LIB: overhaul file operations and improve overall performance

This introduces a lot of changes and uses a more solid
file copy operation.
This commit is contained in:
Julian Ospald 2016-04-10 03:58:20 +02:00
parent 1be8984162
commit 5bcbbcc69c
No known key found for this signature in database
GPG Key ID: 511B62C09D50CD28
7 changed files with 227 additions and 115 deletions

3
.gitmodules vendored
View File

@ -4,3 +4,6 @@
[submodule "3rdparty/hinotify"] [submodule "3rdparty/hinotify"]
path = 3rdparty/hinotify path = 3rdparty/hinotify
url = https://github.com/hasufell/hinotify.git url = https://github.com/hasufell/hinotify.git
[submodule "3rdparty/simple-sendfile"]
path = 3rdparty/simple-sendfile
url = https://github.com/hasufell/simple-sendfile.git

1
3rdparty/simple-sendfile vendored Submodule

@ -0,0 +1 @@
Subproject commit 28f95d506969ffd0c67aabbe48b315b7ae26c604

View File

@ -42,6 +42,7 @@ library
old-locale >= 1, old-locale >= 1,
process, process,
safe, safe,
simple-sendfile,
stm, stm,
time >= 1.4.2, time >= 1.4.2,
unix, unix,
@ -89,6 +90,7 @@ executable hsfm-gtk
old-locale >= 1, old-locale >= 1,
process, process,
safe, safe,
simple-sendfile,
stm, stm,
time >= 1.4.2, time >= 1.4.2,
transformers, transformers,

View File

@ -65,6 +65,7 @@ data FmIOException = FileDoesNotExist String
| InvalidOperation String | InvalidOperation String
| InvalidFileName | InvalidFileName
| Can'tOpenDirectory String | Can'tOpenDirectory String
| CopyFailed String
deriving (Show, Typeable) deriving (Show, Typeable)
@ -186,7 +187,7 @@ throwCantOpenDirectory fp =
-- |Carries out an action, then checks if there is an IOException and -- |Carries out an action, then checks if there is an IOException and
-- a specific errno. If so, then it carries out another action, otherwise -- a specific errno. If so, then it carries out another action, otherwise
-- it rethrows the error. -- it rethrows the error.
catchErrno :: Errno -- ^ errno to catch catchErrno :: [Errno] -- ^ errno to catch
-> IO a -- ^ action to try, which can raise an IOException -> IO a -- ^ action to try, which can raise an IOException
-> IO a -- ^ action to carry out in case of an IOException and -> IO a -- ^ action to carry out in case of an IOException and
-- if errno matches -- if errno matches
@ -194,7 +195,7 @@ catchErrno :: Errno -- ^ errno to catch
catchErrno en a1 a2 = catchErrno en a1 a2 =
catchIOError a1 $ \e -> do catchIOError a1 $ \e -> do
errno <- getErrno errno <- getErrno
if errno == en if errno `elem` en
then a2 then a2
else ioError e else ioError e
@ -203,7 +204,7 @@ catchErrno en a1 a2 =
-- that have the given errno. If errno does not match the exception is rethrown -- that have the given errno. If errno does not match the exception is rethrown
-- as is. -- as is.
rethrowErrnoAs :: Exception e rethrowErrnoAs :: Exception e
=> Errno -- ^ errno to catch => [Errno] -- ^ errno to catch
-> e -- ^ rethrow as if errno matches -> e -- ^ rethrow as if errno matches
-> IO a -- ^ action to try -> IO a -- ^ action to try
-> IO a -> IO a

View File

