Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8a19f54a34 | |||
| 3baecb7b51 | |||
| 5d5b0ae3c1 | |||
| f47c8edb42 | |||
| ef66a24f87 | |||
| f6a5cb8668 | |||
| 4dec385332 | |||
| 5b08e14b55 | |||
| ac381cbf60 | |||
| ce7fdcdcd6 | |||
| a31c9d1e88 | |||
| a5942ff026 |
@@ -27,13 +27,13 @@ library
|
||||
exposed-modules: HPath,
|
||||
HPath.IO,
|
||||
HPath.IO.Errors,
|
||||
HPath.IO.Utils,
|
||||
System.Posix.Directory.Foreign,
|
||||
System.Posix.Directory.Traversals,
|
||||
System.Posix.FD,
|
||||
System.Posix.FilePath
|
||||
other-modules: HPath.Internal
|
||||
build-depends: base >= 4.2 && <5
|
||||
, IfElse
|
||||
, bytestring >= 0.9.2.0
|
||||
, deepseq
|
||||
, exceptions
|
||||
@@ -82,6 +82,7 @@ test-suite spec
|
||||
HPath.IO.CopyFileOverwriteSpec
|
||||
HPath.IO.CopyFileSpec
|
||||
HPath.IO.CreateDirSpec
|
||||
HPath.IO.CreateDirRecursiveSpec
|
||||
HPath.IO.CreateRegularFileSpec
|
||||
HPath.IO.CreateSymlinkSpec
|
||||
HPath.IO.DeleteDirRecursiveSpec
|
||||
@@ -99,6 +100,7 @@ test-suite spec
|
||||
GHC-Options: -Wall
|
||||
Build-Depends: base
|
||||
, HUnit
|
||||
, IfElse
|
||||
, bytestring
|
||||
, hpath
|
||||
, hspec >= 1.3
|
||||
|
||||
@@ -336,6 +336,8 @@ dirname (MkPath fp) = MkPath (takeDirectory $ dropTrailingPathSeparator fp)
|
||||
--
|
||||
-- >>> basename (MkPath "/abc/def/dod") :: Maybe (Path Fn)
|
||||
-- Just "dod"
|
||||
-- >>> basename (MkPath "/abc/def/dod/") :: Maybe (Path Fn)
|
||||
-- Just "dod"
|
||||
-- >>> basename (MkPath "/") :: Maybe (Path Fn)
|
||||
-- Nothing
|
||||
basename :: MonadThrow m => Path b -> m (Path Fn)
|
||||
|
||||
140
src/HPath/IO.hs
140
src/HPath/IO.hs
@@ -55,6 +55,7 @@ module HPath.IO
|
||||
-- * File creation
|
||||
, createRegularFile
|
||||
, createDir
|
||||
, createDirRecursive
|
||||
, createSymlink
|
||||
-- * File renaming/moving
|
||||
, renameFile
|
||||
@@ -88,6 +89,10 @@ import Control.Monad
|
||||
, void
|
||||
, when
|
||||
)
|
||||
import Control.Monad.IfElse
|
||||
(
|
||||
unlessM
|
||||
)
|
||||
import Data.ByteString
|
||||
(
|
||||
ByteString
|
||||
@@ -115,9 +120,11 @@ import Foreign.C.Error
|
||||
(
|
||||
eEXIST
|
||||
, eINVAL
|
||||
, eNOENT
|
||||
, eNOSYS
|
||||
, eNOTEMPTY
|
||||
, eXDEV
|
||||
, getErrno
|
||||
)
|
||||
import Foreign.C.Types
|
||||
(
|
||||
@@ -219,7 +226,7 @@ data FileType = Directory
|
||||
|
||||
|
||||
|
||||
-- |The error mode for any recursive operation.
|
||||
-- |The error mode for recursive operations.
|
||||
--
|
||||
-- On `FailEarly` the whole operation fails immediately if any of the
|
||||
-- recursive sub-operations fail, which is sort of the default
|
||||
@@ -227,7 +234,9 @@ data FileType = Directory
|
||||
--
|
||||
-- On `CollectFailures` skips errors in the recursion and keeps on recursing.
|
||||
-- However all errors are collected in the `RecursiveFailure` error type,
|
||||
-- which is raised finally if there was any error.
|
||||
-- which is raised finally if there was any error. Also note that
|
||||
-- `RecursiveFailure` does not give any guarantees on the ordering
|
||||
-- of the collected exceptions.
|
||||
data RecursiveErrorMode = FailEarly
|
||||
| CollectFailures
|
||||
|
||||
@@ -247,12 +256,13 @@ data CopyMode = Strict -- ^ fail if any target exists
|
||||
|
||||
|
||||
|
||||
-- |Copies the contents of a directory recursively to the given destination.
|
||||
-- Does not follow symbolic links. This behaves more or less like:
|
||||
-- |Copies the contents of a directory recursively to the given destination, while preserving permissions.
|
||||
-- Does not follow symbolic links. This behaves more or less like
|
||||
-- the following, without descending into the destination if it
|
||||
-- already exists:
|
||||
--
|
||||
-- @
|
||||
-- mkdir \/destination\/dir
|
||||
-- cp -R \/source\/dir\/* \/destination\/dir\/
|
||||
-- cp -a \/source\/dir \/destination\/somedir
|
||||
-- @
|
||||
--
|
||||
-- For directory contents, this will ignore any file type that is not
|
||||
@@ -263,9 +273,6 @@ data CopyMode = Strict -- ^ fail if any target exists
|
||||
-- the operation has completed. Permissions of existing directories are
|
||||
-- fixed.
|
||||
--
|
||||
-- Note that there is no guaranteed ordering of the exceptions
|
||||
-- contained within `RecursiveFailure` in `CollectFailures` RecursiveErrorMode.
|
||||
--
|
||||
-- Safety/reliability concerns:
|
||||
--
|
||||
-- * not atomic
|
||||
@@ -298,8 +305,8 @@ data CopyMode = Strict -- ^ fail if any target exists
|
||||
-- Throws in `Strict` CopyMode only:
|
||||
--
|
||||
-- - `AlreadyExists` if destination already exists
|
||||
copyDirRecursive :: Path Abs -- ^ copy contents of this source dir
|
||||
-> Path Abs -- ^ to this full destination (parent dirs
|
||||
copyDirRecursive :: Path Abs -- ^ source dir
|
||||
-> Path Abs -- ^ destination (parent dirs
|
||||
-- are not automatically created)
|
||||
-> CopyMode
|
||||
-> RecursiveErrorMode
|
||||
@@ -315,41 +322,56 @@ copyDirRecursive fromp destdirp cm rm
|
||||
unless (null collectedExceptions)
|
||||
(throwIO . RecursiveFailure $ collectedExceptions)
|
||||
where
|
||||
go :: IORef [IOException] -> Path Abs -> Path Abs -> IO ()
|
||||
go :: IORef [(RecursiveFailureHint, IOException)]
|
||||
-> Path Abs -> Path Abs -> IO ()
|
||||
go ce fromp' destdirp' = do
|
||||
|
||||
-- order is important here, so we don't get empty directories
|
||||
-- NOTE: order is important here, so we don't get empty directories
|
||||
-- on failure
|
||||
contents <- handleIOE ce [] $ do
|
||||
|
||||
-- get the contents of the source dir
|
||||
contents <- handleIOE (ReadContentsFailed fromp' destdirp') ce [] $ do
|
||||
contents <- getDirsFiles fromp'
|
||||
|
||||
fmode' <- PF.fileMode <$> PF.getSymbolicLinkStatus (fromAbs fromp')
|
||||
case cm of
|
||||
Strict -> createDirectory (fromAbs destdirp') fmode'
|
||||
Overwrite -> catchIOError (createDirectory (fromAbs destdirp')
|
||||
fmode')
|
||||
$ \e ->
|
||||
case ioeGetErrorType e of
|
||||
AlreadyExists -> setFileMode (fromAbs destdirp')
|
||||
fmode'
|
||||
_ -> ioError e
|
||||
return contents
|
||||
-- create the destination dir and
|
||||
-- only return contents if we succeed
|
||||
handleIOE (CreateDirFailed fromp' destdirp') ce [] $ do
|
||||
fmode' <- PF.fileMode <$> PF.getSymbolicLinkStatus (fromAbs fromp')
|
||||
case cm of
|
||||
Strict -> createDirectory (fromAbs destdirp') fmode'
|
||||
Overwrite -> catchIOError (createDirectory (fromAbs destdirp')
|
||||
fmode')
|
||||
$ \e ->
|
||||
case ioeGetErrorType e of
|
||||
AlreadyExists -> setFileMode (fromAbs destdirp')
|
||||
fmode'
|
||||
_ -> ioError e
|
||||
return contents
|
||||
|
||||
-- we can't use `easyCopy` here, because we want to call `go`
|
||||
-- NOTE: we can't use `easyCopy` here, because we want to call `go`
|
||||
-- recursively to skip the top-level sanity checks
|
||||
|
||||
-- if reading the contents and creating the destination dir worked,
|
||||
-- then copy the contents to the destination too
|
||||
for_ contents $ \f -> do
|
||||
ftype <- getFileType f
|
||||
newdest <- (destdirp' </>) <$> basename f
|
||||
case ftype of
|
||||
SymbolicLink -> handleIOE ce ()
|
||||
SymbolicLink -> handleIOE (RecreateSymlinkFailed f newdest) ce ()
|
||||
$ recreateSymlink f newdest cm
|
||||
Directory -> go ce f newdest
|
||||
RegularFile -> handleIOE ce () $ copyFile f newdest cm
|
||||
RegularFile -> handleIOE (CopyFileFailed f newdest) ce ()
|
||||
$ copyFile f newdest cm
|
||||
_ -> return ()
|
||||
handleIOE :: IORef [IOException] -> a -> IO a -> IO a
|
||||
handleIOE ce def = case rm of
|
||||
FailEarly -> handleIOError throwIO
|
||||
CollectFailures -> handleIOError (\e -> modifyIORef ce (e:)
|
||||
|
||||
-- helper to handle errors for both RecursiveErrorModes and return a
|
||||
-- default value
|
||||
handleIOE :: RecursiveFailureHint
|
||||
-> IORef [(RecursiveFailureHint, IOException)]
|
||||
-> a -> IO a -> IO a
|
||||
handleIOE hint ce def = case rm of
|
||||
FailEarly -> handleIOError throwIO
|
||||
CollectFailures -> handleIOError (\e -> modifyIORef ce ((hint, e):)
|
||||
>> return def)
|
||||
|
||||
|
||||
@@ -372,7 +394,7 @@ copyDirRecursive fromp destdirp cm rm
|
||||
--
|
||||
-- Throws in `Strict` mode only:
|
||||
--
|
||||
-- - `AlreadyExists` if destination file already exists
|
||||
-- - `AlreadyExists` if destination already exists
|
||||
--
|
||||
-- Throws in `Overwrite` mode only:
|
||||
--
|
||||
@@ -505,8 +527,8 @@ _copyFile sflags dflags from to
|
||||
if size == 0
|
||||
then return $ fromIntegral totalsize
|
||||
else do rsize <- SPB.fdWriteBuf dfd buf size
|
||||
when (rsize /= size) (throwIO . CopyFailed
|
||||
$ "wrong size!")
|
||||
when (rsize /= size) (ioError $ userError
|
||||
"wrong size!")
|
||||
write' sfd dfd buf (totalsize + fromIntegral size)
|
||||
|
||||
|
||||
@@ -660,7 +682,9 @@ executeFile fp args
|
||||
-- Throws:
|
||||
--
|
||||
-- - `PermissionDenied` if output directory cannot be written to
|
||||
-- - `AlreadyExists` if destination file already exists
|
||||
-- - `AlreadyExists` if destination already exists
|
||||
-- - `NoSuchThing` if any of the parent components of the path
|
||||
-- do not exist
|
||||
createRegularFile :: FileMode -> Path Abs -> IO ()
|
||||
createRegularFile fm dest =
|
||||
bracket (SPI.openFd (fromAbs dest) SPI.WriteOnly (Just fm)
|
||||
@@ -674,17 +698,50 @@ createRegularFile fm dest =
|
||||
-- Throws:
|
||||
--
|
||||
-- - `PermissionDenied` if output directory cannot be written to
|
||||
-- - `AlreadyExists` if destination directory already exists
|
||||
-- - `AlreadyExists` if destination already exists
|
||||
-- - `NoSuchThing` if any of the parent components of the path
|
||||
-- do not exist
|
||||
createDir :: FileMode -> Path Abs -> IO ()
|
||||
createDir fm dest = createDirectory (fromAbs dest) fm
|
||||
|
||||
|
||||
-- |Create an empty directory at the given directory with the given filename.
|
||||
-- All parent directories are created with the same filemode. This
|
||||
-- basically behaves like:
|
||||
--
|
||||
-- @
|
||||
-- mkdir -p \/some\/dir
|
||||
-- @
|
||||
--
|
||||
-- Safety/reliability concerns:
|
||||
--
|
||||
-- * not atomic
|
||||
--
|
||||
-- Throws:
|
||||
--
|
||||
-- - `PermissionDenied` if any part of the path components do not
|
||||
-- exist and cannot be written to
|
||||
-- - `AlreadyExists` if destination already exists and
|
||||
-- is not a directory
|
||||
createDirRecursive :: FileMode -> Path Abs -> IO ()
|
||||
createDirRecursive fm dest =
|
||||
catchIOError (createDirectory (fromAbs dest) fm) $ \e -> do
|
||||
errno <- getErrno
|
||||
case errno of
|
||||
en | en == eEXIST -> unlessM (doesDirectoryExist dest) (ioError e)
|
||||
| en == eNOENT -> createDirRecursive fm (dirname dest)
|
||||
>> createDirectory (fromAbs dest) fm
|
||||
| otherwise -> ioError e
|
||||
|
||||
|
||||
-- |Create a symlink.
|
||||
--
|
||||
-- Throws:
|
||||
--
|
||||
-- - `PermissionDenied` if output directory cannot be written to
|
||||
-- - `AlreadyExists` if destination file already exists
|
||||
-- - `NoSuchThing` if any of the parent components of the path
|
||||
-- do not exist
|
||||
--
|
||||
-- Note: calls `symlink`
|
||||
createSymlink :: Path Abs -- ^ destination file
|
||||
@@ -716,10 +773,7 @@ createSymlink dest sympoint
|
||||
-- - `PermissionDenied` if source directory cannot be opened
|
||||
-- - `UnsupportedOperation` if source and destination are on different
|
||||
-- devices
|
||||
-- - `FileDoesExist` if destination file already exists
|
||||
-- (`HPathIOException`)
|
||||
-- - `DirDoesExist` if destination directory already exists
|
||||
-- (`HPathIOException`)
|
||||
-- - `AlreadyExists` if destination already exists
|
||||
-- - `SameFile` if destination and source are the same file
|
||||
-- (`HPathIOException`)
|
||||
--
|
||||
@@ -758,9 +812,7 @@ renameFile fromf tof = do
|
||||
--
|
||||
-- Throws in `Strict` mode only:
|
||||
--
|
||||
-- - `FileDoesExist` if destination file already exists (`HPathIOException`)
|
||||
-- - `DirDoesExist` if destination directory already exists
|
||||
-- (`HPathIOException`)
|
||||
-- - `AlreadyExists` if destination already exists
|
||||
--
|
||||
-- Note: calls `rename` (but does not allow to rename over existing files)
|
||||
moveFile :: Path Abs -- ^ file to move
|
||||
|
||||
@@ -16,24 +16,20 @@ module HPath.IO.Errors
|
||||
(
|
||||
-- * Types
|
||||
HPathIOException(..)
|
||||
, RecursiveFailureHint(..)
|
||||
|
||||
-- * Exception identifiers
|
||||
, isFileDoesNotExist
|
||||
, isDirDoesNotExist
|
||||
, isSameFile
|
||||
, isDestinationInSource
|
||||
, isFileDoesExist
|
||||
, isDirDoesExist
|
||||
, isInvalidOperation
|
||||
, isCan'tOpenDirectory
|
||||
, isCopyFailed
|
||||
, isRecursiveFailure
|
||||
, isReadContentsFailed
|
||||
, isCreateDirFailed
|
||||
, isCopyFileFailed
|
||||
, isRecreateSymlinkFailed
|
||||
|
||||
-- * Path based functions
|
||||
, throwFileDoesExist
|
||||
, throwDirDoesExist
|
||||
, throwFileDoesNotExist
|
||||
, throwDirDoesNotExist
|
||||
, throwSameFile
|
||||
, sameFile
|
||||
, throwDestinationInSource
|
||||
@@ -41,7 +37,6 @@ module HPath.IO.Errors
|
||||
, doesDirectoryExist
|
||||
, isWritable
|
||||
, canOpenDirectory
|
||||
, throwCantOpenDirectory
|
||||
|
||||
-- * Error handling functions
|
||||
, catchErrno
|
||||
@@ -63,6 +58,10 @@ import Control.Monad
|
||||
forM
|
||||
, when
|
||||
)
|
||||
import Control.Monad.IfElse
|
||||
(
|
||||
whenM
|
||||
)
|
||||
import Data.ByteString
|
||||
(
|
||||
ByteString
|
||||
@@ -72,6 +71,9 @@ import Data.ByteString.UTF8
|
||||
toString
|
||||
)
|
||||
import Data.Typeable
|
||||
(
|
||||
Typeable
|
||||
)
|
||||
import Foreign.C.Error
|
||||
(
|
||||
getErrno
|
||||
@@ -86,11 +88,12 @@ import {-# SOURCE #-} HPath.IO
|
||||
(
|
||||
canonicalizePath
|
||||
)
|
||||
import HPath.IO.Utils
|
||||
import System.IO.Error
|
||||
(
|
||||
catchIOError
|
||||
alreadyExistsErrorType
|
||||
, catchIOError
|
||||
, ioeGetErrorType
|
||||
, mkIOError
|
||||
)
|
||||
|
||||
import qualified System.Posix.Directory.ByteString as PFD
|
||||
@@ -102,57 +105,36 @@ import System.Posix.Files.ByteString
|
||||
import qualified System.Posix.Files.ByteString as PF
|
||||
|
||||
|
||||
data HPathIOException = FileDoesNotExist ByteString
|
||||
| DirDoesNotExist ByteString
|
||||
| SameFile ByteString ByteString
|
||||
-- |Additional generic IO exceptions that the posix functions
|
||||
-- do not provide.
|
||||
data HPathIOException = SameFile ByteString ByteString
|
||||
| DestinationInSource ByteString ByteString
|
||||
| FileDoesExist ByteString
|
||||
| DirDoesExist ByteString
|
||||
| InvalidOperation String
|
||||
| Can'tOpenDirectory ByteString
|
||||
| CopyFailed String
|
||||
| RecursiveFailure [IOException]
|
||||
deriving (Typeable, Eq)
|
||||
|
||||
|
||||
instance Show HPathIOException where
|
||||
show (FileDoesNotExist fp) = "File does not exist:" ++ toString fp
|
||||
show (DirDoesNotExist fp) = "Directory does not exist: "
|
||||
++ toString fp
|
||||
show (SameFile fp1 fp2) = toString fp1
|
||||
++ " and " ++ toString fp2
|
||||
++ " are the same file!"
|
||||
show (DestinationInSource fp1 fp2) = toString fp1
|
||||
++ " is contained in "
|
||||
++ toString fp2
|
||||
show (FileDoesExist fp) = "File does exist: " ++ toString fp
|
||||
show (DirDoesExist fp) = "Directory does exist: " ++ toString fp
|
||||
show (InvalidOperation str) = "Invalid operation: " ++ str
|
||||
show (Can'tOpenDirectory fp) = "Can't open directory: "
|
||||
++ toString fp
|
||||
show (CopyFailed str) = "Copying failed: " ++ str
|
||||
show (RecursiveFailure exs) = "Recursive operation failed: "
|
||||
++ show exs
|
||||
|
||||
|
||||
toConstr :: HPathIOException -> String
|
||||
toConstr FileDoesNotExist {} = "FileDoesNotExist"
|
||||
toConstr DirDoesNotExist {} = "DirDoesNotExist"
|
||||
toConstr SameFile {} = "SameFile"
|
||||
toConstr DestinationInSource {} = "DestinationInSource"
|
||||
toConstr FileDoesExist {} = "FileDoesExist"
|
||||
toConstr DirDoesExist {} = "DirDoesExist"
|
||||
toConstr InvalidOperation {} = "InvalidOperation"
|
||||
toConstr Can'tOpenDirectory {} = "Can'tOpenDirectory"
|
||||
toConstr CopyFailed {} = "CopyFailed"
|
||||
toConstr RecursiveFailure {} = "RecursiveFailure"
|
||||
| RecursiveFailure [(RecursiveFailureHint, IOException)]
|
||||
deriving (Eq, Show, Typeable)
|
||||
|
||||
|
||||
-- |A type for giving failure hints on recursive failure, which allows
|
||||
-- to programmatically make choices without examining
|
||||
-- the weakly typed I/O error attributes (like `ioeGetFileName`).
|
||||
--
|
||||
-- The first argument to the data constructor is always the
|
||||
-- source and the second the destination.
|
||||
data RecursiveFailureHint = ReadContentsFailed (Path Abs) (Path Abs)
|
||||
| CreateDirFailed (Path Abs) (Path Abs)
|
||||
| CopyFileFailed (Path Abs) (Path Abs)
|
||||
| RecreateSymlinkFailed (Path Abs) (Path Abs)
|
||||
deriving (Eq, Show)
|
||||
|
||||
|
||||
instance Exception HPathIOException
|
||||
|
||||
|
||||
toConstr :: HPathIOException -> String
|
||||
toConstr SameFile {} = "SameFile"
|
||||
toConstr DestinationInSource {} = "DestinationInSource"
|
||||
toConstr RecursiveFailure {} = "RecursiveFailure"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -160,47 +142,54 @@ instance Exception HPathIOException
|
||||
--[ Exception identifiers ]--
|
||||
-----------------------------
|
||||
|
||||
isFileDoesNotExist, isDirDoesNotExist, isSameFile, isDestinationInSource, isFileDoesExist, isDirDoesExist, isInvalidOperation, isCan'tOpenDirectory, isCopyFailed, isRecursiveFailure :: HPathIOException -> Bool
|
||||
isFileDoesNotExist ex = toConstr (ex :: HPathIOException) == toConstr FileDoesNotExist{}
|
||||
isDirDoesNotExist ex = toConstr (ex :: HPathIOException) == toConstr DirDoesNotExist{}
|
||||
|
||||
isSameFile, isDestinationInSource, isRecursiveFailure :: HPathIOException -> Bool
|
||||
isSameFile ex = toConstr (ex :: HPathIOException) == toConstr SameFile{}
|
||||
isDestinationInSource ex = toConstr (ex :: HPathIOException) == toConstr DestinationInSource{}
|
||||
isFileDoesExist ex = toConstr (ex :: HPathIOException) == toConstr FileDoesExist{}
|
||||
isDirDoesExist ex = toConstr (ex :: HPathIOException) == toConstr DirDoesExist{}
|
||||
isInvalidOperation ex = toConstr (ex :: HPathIOException) == toConstr InvalidOperation{}
|
||||
isCan'tOpenDirectory ex = toConstr (ex :: HPathIOException) == toConstr Can'tOpenDirectory{}
|
||||
isCopyFailed ex = toConstr (ex :: HPathIOException) == toConstr CopyFailed{}
|
||||
isRecursiveFailure ex = toConstr (ex :: HPathIOException) == toConstr RecursiveFailure{}
|
||||
|
||||
|
||||
isReadContentsFailed, isCreateDirFailed, isCopyFileFailed, isRecreateSymlinkFailed ::RecursiveFailureHint -> Bool
|
||||
isReadContentsFailed ReadContentsFailed{} = True
|
||||
isReadContentsFailed _ = False
|
||||
isCreateDirFailed CreateDirFailed{} = True
|
||||
isCreateDirFailed _ = False
|
||||
isCopyFileFailed CopyFileFailed{} = True
|
||||
isCopyFileFailed _ = False
|
||||
isRecreateSymlinkFailed RecreateSymlinkFailed{} = True
|
||||
isRecreateSymlinkFailed _ = False
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----------------------------
|
||||
--[ Path based functions ]--
|
||||
----------------------------
|
||||
|
||||
|
||||
-- |Throws `AlreadyExists` `IOError` if file exists.
|
||||
throwFileDoesExist :: Path Abs -> IO ()
|
||||
throwFileDoesExist fp =
|
||||
whenM (doesFileExist fp) (throwIO . FileDoesExist
|
||||
. fromAbs $ fp)
|
||||
whenM (doesFileExist fp)
|
||||
(ioError . mkIOError
|
||||
alreadyExistsErrorType
|
||||
"File already exists"
|
||||
Nothing
|
||||
$ (Just (toString $ fromAbs fp))
|
||||
)
|
||||
|
||||
|
||||
-- |Throws `AlreadyExists` `IOError` if directory exists.
|
||||
throwDirDoesExist :: Path Abs -> IO ()
|
||||
throwDirDoesExist fp =
|
||||
whenM (doesDirectoryExist fp) (throwIO . DirDoesExist
|
||||
. fromAbs $ fp)
|
||||
|
||||
|
||||
throwFileDoesNotExist :: Path Abs -> IO ()
|
||||
throwFileDoesNotExist fp =
|
||||
unlessM (doesFileExist fp) (throwIO . FileDoesNotExist
|
||||
. fromAbs $ fp)
|
||||
|
||||
|
||||
throwDirDoesNotExist :: Path Abs -> IO ()
|
||||
throwDirDoesNotExist fp =
|
||||
unlessM (doesDirectoryExist fp) (throwIO . DirDoesNotExist
|
||||
. fromAbs $ fp)
|
||||
whenM (doesDirectoryExist fp)
|
||||
(ioError . mkIOError
|
||||
alreadyExistsErrorType
|
||||
"Directory already exists"
|
||||
Nothing
|
||||
$ (Just (toString $ fromAbs fp))
|
||||
)
|
||||
|
||||
|
||||
-- |Uses `isSameFile` and throws `SameFile` if it returns True.
|
||||
@@ -285,13 +274,6 @@ canOpenDirectory fp =
|
||||
return True
|
||||
|
||||
|
||||
-- |Throws a `Can'tOpenDirectory` HPathIOException if the directory at the given
|
||||
-- path cannot be opened.
|
||||
throwCantOpenDirectory :: Path Abs -> IO ()
|
||||
throwCantOpenDirectory fp =
|
||||
unlessM (canOpenDirectory fp)
|
||||
(throwIO . Can'tOpenDirectory . fromAbs $ fp)
|
||||
|
||||
|
||||
|
||||
--------------------------------
|
||||
@@ -371,3 +353,4 @@ reactOnError a ios fmios =
|
||||
else y)
|
||||
(throwIO ex)
|
||||
fmios
|
||||
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
-- |
|
||||
-- Module : HPath.IO.Utils
|
||||
-- Copyright : © 2016 Julian Ospald
|
||||
-- License : BSD3
|
||||
--
|
||||
-- Maintainer : Julian Ospald <hasufell@posteo.de>
|
||||
-- Stability : experimental
|
||||
-- Portability : portable
|
||||
--
|
||||
-- Random and general IO/monad utilities.
|
||||
|
||||
|
||||
module HPath.IO.Utils where
|
||||
|
||||
|
||||
import Control.Monad
|
||||
(
|
||||
when
|
||||
, unless
|
||||
)
|
||||
|
||||
|
||||
-- |If the value of the first argument is True, then execute the action
|
||||
-- provided in the second argument, otherwise do nothing.
|
||||
whenM :: Monad m => m Bool -> m () -> m ()
|
||||
whenM mb a = mb >>= (`when` a)
|
||||
|
||||
|
||||
-- |If the value of the first argument is False, then execute the action
|
||||
-- provided in the second argument, otherwise do nothing.
|
||||
unlessM :: Monad m => m Bool -> m () -> m ()
|
||||
unlessM mb a = mb >>= (`unless` a)
|
||||
@@ -157,8 +157,10 @@ spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
|
||||
CollectFailures
|
||||
`shouldThrow`
|
||||
(\(RecursiveFailure ex@[_, _]) ->
|
||||
any (\e -> ioeGetErrorType e == InappropriateType) ex &&
|
||||
any (\e -> ioeGetErrorType e == PermissionDenied) ex)
|
||||
any (\(h, e) -> ioeGetErrorType e == InappropriateType
|
||||
&& isCopyFileFailed h) ex &&
|
||||
any (\(h, e) -> ioeGetErrorType e == PermissionDenied
|
||||
&& isReadContentsFailed h) ex)
|
||||
normalDirPerms "outputDir1/foo2/foo4"
|
||||
normalDirPerms "outputDir1/foo2/foo4/inputFile4"
|
||||
c <- allDirectoryContents' "outputDir1"
|
||||
@@ -184,7 +186,7 @@ spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
|
||||
Strict
|
||||
CollectFailures
|
||||
`shouldThrow`
|
||||
(\(RecursiveFailure [e]) -> ioeGetErrorType e == PermissionDenied)
|
||||
(\(RecursiveFailure [(CreateDirFailed{}, e)]) -> ioeGetErrorType e == PermissionDenied)
|
||||
|
||||
it "copyDirRecursive (Strict, CollectFailures), cannot open output dir" $
|
||||
copyDirRecursive' "inputDir"
|
||||
@@ -200,7 +202,7 @@ spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
|
||||
Strict
|
||||
CollectFailures
|
||||
`shouldThrow`
|
||||
(\(RecursiveFailure [e]) -> ioeGetErrorType e == AlreadyExists)
|
||||
(\(RecursiveFailure [(CreateDirFailed{}, e)]) -> ioeGetErrorType e == AlreadyExists)
|
||||
|
||||
it "copyDirRecursive (Strict, CollectFailures), destination already exists and is a file" $
|
||||
copyDirRecursive' "inputDir"
|
||||
@@ -216,7 +218,7 @@ spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
|
||||
Strict
|
||||
CollectFailures
|
||||
`shouldThrow`
|
||||
(\(RecursiveFailure [e]) -> ioeGetErrorType e == InappropriateType)
|
||||
(\(RecursiveFailure [(ReadContentsFailed{}, e)]) -> ioeGetErrorType e == InappropriateType)
|
||||
|
||||
it "copyDirRecursive (Strict, CollectFailures), wrong input (symlink to directory)" $
|
||||
copyDirRecursive' "wrongInputSymL"
|
||||
@@ -224,7 +226,7 @@ spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
|
||||
Strict
|
||||
CollectFailures
|
||||
`shouldThrow`
|
||||
(\(RecursiveFailure [e]) -> ioeGetErrorType e == InvalidArgument)
|
||||
(\(RecursiveFailure [(ReadContentsFailed{}, e)]) -> ioeGetErrorType e == InvalidArgument)
|
||||
|
||||
it "copyDirRecursive (Strict, CollectFailures), destination in source" $
|
||||
copyDirRecursive' "inputDir"
|
||||
|
||||
78
test/HPath/IO/CreateDirRecursiveSpec.hs
Normal file
78
test/HPath/IO/CreateDirRecursiveSpec.hs
Normal file
@@ -0,0 +1,78 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
module HPath.IO.CreateDirRecursiveSpec where
|
||||
|
||||
|
||||
import Test.Hspec
|
||||
import System.IO.Error
|
||||
(
|
||||
ioeGetErrorType
|
||||
)
|
||||
import GHC.IO.Exception
|
||||
(
|
||||
IOErrorType(..)
|
||||
)
|
||||
import Utils
|
||||
|
||||
|
||||
|
||||
upTmpDir :: IO ()
|
||||
upTmpDir = do
|
||||
setTmpDir "CreateDirRecursiveSpec"
|
||||
createTmpDir
|
||||
|
||||
setupFiles :: IO ()
|
||||
setupFiles = do
|
||||
createDir' "alreadyExists"
|
||||
createRegularFile' "alreadyExistsF"
|
||||
createDir' "noPerms"
|
||||
createDir' "noWritePerms"
|
||||
noPerms "noPerms"
|
||||
noWritableDirPerms "noWritePerms"
|
||||
|
||||
cleanupFiles :: IO ()
|
||||
cleanupFiles = do
|
||||
normalDirPerms "noPerms"
|
||||
normalDirPerms "noWritePerms"
|
||||
deleteDir' "alreadyExists"
|
||||
deleteDir' "noPerms"
|
||||
deleteDir' "noWritePerms"
|
||||
deleteFile' "alreadyExistsF"
|
||||
|
||||
|
||||
spec :: Spec
|
||||
spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
|
||||
describe "HPath.IO.createDirRecursive" $ do
|
||||
|
||||
-- successes --
|
||||
it "createDirRecursive, all fine" $ do
|
||||
createDirRecursive' "newDir"
|
||||
deleteDir' "newDir"
|
||||
|
||||
it "createDirRecursive, parent directories do not exist" $ do
|
||||
createDirRecursive' "some/thing/dada"
|
||||
deleteDir' "some/thing/dada"
|
||||
deleteDir' "some/thing"
|
||||
deleteDir' "some"
|
||||
|
||||
it "createDirRecursive, destination directory already exists" $
|
||||
createDirRecursive' "alreadyExists"
|
||||
|
||||
-- posix failures --
|
||||
it "createDirRecursive, destination already exists and is a file" $
|
||||
createDirRecursive' "alreadyExistsF"
|
||||
`shouldThrow`
|
||||
(\e -> ioeGetErrorType e == AlreadyExists)
|
||||
|
||||
it "createDirRecursive, can't write to output directory" $
|
||||
createDirRecursive' "noWritePerms/newDir"
|
||||
`shouldThrow`
|
||||
(\e -> ioeGetErrorType e == PermissionDenied)
|
||||
|
||||
it "createDirRecursive, can't open output directory" $
|
||||
createDirRecursive' "noPerms/newDir"
|
||||
`shouldThrow`
|
||||
(\e -> ioeGetErrorType e == PermissionDenied)
|
||||
|
||||
|
||||
|
||||
@@ -50,6 +50,11 @@ spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
|
||||
removeDirIfExists "newDir"
|
||||
|
||||
-- posix failures --
|
||||
it "createDir, parent directories do not exist" $
|
||||
createDir' "some/thing/dada"
|
||||
`shouldThrow`
|
||||
(\e -> ioeGetErrorType e == NoSuchThing)
|
||||
|
||||
it "createDir, can't write to output directory" $
|
||||
createDir' "noWritePerms/newDir"
|
||||
`shouldThrow`
|
||||
|
||||
@@ -48,6 +48,11 @@ spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
|
||||
removeFileIfExists "newDir"
|
||||
|
||||
-- posix failures --
|
||||
it "createRegularFile, parent directories do not exist" $
|
||||
createRegularFile' "some/thing/dada"
|
||||
`shouldThrow`
|
||||
(\e -> ioeGetErrorType e == NoSuchThing)
|
||||
|
||||
it "createRegularFile, can't write to destination directory" $
|
||||
createRegularFile' "noWritePerms/newDir"
|
||||
`shouldThrow`
|
||||
|
||||
@@ -49,6 +49,11 @@ spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
|
||||
removeFileIfExists "newSymL"
|
||||
|
||||
-- posix failures --
|
||||
it "createSymlink, parent directories do not exist" $
|
||||
createSymlink' "some/thing/dada" "lala"
|
||||
`shouldThrow`
|
||||
(\e -> ioeGetErrorType e == NoSuchThing)
|
||||
|
||||
it "createSymlink, can't write to destination directory" $
|
||||
createSymlink' "noWritePerms/newDir" "lala"
|
||||
`shouldThrow`
|
||||
|
||||
@@ -116,7 +116,7 @@ spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
|
||||
"alreadyExistsD"
|
||||
Overwrite
|
||||
`shouldThrow`
|
||||
isDirDoesExist
|
||||
(\e -> ioeGetErrorType e == AlreadyExists)
|
||||
|
||||
it "moveFile (Overwrite), source and dest are same file" $
|
||||
moveFile' "myFile"
|
||||
|
||||
@@ -112,14 +112,14 @@ spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
|
||||
"alreadyExists"
|
||||
Strict
|
||||
`shouldThrow`
|
||||
isFileDoesExist
|
||||
(\e -> ioeGetErrorType e == AlreadyExists)
|
||||
|
||||
it "moveFile (Strict), move from file to dir" $
|
||||
moveFile' "myFile"
|
||||
"alreadyExistsD"
|
||||
Strict
|
||||
`shouldThrow`
|
||||
isDirDoesExist
|
||||
(\e -> ioeGetErrorType e == AlreadyExists)
|
||||
|
||||
it "moveFile (Strict), source and dest are same file" $
|
||||
moveFile' "myFile"
|
||||
|
||||
@@ -101,13 +101,13 @@ spec = beforeAll_ (upTmpDir >> setupFiles) $ afterAll_ cleanupFiles $
|
||||
renameFile' "myFile"
|
||||
"alreadyExists"
|
||||
`shouldThrow`
|
||||
isFileDoesExist
|
||||
(\e -> ioeGetErrorType e == AlreadyExists)
|
||||
|
||||
it "renameFile, move from file to dir" $
|
||||
renameFile' "myFile"
|
||||
"alreadyExistsD"
|
||||
`shouldThrow`
|
||||
isDirDoesExist
|
||||
(\e -> ioeGetErrorType e == AlreadyExists)
|
||||
|
||||
it "renameFile, source and dest are same file" $
|
||||
renameFile' "myFile"
|
||||
|
||||
@@ -14,6 +14,10 @@ import Control.Monad
|
||||
forM_
|
||||
, void
|
||||
)
|
||||
import Control.Monad.IfElse
|
||||
(
|
||||
whenM
|
||||
)
|
||||
import qualified Data.ByteString as BS
|
||||
import Data.IORef
|
||||
(
|
||||
@@ -24,7 +28,6 @@ import Data.IORef
|
||||
)
|
||||
import HPath.IO
|
||||
import HPath.IO.Errors
|
||||
import HPath.IO.Utils
|
||||
import Data.Maybe
|
||||
(
|
||||
fromJust
|
||||
@@ -179,6 +182,9 @@ createDir' :: ByteString -> IO ()
|
||||
{-# NOINLINE createDir' #-}
|
||||
createDir' dest = withTmpDir dest (createDir newDirPerms)
|
||||
|
||||
createDirRecursive' :: ByteString -> IO ()
|
||||
{-# NOINLINE createDirRecursive' #-}
|
||||
createDirRecursive' dest = withTmpDir dest (createDirRecursive newDirPerms)
|
||||
|
||||
createRegularFile' :: ByteString -> IO ()
|
||||
{-# NOINLINE createRegularFile' #-}
|
||||
|
||||
Reference in New Issue
Block a user