LIB: add copyFileOverwrite
This commit is contained in:
parent
1487351f29
commit
d58fd6e6f0
@ -315,7 +315,32 @@ recreateSymlink symsource newsym
|
|||||||
copyFile :: Path Abs -- ^ source file
|
copyFile :: Path Abs -- ^ source file
|
||||||
-> Path Abs -- ^ destination file
|
-> Path Abs -- ^ destination file
|
||||||
-> IO ()
|
-> IO ()
|
||||||
copyFile from to
|
copyFile from to = _copyFile SPI.defaultFileFlags { exclusive = True } from to
|
||||||
|
|
||||||
|
|
||||||
|
-- |Like `copyFile` except it overwrites the destination if it already exists.
|
||||||
|
-- This also works if source and destination are the same file.
|
||||||
|
--
|
||||||
|
-- Throws:
|
||||||
|
--
|
||||||
|
-- - `NoSuchThing` if source file does not exist
|
||||||
|
-- - `PermissionDenied` if output directory is not writable
|
||||||
|
-- - `PermissionDenied` if source directory can't be opened
|
||||||
|
-- - `InvalidArgument` if source file is wrong type (symlink)
|
||||||
|
-- - `InvalidArgument` if source file is wrong type (directory)
|
||||||
|
--
|
||||||
|
-- Note: calls `sendfile`
|
||||||
|
copyFileOverwrite :: Path Abs -- ^ source file
|
||||||
|
-> Path Abs -- ^ destination file
|
||||||
|
-> IO ()
|
||||||
|
copyFileOverwrite from to = _copyFile SPI.defaultFileFlags { exclusive = False } from to
|
||||||
|
|
||||||
|
|
||||||
|
_copyFile :: SPI.OpenFileFlags
|
||||||
|
-> Path Abs -- ^ source file
|
||||||
|
-> Path Abs -- ^ destination file
|
||||||
|
-> IO ()
|
||||||
|
_copyFile off from to
|
||||||
=
|
=
|
||||||
-- from sendfile(2) manpage:
|
-- from sendfile(2) manpage:
|
||||||
-- Applications may wish to fall back to read(2)/write(2) in the case
|
-- Applications may wish to fall back to read(2)/write(2) in the case
|
||||||
@ -332,8 +357,7 @@ copyFile from to
|
|||||||
$ \sfd -> do
|
$ \sfd -> do
|
||||||
fileM <- System.Posix.Files.ByteString.fileMode
|
fileM <- System.Posix.Files.ByteString.fileMode
|
||||||
<$> getFdStatus sfd
|
<$> getFdStatus sfd
|
||||||
bracketeer (SPI.openFd dest SPI.WriteOnly (Just fileM)
|
bracketeer (SPI.openFd dest SPI.WriteOnly (Just fileM) off)
|
||||||
SPI.defaultFileFlags { exclusive = True })
|
|
||||||
SPI.closeFd
|
SPI.closeFd
|
||||||
(\fd -> SPI.closeFd fd >> deleteFile to)
|
(\fd -> SPI.closeFd fd >> deleteFile to)
|
||||||
$ \dfd -> sendfileFd dfd sfd EntireFile
|
$ \dfd -> sendfileFd dfd sfd EntireFile
|
||||||
@ -345,8 +369,7 @@ copyFile from to
|
|||||||
$ \sfd -> do
|
$ \sfd -> do
|
||||||
fileM <- System.Posix.Files.ByteString.fileMode
|
fileM <- System.Posix.Files.ByteString.fileMode
|
||||||
<$> getFdStatus sfd
|
<$> getFdStatus sfd
|
||||||
bracketeer (SPI.openFd dest SPI.WriteOnly (Just fileM)
|
bracketeer (SPI.openFd dest SPI.WriteOnly (Just fileM) off)
|
||||||
SPI.defaultFileFlags { exclusive = True })
|
|
||||||
SPI.closeFd
|
SPI.closeFd
|
||||||
(\fd -> SPI.closeFd fd >> deleteFile to)
|
(\fd -> SPI.closeFd fd >> deleteFile to)
|
||||||
$ \dfd -> allocaBytes (fromIntegral bufSize) $ \buf ->
|
$ \dfd -> allocaBytes (fromIntegral bufSize) $ \buf ->
|
||||||
@ -360,6 +383,7 @@ copyFile from to
|
|||||||
if size == 0
|
if size == 0
|
||||||
then return $ fromIntegral totalsize
|
then return $ fromIntegral totalsize
|
||||||
else do rsize <- SPB.fdWriteBuf dfd buf size
|
else do rsize <- SPB.fdWriteBuf dfd buf size
|
||||||
|
-- TODO: switch to IOError?
|
||||||
when (rsize /= size) (throw . CopyFailed $ "wrong size!")
|
when (rsize /= size) (throw . CopyFailed $ "wrong size!")
|
||||||
write' sfd dfd buf (totalsize + fromIntegral size)
|
write' sfd dfd buf (totalsize + fromIntegral size)
|
||||||
|
|
||||||
|
89
test/FileSystem/FileOperations/CopyFileOverwriteSpec.hs
Normal file
89
test/FileSystem/FileOperations/CopyFileOverwriteSpec.hs
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
module FileSystem.FileOperations.CopyFileOverwriteSpec where
|
||||||
|
|
||||||
|
|
||||||
|
import Test.Hspec
|
||||||
|
import System.IO.Error
|
||||||
|
(
|
||||||
|
ioeGetErrorType
|
||||||
|
)
|
||||||
|
import GHC.IO.Exception
|
||||||
|
(
|
||||||
|
IOErrorType(..)
|
||||||
|
)
|
||||||
|
import System.Exit
|
||||||
|
import System.Process
|
||||||
|
import Utils
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
copyFileOverwriteSpec :: Spec
|
||||||
|
copyFileOverwriteSpec =
|
||||||
|
describe "HSFM.FileSystem.FileOperations.copyFileOverwrite" $ do
|
||||||
|
|
||||||
|
-- successes --
|
||||||
|
it "copyFileOverwrite, everything clear" $ do
|
||||||
|
copyFileOverwrite' "test/FileSystem/FileOperations/copyFileOverwriteSpec/inputFile"
|
||||||
|
"test/FileSystem/FileOperations/copyFileOverwriteSpec/outputFile"
|
||||||
|
removeFileIfExists "test/FileSystem/FileOperations/copyFileOverwriteSpec/outputFile"
|
||||||
|
|
||||||
|
it "copyFileOverwrite, output file already exists, all clear" $
|
||||||
|
copyFileOverwrite' "test/FileSystem/FileOperations/copyFileOverwriteSpec/inputFile"
|
||||||
|
"test/FileSystem/FileOperations/copyFileOverwriteSpec/alreadyExists"
|
||||||
|
|
||||||
|
it "copyFileOverwrite, output and input are same file" $
|
||||||
|
copyFileOverwrite' "test/FileSystem/FileOperations/copyFileOverwriteSpec/inputFile"
|
||||||
|
"test/FileSystem/FileOperations/copyFileOverwriteSpec/inputFile"
|
||||||
|
|
||||||
|
it "copyFileOverwrite, and compare" $ do
|
||||||
|
copyFileOverwrite' "test/FileSystem/FileOperations/copyFileOverwriteSpec/inputFile"
|
||||||
|
"test/FileSystem/FileOperations/copyFileOverwriteSpec/outputFile"
|
||||||
|
(system $ "cmp -s " ++ "test/FileSystem/FileOperations/copyFileOverwriteSpec/inputFile" ++ " "
|
||||||
|
++ "test/FileSystem/FileOperations/copyFileOverwriteSpec/outputFile")
|
||||||
|
`shouldReturn` ExitSuccess
|
||||||
|
removeFileIfExists "test/FileSystem/FileOperations/copyFileOverwriteSpec/outputFile"
|
||||||
|
|
||||||
|
-- posix failures --
|
||||||
|
it "copyFileOverwrite, input file does not exist" $
|
||||||
|
copyFileOverwrite' "test/FileSystem/FileOperations/copyFileOverwriteSpec/noSuchFile"
|
||||||
|
"test/FileSystem/FileOperations/copyFileOverwriteSpec/outputFile"
|
||||||
|
`shouldThrow`
|
||||||
|
(\e -> ioeGetErrorType e == NoSuchThing)
|
||||||
|
|
||||||
|
it "copyFileOverwrite, no permission to write to output directory" $
|
||||||
|
copyFileOverwrite' "test/FileSystem/FileOperations/copyFileOverwriteSpec/inputFile"
|
||||||
|
"test/FileSystem/FileOperations/copyFileOverwriteSpec/outputDirNoWrite/outputFile"
|
||||||
|
`shouldThrow`
|
||||||
|
(\e -> ioeGetErrorType e == PermissionDenied)
|
||||||
|
|
||||||
|
it "copyFileOverwrite, cannot open output directory" $
|
||||||
|
copyFileOverwrite' "test/FileSystem/FileOperations/copyFileOverwriteSpec/inputFile"
|
||||||
|
"test/FileSystem/FileOperations/copyFileOverwriteSpec/noPerms/outputFile"
|
||||||
|
`shouldThrow`
|
||||||
|
(\e -> ioeGetErrorType e == PermissionDenied)
|
||||||
|
|
||||||
|
it "copyFileOverwrite, cannot open source directory" $
|
||||||
|
copyFileOverwrite' "test/FileSystem/FileOperations/copyFileOverwriteSpec/noPerms/inputFile"
|
||||||
|
"test/FileSystem/FileOperations/copyFileOverwriteSpec/outputFile"
|
||||||
|
`shouldThrow`
|
||||||
|
(\e -> ioeGetErrorType e == PermissionDenied)
|
||||||
|
|
||||||
|
it "copyFileOverwrite, wrong input type (symlink)" $
|
||||||
|
copyFileOverwrite' "test/FileSystem/FileOperations/copyFileOverwriteSpec/inputFileSymL"
|
||||||
|
"test/FileSystem/FileOperations/copyFileOverwriteSpec/outputFile"
|
||||||
|
`shouldThrow`
|
||||||
|
(\e -> ioeGetErrorType e == InvalidArgument)
|
||||||
|
|
||||||
|
it "copyFileOverwrite, wrong input type (directory)" $
|
||||||
|
copyFileOverwrite' "test/FileSystem/FileOperations/copyFileOverwriteSpec/wrongInput"
|
||||||
|
"test/FileSystem/FileOperations/copyFileOverwriteSpec/outputFile"
|
||||||
|
`shouldThrow`
|
||||||
|
(\e -> ioeGetErrorType e == InappropriateType)
|
||||||
|
|
||||||
|
it "copyFileOverwrite, output file already exists and is a dir" $
|
||||||
|
copyFileOverwrite' "test/FileSystem/FileOperations/copyFileOverwriteSpec/inputFile"
|
||||||
|
"test/FileSystem/FileOperations/copyFileOverwriteSpec/alreadyExistsD"
|
||||||
|
`shouldThrow`
|
||||||
|
(\e -> ioeGetErrorType e == InappropriateType)
|
||||||
|
|
@ -0,0 +1,4 @@
|
|||||||
|
abc
|
||||||
|
def
|
||||||
|
|
||||||
|
dsadasdsa
|
@ -0,0 +1,4 @@
|
|||||||
|
abc
|
||||||
|
def
|
||||||
|
|
||||||
|
dsadasdsa
|
@ -0,0 +1 @@
|
|||||||
|
inputFile
|
22
test/Spec.hs
22
test/Spec.hs
@ -3,6 +3,7 @@
|
|||||||
import Test.Hspec
|
import Test.Hspec
|
||||||
|
|
||||||
import FileSystem.FileOperations.CopyDirRecursiveSpec
|
import FileSystem.FileOperations.CopyDirRecursiveSpec
|
||||||
|
import FileSystem.FileOperations.CopyFileOverwriteSpec
|
||||||
import FileSystem.FileOperations.CopyFileSpec
|
import FileSystem.FileOperations.CopyFileSpec
|
||||||
import FileSystem.FileOperations.CreateDirSpec
|
import FileSystem.FileOperations.CreateDirSpec
|
||||||
import FileSystem.FileOperations.CreateRegularFileSpec
|
import FileSystem.FileOperations.CreateRegularFileSpec
|
||||||
@ -23,15 +24,16 @@ import Utils
|
|||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = hspec $ before_ fixPermissions $ after_ revertPermissions $ do
|
main = hspec $ before_ fixPermissions $ after_ revertPermissions $ do
|
||||||
let tests = [copyFileSpec
|
let tests = [copyFileSpec
|
||||||
, copyDirRecursiveSpec
|
,copyFileOverwriteSpec
|
||||||
, createDirSpec
|
,copyDirRecursiveSpec
|
||||||
, createRegularFileSpec
|
,createDirSpec
|
||||||
, renameFileSpec
|
,createRegularFileSpec
|
||||||
, moveFileSpec
|
,renameFileSpec
|
||||||
, recreateSymlinkSpec
|
,moveFileSpec
|
||||||
, deleteFileSpec
|
,recreateSymlinkSpec
|
||||||
, deleteDirSpec
|
,deleteFileSpec
|
||||||
, deleteDirRecursiveSpec
|
,deleteDirSpec
|
||||||
|
,deleteDirRecursiveSpec
|
||||||
]
|
]
|
||||||
|
|
||||||
-- run all stateful tests twice to catch missing cleanups or state skew
|
-- run all stateful tests twice to catch missing cleanups or state skew
|
||||||
@ -44,6 +46,7 @@ main = hspec $ before_ fixPermissions $ after_ revertPermissions $ do
|
|||||||
|
|
||||||
where
|
where
|
||||||
noWriteDirs = ["test/FileSystem/FileOperations/copyFileSpec/outputDirNoWrite"
|
noWriteDirs = ["test/FileSystem/FileOperations/copyFileSpec/outputDirNoWrite"
|
||||||
|
,"test/FileSystem/FileOperations/copyFileOverwriteSpec/outputDirNoWrite"
|
||||||
,"test/FileSystem/FileOperations/copyDirRecursiveSpec/noWritePerm"
|
,"test/FileSystem/FileOperations/copyDirRecursiveSpec/noWritePerm"
|
||||||
,"test/FileSystem/FileOperations/createDirSpec/noWritePerms"
|
,"test/FileSystem/FileOperations/createDirSpec/noWritePerms"
|
||||||
,"test/FileSystem/FileOperations/createRegularFileSpec/noWritePerms"
|
,"test/FileSystem/FileOperations/createRegularFileSpec/noWritePerms"
|
||||||
@ -52,6 +55,7 @@ main = hspec $ before_ fixPermissions $ after_ revertPermissions $ do
|
|||||||
,"test/FileSystem/FileOperations/recreateSymlinkSpec/noWritePerm"
|
,"test/FileSystem/FileOperations/recreateSymlinkSpec/noWritePerm"
|
||||||
]
|
]
|
||||||
noPermsDirs = ["test/FileSystem/FileOperations/copyFileSpec/noPerms"
|
noPermsDirs = ["test/FileSystem/FileOperations/copyFileSpec/noPerms"
|
||||||
|
,"test/FileSystem/FileOperations/copyFileOverwriteSpec/noPerms"
|
||||||
,"test/FileSystem/FileOperations/copyDirRecursiveSpec/noPerms"
|
,"test/FileSystem/FileOperations/copyDirRecursiveSpec/noPerms"
|
||||||
,"test/FileSystem/FileOperations/createDirSpec/noPerms"
|
,"test/FileSystem/FileOperations/createDirSpec/noPerms"
|
||||||
,"test/FileSystem/FileOperations/createRegularFileSpec/noPerms"
|
,"test/FileSystem/FileOperations/createRegularFileSpec/noPerms"
|
||||||
|
@ -72,6 +72,11 @@ copyFile' inputFileP outputFileP =
|
|||||||
withPwd' inputFileP outputFileP copyFile
|
withPwd' inputFileP outputFileP copyFile
|
||||||
|
|
||||||
|
|
||||||
|
copyFileOverwrite' :: ByteString -> ByteString -> IO ()
|
||||||
|
copyFileOverwrite' inputFileP outputFileP =
|
||||||
|
withPwd' inputFileP outputFileP copyFileOverwrite
|
||||||
|
|
||||||
|
|
||||||
copyDirRecursive' :: ByteString -> ByteString -> IO ()
|
copyDirRecursive' :: ByteString -> ByteString -> IO ()
|
||||||
copyDirRecursive' inputDirP outputDirP =
|
copyDirRecursive' inputDirP outputDirP =
|
||||||
withPwd' inputDirP outputDirP copyDirRecursive
|
withPwd' inputDirP outputDirP copyDirRecursive
|
||||||
|
Loading…
Reference in New Issue
Block a user