@ -38,6 +38,7 @@ import Control.Exception
import Control.Monad import Control.Monad
( (
unless unless
, when
, void , void
) )
import Data.ByteString import Data.ByteString
@ -48,9 +49,23 @@ import Data.Foldable
( (
for_ for_
) )
import Data.Word
(
Word8
)
import Foreign.C.Error import Foreign.C.Error
( (
eXDEV eXDEV
, eINVAL
, eNOSYS
)
import Foreign.Marshal.Alloc
(
allocaBytes
)
import Foreign.Ptr
(
Ptr
) )
import HPath import HPath
( (
@ -61,6 +76,11 @@ import qualified HPath as P
import HSFM.FileSystem.Errors import HSFM.FileSystem.Errors
import HSFM.FileSystem.FileType import HSFM.FileSystem.FileType
import HSFM.Utils.IO import HSFM.Utils.IO
import Network.Sendfile
(
sendfileFd
, FileRange(EntireFile)
)
import System.Posix.Directory.ByteString import System.Posix.Directory.ByteString
( (
createDirectory createDirectory
@ -69,9 +89,9 @@ import System.Posix.Directory.ByteString
import System.Posix.Files.ByteString import System.Posix.Files.ByteString
( (
createSymbolicLink createSymbolicLink
, fileMode
, readSymbolicLink , readSymbolicLink
, getSymbolicLinkStatus , fileMode
, getFdStatus
, groupExecuteMode , groupExecuteMode
, groupReadMode , groupReadMode
, groupWriteMode , groupWriteMode
@ -85,16 +105,15 @@ import System.Posix.Files.ByteString
, unionFileModes , unionFileModes
, removeLink , removeLink
) )
import qualified System.Posix.Files.ByteString as PF
import qualified "unix" System.Posix.IO.ByteString as SPI import qualified "unix" System.Posix.IO.ByteString as SPI
import "unix-bytestring" System.Posix.IO.ByteString import qualified "unix-bytestring" System.Posix.IO.ByteString as SPB
(
fdWrite
)
import qualified System.Posix.Process.ByteString as SPP import qualified System.Posix.Process.ByteString as SPP
import System.Posix.Types import System.Posix.Types
( (
FileMode FileMode
, ProcessID , ProcessID
, Fd
) )
@ -107,29 +126,29 @@ import System.Posix.Types
-- |Data type describing an actual file operation that can be -- |Data type describing an actual file operation that can be
-- carried out via `doFile`. Useful to build up a list of operations -- carried out via `doFile`. Useful to build up a list of operations
-- or delay operations. -- or delay operations.
data FileOperation = FCopy Copy data FileOperation a = FCopy (Copy a)
| FMove Move | FMove (Move a)
| FDelete [AnchoredFile FileInfo] | FDelete [AnchoredFile a]
| FOpen (AnchoredFile FileInfo) | FOpen (AnchoredFile a)
| FExecute (AnchoredFile FileInfo) [ByteString] | FExecute (AnchoredFile a) [ByteString]
| None | None
-- |Data type describing partial or complete file copy operation. -- |Data type describing partial or complete file copy operation.
-- CC stands for a complete operation and can be used for `runFileOp`. -- CC stands for a complete operation and can be used for `runFileOp`.
data Copy = CP1 [AnchoredFile FileInfo] data Copy a = CP1 [AnchoredFile a]
| CP2 [AnchoredFile FileInfo] | CP2 [AnchoredFile a]
(AnchoredFile FileInfo) (AnchoredFile a)
| CC [AnchoredFile FileInfo] | CC [AnchoredFile a]
(AnchoredFile FileInfo) (AnchoredFile a)
CopyMode CopyMode
-- |Data type describing partial or complete file move operation. -- |Data type describing partial or complete file move operation.
-- MC stands for a complete operation and can be used for `runFileOp`. -- MC stands for a complete operation and can be used for `runFileOp`.
data Move = MP1 [AnchoredFile FileInfo] data Move a = MP1 [AnchoredFile a]
| MC [AnchoredFile FileInfo] | MC [AnchoredFile a]
(AnchoredFile FileInfo) (AnchoredFile a)
CopyMode CopyMode
@ -144,7 +163,7 @@ data CopyMode = Strict -- ^ fail if the target already exists
-- |Run a given FileOperation. If the FileOperation is partial, it will -- |Run a given FileOperation. If the FileOperation is partial, it will
-- be returned. -- be returned.
runFileOp :: FileOperation -> IO (Maybe FileOperation) runFileOp :: FileOperation a -> IO (Maybe (FileOperation a))
runFileOp (FCopy (CC froms to cm)) = mapM_ (\x -> easyCopy cm x to) froms runFileOp (FCopy (CC froms to cm)) = mapM_ (\x -> easyCopy cm x to) froms
>> return Nothing >> return Nothing
runFileOp (FCopy fo) = return $ Just $ FCopy fo runFileOp (FCopy fo) = return $ Just $ FCopy fo
@ -166,8 +185,8 @@ runFileOp _ = return Nothing
-- |Copies a directory to the given destination with the specified -- |Copies a directory to the given destination with the specified
-- `DirCopyMode`. Excludes symlinks. -- `DirCopyMode`. Excludes symlinks.
copyDir :: CopyMode copyDir :: CopyMode
-> AnchoredFile FileInfo -- ^ source dir -> AnchoredFile a -- ^ source dir
-> AnchoredFile FileInfo -- ^ destination dir -> AnchoredFile a -- ^ destination dir
-> Path Fn -- ^ destination dir name -> Path Fn -- ^ destination dir name
-> IO () -> IO ()
copyDir _ AFileInvFN _ _ = throw InvalidFileName copyDir _ AFileInvFN _ _ = throw InvalidFileName
@ -179,33 +198,42 @@ copyDir (Rename fn)
_ _
= copyDir Strict from to fn = copyDir Strict from to fn
-- this branch must never get `Rename` as CopyMode -- this branch must never get `Rename` as CopyMode
copyDir cm from@(_ :/ Dir _ FileInfo{ fileMode = fmode }) copyDir cm from@(_ :/ Dir {})
to@(_ :/ Dir {}) to@(_ :/ Dir {})
fn fn
= do = do
let fromp = fullPath from let fromp = fullPath from
top = fullPath to top = fullPath to
destdirp = top P.</> fn destdirp = top P.</> fn
-- for performance, sanity checks are only done for the top dir
throwDestinationInSource fromp destdirp throwDestinationInSource fromp destdirp
throwSameFile fromp destdirp throwSameFile fromp destdirp
throwCantOpenDirectory fromp throwCantOpenDirectory fromp
throwCantOpenDirectory top throwCantOpenDirectory top
go cm from to fn
createDestdir destdirp fmode where
destdir <- HSFM.FileSystem.FileType.readFileWithFileInfo destdirp go :: CopyMode -> AnchoredFile a -> AnchoredFile a -> Path Fn -> IO ()
go cm' from'@(_ :/ Dir {})
contents <- readDirectoryContentsWithFileInfo' (fullPath from) to'@(_ :/ Dir {})
fn' = do
fmode' <- PF.fileMode <$> PF.getSymbolicLinkStatus (fullPathS from')
createDestdir (fullPath to' P.</> fn') fmode'
destdir <- readFileUnsafe (\_ -> return undefined)
(fullPath to' P.</> fn')
contents <- readDirectoryContentsUnsafe
getDirsFiles (\_ -> return undefined) (fullPath from')
for_ contents $ \f -> for_ contents $ \f ->
case f of case f of
(_ :/ SymLink {}) -> recreateSymlink cm f destdir (name . file $ f) (_ :/ SymLink {}) -> recreateSymlink cm' f destdir (name . file $ f)
(_ :/ Dir {}) -> copyDir cm f destdir (name . file $ f) (_ :/ Dir {}) -> go cm' f destdir (name . file $ f)
(_ :/ RegFile {}) -> copyFile Replace f destdir (name . file $ f) (_ :/ RegFile {}) -> unsafeCopyFile Replace f destdir
(name . file $ f)
_ -> return () _ -> return ()
where where
createDestdir destdir fmode' = createDestdir destdir fmode' =
let destdir' = P.toFilePath destdir let destdir' = P.toFilePath destdir
in case cm of in case cm' of
Merge -> Merge ->
unlessM (doesDirectoryExist destdir) unlessM (doesDirectoryExist destdir)
(createDirectory destdir' fmode') (createDirectory destdir' fmode')
@ -215,16 +243,18 @@ copyDir cm from@(_ :/ Dir _ FileInfo{ fileMode = fmode })
Replace -> do Replace -> do
whenM (doesDirectoryExist destdir) whenM (doesDirectoryExist destdir)
(deleteDirRecursive =<< (deleteDirRecursive =<<
HSFM.FileSystem.FileType.readFileWithFileInfo destdir) readFileUnsafe
(\_ -> return undefined) destdir)
createDirectory destdir' fmode' createDirectory destdir' fmode'
_ -> throw $ InvalidOperation "Internal error, wrong CopyMode!" _ -> throw $ InvalidOperation "Internal error, wrong CopyMode!"
go _ _ _ _ = throw $ InvalidOperation "wrong input type"
copyDir _ _ _ _ = throw $ InvalidOperation "wrong input type" copyDir _ _ _ _ = throw $ InvalidOperation "wrong input type"
-- |Recreate a symlink. -- |Recreate a symlink.
recreateSymlink :: CopyMode recreateSymlink :: CopyMode
-> AnchoredFile FileInfo -- ^ the old symlink file -> AnchoredFile a -- ^ the old symlink file
-> AnchoredFile FileInfo -- ^ destination dir of the -> AnchoredFile a -- ^ destination dir of the
-- new symlink file -- new symlink file
-> Path Fn -- ^ destination file name -> Path Fn -- ^ destination file name
-> IO () -> IO ()
@ -245,7 +275,7 @@ recreateSymlink cm symf@(_ :/ SymLink {}) symdest@(_ :/ Dir {}) fn
createSymbolicLink sympoint (P.fromAbs symname) createSymbolicLink sympoint (P.fromAbs symname)
where where
delOld symname = do delOld symname = do
f <- HSFM.FileSystem.FileType.readFileWithFileInfo symname f <- readFileUnsafe (\_ -> return undefined) symname
unless (failed . file $ f) unless (failed . file $ f)
(easyDelete f) (easyDelete f)
recreateSymlink _ _ _ _ = throw $ InvalidOperation "wrong input type" recreateSymlink _ _ _ _ = throw $ InvalidOperation "wrong input type"
@ -255,8 +285,8 @@ recreateSymlink _ _ _ _ = throw $ InvalidOperation "wrong input type"
-- |Copies the given regular file to the given dir with the given filename. -- |Copies the given regular file to the given dir with the given filename.
-- Excludes symlinks. -- Excludes symlinks.
copyFile :: CopyMode copyFile :: CopyMode
-> AnchoredFile FileInfo -- ^ source file -> AnchoredFile a -- ^ source file
-> AnchoredFile FileInfo -- ^ destination dir -> AnchoredFile a -- ^ destination dir
-> Path Fn -- ^ destination file name -> Path Fn -- ^ destination file name
-> IO () -> IO ()
copyFile _ AFileInvFN _ _ = throw InvalidFileName copyFile _ AFileInvFN _ _ = throw InvalidFileName
@ -266,29 +296,79 @@ copyFile (Rename pn) from@(_ :/ RegFile {}) to@(_ :/ Dir {}) _
= copyFile Strict from to pn = copyFile Strict from to pn
copyFile cm from@(_ :/ RegFile {}) to@(_ :/ Dir {}) fn copyFile cm from@(_ :/ RegFile {}) to@(_ :/ Dir {}) fn
= do = do
let from' = fullPath from let to' = fullPath to P.</> fn
to' = fullPath to P.</> fn
throwCantOpenDirectory $ fullPath to throwCantOpenDirectory $ fullPath to
throwCantOpenDirectory . P.dirname . fullPath $ from
throwSameFile (fullPath from) to'
unsafeCopyFile cm from to fn
copyFile _ _ _ _ = throw $ InvalidOperation "wrong input type"
-- |Unsafe version of `copyFile` without initial sanity checks. Thise
-- holds the actual copy logic though and is called by `copyFile` in the end.
unsafeCopyFile :: CopyMode
-> AnchoredFile a -- ^ source file
-> AnchoredFile a -- ^ destination dir
-> Path Fn -- ^ destination file name
-> IO ()
unsafeCopyFile _ AFileInvFN _ _ = throw InvalidFileName
unsafeCopyFile _ _ AFileInvFN _ = throw InvalidFileName
unsafeCopyFile _ _ _ InvFN = throw InvalidFileName
unsafeCopyFile (Rename pn) from@(_ :/ RegFile {}) to@(_ :/ Dir {}) _
= copyFile Strict from to pn
unsafeCopyFile cm from@(_ :/ RegFile {}) to@(_ :/ Dir {}) fn
= do
let to' = fullPath to P.</> fn
case cm of case cm of
Strict -> throwFileDoesExist to' Strict -> throwFileDoesExist to'
_ -> return () _ -> return ()
throwCantOpenDirectory . P.dirname . fullPath $ from catchErrno [eINVAL, eNOSYS]
throwCantOpenDirectory . fullPath $ to (sendFileCopy (fullPathS from) (P.fromAbs to'))
fromFstatus <- getSymbolicLinkStatus (P.fromAbs from') (void $ fallbackCopy (fullPathS from) (P.fromAbs to'))
fromContent <- readFileContents from where
bracket (SPI.createFile (P.fromAbs to') -- this is low-level stuff utilizing sendfile(2) for speed
$ System.Posix.Files.ByteString.fileMode fromFstatus) -- TODO: preserve permissions
sendFileCopy source dest =
bracket (SPI.openFd source SPI.ReadOnly Nothing SPI.defaultFileFlags)
SPI.closeFd SPI.closeFd
(\fd -> void $ fdWrite fd fromContent) $ \sfd -> do
copyFile _ _ _ _ = throw $ InvalidOperation "wrong input type" fileM <- System.Posix.Files.ByteString.fileMode
<$> getFdStatus sfd
bracket (SPI.openFd dest SPI.WriteOnly (Just fileM)
SPI.defaultFileFlags)
SPI.closeFd
$ \dfd -> sendfileFd dfd sfd EntireFile (return ())
-- low-level copy operation utilizing read(2)/write(2)
-- in case `sendFileCopy` fails/is unsupported
fallbackCopy source dest =
bracket (SPI.openFd source SPI.ReadOnly Nothing SPI.defaultFileFlags)
SPI.closeFd
$ \sfd -> do
fileM <- System.Posix.Files.ByteString.fileMode
<$> getFdStatus sfd
bracket (SPI.openFd dest SPI.WriteOnly (Just fileM)
SPI.defaultFileFlags)
SPI.closeFd
$ \dfd -> allocaBytes 8192 $ \buf ->
write' sfd dfd buf 0
where
write' :: Fd -> Fd -> Ptr Word8 -> Int -> IO Int
write' sfd dfd buf totalsize = do
size <- SPB.fdReadBuf sfd buf 8192
if (size == 0)
then return $ fromIntegral totalsize
else do rsize <- SPB.fdWriteBuf dfd buf size
when (rsize /= size) (throw . CopyFailed $ "wrong size!")
write' sfd dfd buf (totalsize + fromIntegral size)
unsafeCopyFile _ _ _ _ = throw $ InvalidOperation "wrong input type"
-- |Copies a regular file, directory or symlink. In case of a symlink, -- |Copies a regular file, directory or symlink. In case of a symlink,
-- it is just recreated, even if it points to a directory. -- it is just recreated, even if it points to a directory.
easyCopy :: CopyMode easyCopy :: CopyMode
-> AnchoredFile FileInfo -> AnchoredFile a
-> AnchoredFile FileInfo -> AnchoredFile a
-> IO () -> IO ()
easyCopy cm from@(_ :/ SymLink{}) easyCopy cm from@(_ :/ SymLink{})
to@(_ :/ Dir{}) to@(_ :/ Dir{})
@ -311,7 +391,7 @@ easyCopy _ _ _ = throw $ InvalidOperation "wrong input type"
-- |Deletes a symlink, which can either point to a file or directory. -- |Deletes a symlink, which can either point to a file or directory.
deleteSymlink :: AnchoredFile FileInfo -> IO () deleteSymlink :: AnchoredFile a -> IO ()
deleteSymlink AFileInvFN = throw InvalidFileName deleteSymlink AFileInvFN = throw InvalidFileName
deleteSymlink f@(_ :/ SymLink {}) deleteSymlink f@(_ :/ SymLink {})
= removeLink (P.toFilePath . fullPath $ f) = removeLink (P.toFilePath . fullPath $ f)
@ -319,7 +399,7 @@ deleteSymlink _ = throw $ InvalidOperation "wrong input type"
-- |Deletes the given regular file, never symlinks. -- |Deletes the given regular file, never symlinks.
deleteFile :: AnchoredFile FileInfo -> IO () deleteFile :: AnchoredFile a -> IO ()
deleteFile AFileInvFN = throw InvalidFileName deleteFile AFileInvFN = throw InvalidFileName
deleteFile f@(_ :/ RegFile {}) deleteFile f@(_ :/ RegFile {})
= removeLink (P.toFilePath . fullPath $ f) = removeLink (P.toFilePath . fullPath $ f)
@ -327,7 +407,7 @@ deleteFile _ = throw $ InvalidOperation "wrong input type"
-- |Deletes the given directory, never symlinks. -- |Deletes the given directory, never symlinks.
deleteDir :: AnchoredFile FileInfo -> IO () deleteDir :: AnchoredFile a -> IO ()
deleteDir AFileInvFN = throw InvalidFileName deleteDir AFileInvFN = throw InvalidFileName
deleteDir f@(_ :/ Dir {}) deleteDir f@(_ :/ Dir {})
= removeDirectory (P.toFilePath . fullPath $ f) = removeDirectory (P.toFilePath . fullPath $ f)
@ -335,28 +415,35 @@ deleteDir _ = throw $ InvalidOperation "wrong input type"
-- |Deletes the given directory recursively. -- |Deletes the given directory recursively.
deleteDirRecursive :: AnchoredFile FileInfo -> IO () deleteDirRecursive :: AnchoredFile a -> IO ()
deleteDirRecursive AFileInvFN = throw InvalidFileName deleteDirRecursive AFileInvFN = throw InvalidFileName
deleteDirRecursive f@(_ :/ Dir {}) = do deleteDirRecursive f'@(_ :/ Dir {}) = do
let fp = fullPath f let fp = fullPath f'
throwCantOpenDirectory fp throwCantOpenDirectory fp
files <- readDirectoryContentsWithFileInfo' fp go f'
where
go :: AnchoredFile a -> IO ()
go f@(_ :/ Dir {}) = do
let fp = fullPath f
files <- readDirectoryContentsUnsafe getDirsFiles
(\_ -> return undefined) fp
for_ files $ \file -> for_ files $ \file ->
case file of case file of
(_ :/ SymLink {}) -> deleteSymlink file (_ :/ SymLink {}) -> deleteSymlink file
(_ :/ Dir {}) -> deleteDirRecursive file (_ :/ Dir {}) -> go file
(_ :/ RegFile {}) -> removeLink (P.toFilePath . fullPath $ file) (_ :/ RegFile {}) -> removeLink (P.toFilePath . fullPath $ file)
_ -> throw $ FileDoesExist _ -> throw $ FileDoesExist
(P.fpToString . P.toFilePath . fullPath (P.fpToString . P.toFilePath . fullPath
$ file) $ file)
removeDirectory . P.toFilePath $ fp removeDirectory . P.toFilePath $ fp
go _ = throw $ InvalidOperation "wrong input type"
deleteDirRecursive _ = throw $ InvalidOperation "wrong input type" deleteDirRecursive _ = throw $ InvalidOperation "wrong input type"
-- |Deletes a file, directory or symlink, whatever it may be. -- |Deletes a file, directory or symlink, whatever it may be.
-- In case of directory, performs recursive deletion. In case of -- In case of directory, performs recursive deletion. In case of
-- a symlink, the symlink file is deleted. -- a symlink, the symlink file is deleted.
easyDelete :: AnchoredFile FileInfo -> IO () easyDelete :: AnchoredFile a -> IO ()
easyDelete f@(_ :/ SymLink {}) = deleteSymlink f easyDelete f@(_ :/ SymLink {}) = deleteSymlink f
easyDelete f@(_ :/ RegFile {}) easyDelete f@(_ :/ RegFile {})
= deleteFile f = deleteFile f
@ -382,7 +469,7 @@ openFile f =
-- |Executes a program with the given arguments. -- |Executes a program with the given arguments.
executeFile :: AnchoredFile FileInfo -- ^ program executeFile :: AnchoredFile a -- ^ program
-> [ByteString] -- ^ arguments -> [ByteString] -- ^ arguments
-> IO ProcessID -> IO ProcessID
executeFile AFileInvFN _ = throw InvalidFileName executeFile AFileInvFN _ = throw InvalidFileName
@ -431,7 +518,7 @@ createDir _ _ = throw $ InvalidOperation "wrong input type"
-- |Rename a given file with the provided filename. -- |Rename a given file with the provided filename.
renameFile :: AnchoredFile FileInfo -> Path Fn -> IO () renameFile :: AnchoredFile a -> Path Fn -> IO ()
renameFile AFileInvFN _ = throw InvalidFileName renameFile AFileInvFN _ = throw InvalidFileName
renameFile _ InvFN = throw InvalidFileName renameFile _ InvFN = throw InvalidFileName
renameFile af (ValFN fn) = do renameFile af (ValFN fn) = do
@ -445,8 +532,8 @@ renameFile _ _ = throw $ InvalidOperation "wrong input type"
-- |Move a given file to the given target directory. -- |Move a given file to the given target directory.
moveFile :: CopyMode moveFile :: CopyMode
-> AnchoredFile FileInfo -- ^ file to move -> AnchoredFile a -- ^ file to move
-> AnchoredFile FileInfo -- ^ base target directory -> AnchoredFile a -- ^ base target directory
-> Path Fn -- ^ target file name -> Path Fn -- ^ target file name
-> IO () -> IO ()
moveFile _ AFileInvFN _ _ = throw InvalidFileName moveFile _ AFileInvFN _ _ = throw InvalidFileName
@ -464,20 +551,20 @@ moveFile cm from to@(_ :/ Dir {}) fn = do
Replace -> delOld to' Replace -> delOld to'
Rename _ -> throw $ InvalidOperation "Internal error! Wrong CopyMode!" Rename _ -> throw $ InvalidOperation "Internal error! Wrong CopyMode!"
throwSameFile from' to' throwSameFile from' to'
catchErrno eXDEV (rename froms' tos') $ do catchErrno [eXDEV] (rename froms' tos') $ do
easyCopy Strict from to easyCopy Strict from to
easyDelete from easyDelete from
where where
delOld fp = do delOld fp = do
to' <- HSFM.FileSystem.FileType.readFileWithFileInfo fp to' <- readFileUnsafe (\_ -> return undefined) fp
unless (failed . file $ to') (easyDelete to') unless (failed . file $ to') (easyDelete to')
moveFile _ _ _ _ = throw $ InvalidOperation "wrong input type" moveFile _ _ _ _ = throw $ InvalidOperation "wrong input type"
-- |Like `moveFile` except it uses the filename of the source as target. -- |Like `moveFile` except it uses the filename of the source as target.
easyMove :: CopyMode easyMove :: CopyMode
-> AnchoredFile FileInfo -- ^ file to move -> AnchoredFile a -- ^ file to move
-> AnchoredFile FileInfo -- ^ base target directory -> AnchoredFile a -- ^ base target directory
-> IO () -> IO ()
easyMove cm from to = moveFile cm from to (name . file $ from) easyMove cm from to = moveFile cm from to (name . file $ from)

View File

@ -392,15 +392,22 @@ readFile :: (Path Abs -> IO a) -- ^ function that fills the free
-> Path Abs -- ^ Path to read -> Path Abs -- ^ Path to read
-> IO (AnchoredFile a) -> IO (AnchoredFile a)
readFile ff p = do readFile ff p = do
cdp <- P.canonicalizePath (P.dirname p)
readFileUnsafe ff (cdp P.</> P.basename p)
readFileUnsafe :: (Path Abs -> IO a)
-> Path Abs
-> IO (AnchoredFile a)
readFileUnsafe ff p = do
let fn = P.basename p let fn = P.basename p
bd = P.dirname p bd = P.dirname p
p' = P.toFilePath p p' = P.toFilePath p
bd' <- P.canonicalizePath bd handleDT bd fn $ do
handleDT bd' fn $ do
fs <- PF.getSymbolicLinkStatus p' fs <- PF.getSymbolicLinkStatus p'
fv <- ff p fv <- ff p
file <- constructFile fs fv bd' fn file <- constructFile fs fv bd fn
return (bd' :/ file) return (bd :/ file)
where where
constructFile fs fv bd' fn' constructFile fs fv bd' fn'
| PF.isSymbolicLink fs = do | PF.isSymbolicLink fs = do
@ -442,6 +449,16 @@ readDirectoryContents getfiles ff p = do
return $ removeNonexistent fcs return $ removeNonexistent fcs
readDirectoryContentsUnsafe :: (Path Abs -> IO [Path Fn])
-> (Path Abs -> IO a)
-> Path Abs
-> IO [AnchoredFile a]
readDirectoryContentsUnsafe getfiles ff p = do
files <- getfiles p
fcs <- mapM (\x -> readFileUnsafe ff $ p P.</> x) files
return $ removeNonexistent fcs
-- |Build a list of AnchoredFile, given the path to a directory, filling -- |Build a list of AnchoredFile, given the path to a directory, filling
-- the free variables via `getFileInfo`. This includes the "." and ".." -- the free variables via `getFileInfo`. This includes the "." and ".."
-- directories. -- directories.
@ -544,6 +561,7 @@ comparingConstr t t' = compare (name t) (name t')
-- |Reads a file and returns the content as a ByteString. -- |Reads a file and returns the content as a ByteString.
-- Follows symbolic links. -- Follows symbolic links.
-- TODO: maybe make this lazy?! The strict form will likely blow up memory
readFileContents :: AnchoredFile a -> IO ByteString readFileContents :: AnchoredFile a -> IO ByteString
readFileContents af@(_ :/ RegFile{}) = readFileContents af@(_ :/ RegFile{}) =
bracket (PIO.openFd f PIO.ReadOnly Nothing PIO.defaultFileFlags) bracket (PIO.openFd f PIO.ReadOnly Nothing PIO.defaultFileFlags)
@ -612,7 +630,7 @@ getDirsFiles' :: (Path Fn -> [Path Fn] -> [Path Fn]) -- ^ filter function
-> Path Abs -- ^ dir to read -> Path Abs -- ^ dir to read
-> IO [Path Fn] -> IO [Path Fn]
getDirsFiles' filterf fp = getDirsFiles' filterf fp =
rethrowErrnoAs eACCES (Can'tOpenDirectory . P.fpToString . P.fromAbs $ fp) rethrowErrnoAs [eACCES] (Can'tOpenDirectory . P.fpToString . P.fromAbs $ fp)
$ bracket (PFD.openDirStream . P.toFilePath $ fp) $ bracket (PFD.openDirStream . P.toFilePath $ fp)
PFD.closeDirStream PFD.closeDirStream
$ \dirstream -> $ \dirstream ->

View File

@ -101,7 +101,7 @@ data MyView = MkMyView {
, rawModel :: TVar (ListStore Item) , rawModel :: TVar (ListStore Item)
, sortedModel :: TVar (TypedTreeModelSort Item) , sortedModel :: TVar (TypedTreeModelSort Item)
, filteredModel :: TVar (TypedTreeModelFilter Item) , filteredModel :: TVar (TypedTreeModelFilter Item)
, operationBuffer :: TVar FileOperation , operationBuffer :: TVar (FileOperation FileInfo)
, inotify :: MVar INotify , inotify :: MVar INotify
} }