packages feed

uniform-fileio-0.1.3: Uniform/FileStrings.hs

---------------------------------------------------------------------
-- Module      :  FileIO.Strings
--
----------------------------------------------------------------------
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE TypeFamilies #-}
{-# OPTIONS_GHC -fno-warn-missing-methods #-}

module Uniform.FileStrings
  ( module Uniform.Filenames,
    module Uniform.FileIOalgebra,
    SIO.IOMode (..),
    closeFile2,
    listDir',
    TIO.hGetLine,
    TIO.hPutStr,
  )
where

import Control.Arrow (first, second)
import Control.DeepSeq (force, ($!!))
import Control.Exception (SomeException, catch)
import Control.Monad (filterM, when)
import Control.Monad.Catch as Catch
  ( Exception,
    MonadThrow,
    SomeException,
  )
import Control.Monad.IO.Class (MonadIO (..))
import qualified Data.ByteString as BS (readFile, writeFile)
import qualified Data.ByteString.Lazy as L
import Data.Digest.Pure.MD5 (md5)
import Data.Either (isLeft)
import Data.List (isPrefixOf)
import Data.Maybe (catMaybes)
import qualified Data.Text.IO as T (appendFile, readFile, writeFile)
import qualified Data.Text.IO as TIO (hGetLine, hPutStr)
import qualified Path
import qualified Path.IO as PathIO
import qualified System.Directory as D
import qualified System.FilePath as OS
import qualified System.IO as SIO
import System.Posix (FileMode)
import qualified System.Posix as Posix
import Uniform.FileIOalgebra
import Uniform.FileStatus
import Uniform.Filenames
import Uniform.Filenames as FN (toFilePath)
import Uniform.Strings -- (Text)
import Uniform.Error


closeFile2 :: SIO.Handle -> ErrIO ()
-- close a handle, does not need a filepath
closeFile2 handle = callIO $ SIO.hClose handle

instance FileHandles String where
  write2handle h c = callIO $ SIO.hPutStr h c
  readLine4handle h = callIO $ SIO.hGetLine h

instance FileHandles L.ByteString where
  write2handle h c = callIO $ L.hPutStr h c
  readLine4handle h = error "readLine4handle not implemented for lazy bytestring in FileStrings"

instance FileHandles Text where
  write2handle h c = callIO $ TIO.hPutStr h c
  readLine4handle h = callIO $ TIO.hGetLine h

instance FileHandles [Text] where
  write2handle h c = callIO $ TIO.hPutStr h (unlines' c)
  readLine4handle h = do
    res <- callIO $ TIO.hGetLine h
    return . lines' $ res

listDir' ::
  (MonadIO m, MonadThrow m) =>
  -- | Directory to list
  Path b Dir ->
  -- | Sub-directories and files
  m ([Path Abs Dir], [Path Abs File])
listDir' p = do
  abList :: ([Path.Path Abs Dir], [Path.Path Abs File]) <-
    PathIO.listDir . unPath $ p
  let abPathList = abList
  return abPathList

instance FileSystemOps FilePath where
  checkSymbolicLink fp = callIO $ D.pathIsSymbolicLink fp
  getPermissions' = callIO . D.getPermissions

instance DirOps FilePath where
  doesDirExist' = callIO . D.doesDirectoryExist
  createDirIfMissing' = callIO . D.createDirectoryIfMissing True

  createDir' = callIO . D.createDirectory
  renameDir' old new = do
    putIOwords ["renamed start"]
    testSource <- doesDirExist' old
    testTarget <- doesDirExist' new
    if testTarget
      then
        throwErrorT . showT $ new 
      else
        if not testSource
          then
            throwErrorT . showT $ old 
          else do
            callIO $ putStrLn "renamed"
            r <- callIO $ D.renameDirectory old new
            return ()

  getDirectoryDirs' dir = filterM f =<< getDirCont dir
    where
      f = doesDirExist'
  getDirectoryDirsNonHidden' dir = filterM f =<< getDirContNonHidden dir
    where
      f = doesDirExist'

  deleteDirRecursive f =
    do
      t <- doesDirExist' f
      when t $ do
        callIO . D.removeDirectoryRecursive $ f

        putIOwords ["deleted", showT f]

instance FileOps FilePath where
  doesFileExist' = callIO . D.doesFileExist

  copyOneFile old new = do
    -- source must exist, target must not exist
    t <- doesFileExist' old
    t2 <- doesFileExist' new
    if t && not t2
      then do
        let dir = getParentDir new -- was takeDir
        direxist <- doesDirExist' dir
        unless direxist $
          createDirIfMissing' dir
        callIO $ D.copyFile old new
      else
        if not t
          then
            throwErrorWords ["copyFile source not exist", showT old]
          else
            if t2
              then
                throwErrorWords ["copyFile target exist", showT new]
              else throwErrorWords ["copyOneFile", "other error"]
  copyOneFileOver old new = do
    -- may overwrite existing target
    t <- doesFileExist' old
    if t
      then do
        let dir = getParentDir new -- was takeDir
        direxist <- doesDirExist' dir
        unless direxist $
          createDirIfMissing' dir
        callIO $ D.copyFile old new
      else -- not t - not existing source
        throwErrorWords ["copyFileOver source not exist", showT old]

  getMD5 fn =
    do
      status <- getSymbolicLinkStatus fn
      let regular = isRegularFile status
      readable <- getFileAccess fn (True, False, False)
      if regular && readable
        then callIO $ do
          filedata :: L.ByteString <- L.readFile fn
          let res = showT $ md5 filedata
          return $!! (Just res)
        else throwErrorWords ["getMD5 error file not readable", showT fn]
      `catchE` \e -> do
        putIOwords ["getMD5 in FileStrings.hs", showT fn, showT e]

        throwErrorWords ["getMD5 error for", showT fn]

  getDirCont fn = getDirContAll True fn

  getDirContNonHidden fp = do
    r <- getDirContAll False fp
    return r

  deleteFile f = do
    callIO . D.removeFile $ f

  getAppConfigDirectory = error "not implemented" -- do

  getSymbolicLinkStatus fp = do
    st <- callIO $ Posix.getSymbolicLinkStatus fp
    return st

  getFileAccess fp (r, w, e) =
    callIO $
      Posix.fileAccess fp r w e

  getFileModificationTime fp = do
    stat :: Posix.FileStatus <- getFileStatus' fp
    let time = getModificationTimeFromStatus stat
    return time

  openFile2handle fp mode =
    callIO $ SIO.openFile fp mode

getDirContAll hiddenFlag fn = do
  -- the hiddenFlag must be true to include them
  testDir <- doesDirExist' fn
  readExec <- getFileAccess fn (True, False, True)
  if testDir && readExec
    then do
      r <- callIO . D.listDirectory $ fn
      let r2 = filter (\file' -> file' /= "." && file' /= "..") r
      let r3 =
            if hiddenFlag
              then r2
              else filter (not . isPrefixOf ".") r2
      let r4 = map (fn </>) r3
      return r4
    else
      throwErrorWords
        [ "getDirCont not exist or not readable",
          showT fn,
          showT testDir,
          showT readExec
        ]

instance FileSystemOps (Path ar df) where
  getPermissions' = PathIO.getPermissions . unPath
  checkSymbolicLink fp = callIO $ D.pathIsSymbolicLink (unL fp)

instance DirOps (Path Abs Dir) where
  doesDirExist' = PathIO.doesDirExist

  createDir' = PathIO.createDir . unPath

  renameDir' old new =
    -- :: fp -> fp ->  ErrIO Text
    PathIO.renameDir (unPath old) (unPath new)

  getDirectoryDirs' dir = do
    res <- filterM f =<< getDirCont (toFilePath dir)
    return . map makeAbsDir $ res
    where
      f = doesDirExist'
  getDirectoryDirsNonHidden' dir = do
    res <- filterM f =<< getDirContNonHidden (toFilePath dir)
    return . map makeAbsDir $ res
    where
      f = doesDirExist'

  createDirIfMissing' = PathIO.createDirIfMissing True . unPath

  copyDirRecursive old new = PathIO.copyDirRecur (unPath old) (unPath new)

  deleteDirRecursive f = deleteDirRecursive (unL f)

instance DirOps (Path Rel Dir) where
  doesDirExist' = PathIO.doesDirExist

  createDir' = PathIO.createDir . unPath

  renameDir' old new =
    PathIO.renameDir (unPath old) (unPath new)

  getDirectoryDirs' dir = do
    res <- filterM f =<< getDirCont (toFilePath dir)
    return . map makeRelDir $ res
    where
      f = doesDirExist'

  getDirectoryDirsNonHidden' dir = do
    res <- filterM f =<< getDirContNonHidden (toFilePath dir)
    return . map makeRelDir $ res
    where
      f = doesDirExist'

  createDirIfMissing' = PathIO.createDirIfMissing True . unPath

  copyDirRecursive old new = PathIO.copyDirRecur (unPath old) (unPath new)

  deleteDirRecursive f = deleteDirRecursive (unL f)

instance (Show (Path ar File)) => FileOps (Path ar File) where
  doesFileExist' = PathIO.doesFileExist . unPath

  copyOneFile old new = copyOneFile (unL old) (unL new)
  copyOneFileOver old new = copyOneFileOver (unL old) (unL new)
  renameOneFile old new =
    PathIO.renameFile (unPath old) (unPath new)

  deleteFile f = deleteFile (unL f)

  getMD5 fp = getMD5 (unL fp)

  getDirCont fp =
    error "getDirCont cannot be implemented for Path"
  getDirContNonHidden fp =
    error "getDirContentNonHidden cannot be implemented for Path"

  getFileModificationTime fp = getFileModificationTime (unL fp)

  openFile2handle fp mode = openFile2handle (unL fp) mode

  getFileAccess fp (r, w, e) =
    callIO
      ( do
          Posix.fileAccess (unL fp) r w e
          `catch` \e -> do
            putIOwords ["getFileAccess error", showT fp, s2t $ show (e::SomeException)]
            return False
      )

unL = FN.toFilePath

readFileT :: Path ar File -> ErrIO Text
readFileT fp = callIO . T.readFile . unL $ fp

writeFileT :: Path ar File -> Text -> ErrIO ()
writeFileT fp st = callIO $ T.writeFile (unL fp) st

-- attention - does not create file if not existing

instance (Show (Path ar File)) => FileOps2 (Path ar File) String where
  readFile2 fp = callIO $ readFile (unL fp)

  -- a strict read (does cloes?)
  writeFile2 fp st = callIO $ writeFile (unL fp) st
  appendFile2 fp st = callIO $ appendFile (unL fp) st

instance (Show (Path ar File)) => FileOps2 (Path ar File) Text where
  readFile2 fp = readFile2 (unL fp)

  writeFile2 fp st = writeFile2 (unL fp) st
  appendFile2 fp st = appendFile2 (unL fp) st

  writeFileOrCreate2 filepath st = do
    let dir = getParentDir filepath

    createDirIfMissing' dir
    when False $ putIOwords ["writeFileOrCreate2 dir created", showT dir]
    t <- doesDirExist' dir
    when False $ putIOwords ["writeFileOrCreate2 dir test", showT t]
    writeFile2 filepath st
    when False $ putIOwords ["writeFileOrCreate2 file written", showT filepath]

instance FileOps2 FilePath Text where
  readFile2 fp = callIO $ T.readFile fp
  writeFile2 fp st = callIO $ T.writeFile fp st
  appendFile2 fp st = callIO $ T.appendFile fp st

instance FileOps2 FilePath L.ByteString where
  readFile2 fp = callIO $ L.readFile fp
  writeFile2 fp st = callIO $ L.writeFile fp st
  appendFile2 fp st = callIO $ L.appendFile fp st

instance (Show (Path ar File)) => FileOps2 (Path ar File) L.ByteString where
  readFile2 fp = callIO $ L.readFile . unL $ fp
  writeFile2 fp st = callIO $ L.writeFile (unL fp) st
  appendFile2 fp st = callIO $ L.appendFile (unL fp) st

instance FileOps2a FilePath FilePath where
  getDirContentFiles dir =
    filterM doesFileExist'
      =<< getDirCont dir

  getDirContentNonHiddenFiles dir =
    filterM doesFileExist'
      =<< getDirContNonHidden dir

instance FileOps2a (Path Abs Dir) (Path Abs File) where
  getDirContentFiles dir = do
    res <- getDirContentFiles (toFilePath dir)
    return (map makeAbsFile res)
  getDirContentNonHiddenFiles dir = do
    res <- getDirContentNonHiddenFiles (toFilePath dir)
    return (map makeAbsFile res)

instance FileOps2a (Path Rel Dir) (Path Rel File) where
  getDirContentFiles dir = do
    res <- getDirContentFiles (toFilePath dir)
    return (map makeRelFile res)
  getDirContentNonHiddenFiles dir = do
    res <- getDirContentNonHiddenFiles (toFilePath dir)
    return (map makeRelFile res)