LIB: make interaction with FileOperation more safe

We now don't safe an AnchoredFile anymore, but a Path and
construct the AnchoredFile just before the operation is carried out.

This means the copy and move buffers cannot contain stale references
to files that don't exist anymore.
This commit is contained in:
Julian Ospald 2016-04-10 18:52:51 +02:00
parent bd022956f5
commit 48edf7d47b
No known key found for this signature in database
GPG Key ID: 511B62C09D50CD28
4 changed files with 60 additions and 35 deletions

View File

@ -66,6 +66,7 @@ data FmIOException = FileDoesNotExist String
| InvalidFileName | InvalidFileName
| Can'tOpenDirectory String | Can'tOpenDirectory String
| CopyFailed String | CopyFailed String
| MoveFailed String
deriving (Show, Typeable) deriving (Show, Typeable)

View File

@ -70,12 +70,14 @@ import Foreign.Ptr
import HPath import HPath
( (
Path Path
, Abs
, Fn , Fn
) )
import qualified HPath as P 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 Prelude hiding (readFile)
import Network.Sendfile import Network.Sendfile
( (
sendfileFd sendfileFd
@ -126,30 +128,30 @@ 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 a = FCopy (Copy a) data FileOperation = FCopy Copy
| FMove (Move a) | FMove Move
| FDelete [AnchoredFile a] | FDelete [Path Abs]
| FOpen (AnchoredFile a) | FOpen (Path Abs)
| FExecute (AnchoredFile a) [ByteString] | FExecute (Path Abs) [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 a = CP1 [AnchoredFile a] data Copy = CP1 [Path Abs]
| CP2 [AnchoredFile a] | CP2 [Path Abs]
(AnchoredFile a) (Path Abs)
| CC [AnchoredFile a] | CC [Path Abs]
(AnchoredFile a) (Path Abs)
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 a = MP1 [AnchoredFile a] data Move = MP1 [Path Abs]
| MC [AnchoredFile a] | MC [Path Abs]
(AnchoredFile a) (Path Abs)
CopyMode CopyMode
-- |Copy modes. -- |Copy modes.
@ -162,18 +164,40 @@ 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. Returns `Nothing` on success.
runFileOp :: FileOperation a -> IO (Maybe (FileOperation a)) --
runFileOp (FCopy (CC froms to cm)) = mapM_ (\x -> easyCopy cm x to) froms -- Since file operations can be delayed, this is `Path Abs` based, not
>> return Nothing -- `AnchoredFile` based. This makes sure we don't have stale
runFileOp (FCopy fo) = return $ Just $ FCopy fo -- file information.
runFileOp (FMove (MC froms to cm)) = mapM_ (\x -> easyMove cm x to) froms runFileOp :: FileOperation -> IO (Maybe FileOperation)
>> return Nothing runFileOp fo' =
runFileOp (FMove fo) = return $ Just $ FMove fo case fo' of
runFileOp (FDelete fp) = mapM_ easyDelete fp >> return Nothing (FCopy (CC froms to cm)) -> do
runFileOp (FOpen fp) = openFile fp >> return Nothing froms' <- mapM toAfile froms
runFileOp (FExecute fp args) = executeFile fp args >> return Nothing to' <- toAfile to
runFileOp _ = return Nothing when (anyFailed $ file <$> froms')
(throw . CopyFailed $ "File in copy buffer does not exist anymore!")
mapM_ (\x -> easyCopy cm x to') froms'
>> return Nothing
(FCopy fo) -> return $ Just $ FCopy fo
(FMove (MC froms to cm)) -> do
froms' <- mapM toAfile froms
to' <- toAfile to
when (anyFailed $ file <$> froms')
(throw . MoveFailed $ "File in move buffer does not exist anymore!")
mapM_ (\x -> easyMove cm x to') froms'
>> return Nothing
(FMove fo) -> return $ Just $ FMove fo
(FDelete fps) -> do
fps' <- mapM toAfile fps
mapM_ easyDelete fps' >> return Nothing
(FOpen fp) ->
toAfile fp >>= openFile >> return Nothing
(FExecute fp args) ->
toAfile fp >>= flip executeFile args >> return Nothing
_ -> return Nothing
where
toAfile = readFile (\_ -> return undefined)

View File

@ -277,7 +277,7 @@ del _ _ _ = withErrorDialog
-- |Initializes a file move operation. -- |Initializes a file move operation.
moveInit :: [Item] -> MyGUI -> MyView -> IO () moveInit :: [Item] -> MyGUI -> MyView -> IO ()
moveInit items@(_:_) mygui myview = do moveInit items@(_:_) mygui myview = do
writeTVarIO (operationBuffer myview) (FMove . MP1 $ items) writeTVarIO (operationBuffer myview) (FMove . MP1 . map fullPath $ items)
let sbmsg = case items of let sbmsg = case items of
(item:[]) -> "Move buffer: " ++ P.fpToString (fullPathS item) (item:[]) -> "Move buffer: " ++ P.fpToString (fullPathS item)
_ -> "Move buffer: " ++ (show . length $ items) _ -> "Move buffer: " ++ (show . length $ items)
@ -291,7 +291,7 @@ moveInit _ _ _ = withErrorDialog
-- |Supposed to be used with 'withRows'. Initializes a file copy operation. -- |Supposed to be used with 'withRows'. Initializes a file copy operation.
copyInit :: [Item] -> MyGUI -> MyView -> IO () copyInit :: [Item] -> MyGUI -> MyView -> IO ()
copyInit items@(_:_) mygui myview = do copyInit items@(_:_) mygui myview = do
writeTVarIO (operationBuffer myview) (FCopy . CP1 $ items) writeTVarIO (operationBuffer myview) (FCopy . CP1 . map fullPath $ items)
let sbmsg = case items of let sbmsg = case items of
(item:[]) -> "Copy buffer: " ++ P.fpToString (fullPathS item) (item:[]) -> "Copy buffer: " ++ P.fpToString (fullPathS item)
_ -> "Copy buffer: " ++ (show . length $ items) _ -> "Copy buffer: " ++ (show . length $ items)
@ -307,18 +307,18 @@ copyInit _ _ _ = withErrorDialog
operationFinal :: MyGUI -> MyView -> IO () operationFinal :: MyGUI -> MyView -> IO ()
operationFinal _ myview = withErrorDialog $ do operationFinal _ myview = withErrorDialog $ do
op <- readTVarIO (operationBuffer myview) op <- readTVarIO (operationBuffer myview)
cdir <- getCurrentDir myview cdir <- fullPath <$> getCurrentDir myview
case op of case op of
FMove (MP1 s) -> do FMove (MP1 s) -> do
let cmsg = "Really move " ++ imsg s let cmsg = "Really move " ++ imsg s
++ " to \"" ++ P.fpToString (fullPathS cdir) ++ " to \"" ++ P.fpToString (P.fromAbs cdir)
++ "\"?" ++ "\"?"
withConfirmationDialog cmsg . withCopyModeDialog withConfirmationDialog cmsg . withCopyModeDialog
$ \cm -> void $ runFileOp (FMove . MC s cdir $ cm) $ \cm -> void $ runFileOp (FMove . MC s cdir $ cm)
return () return ()
FCopy (CP1 s) -> do FCopy (CP1 s) -> do
let cmsg = "Really copy " ++ imsg s let cmsg = "Really copy " ++ imsg s
++ " to \"" ++ P.fpToString (fullPathS cdir) ++ " to \"" ++ P.fpToString (P.fromAbs cdir)
++ "\"?" ++ "\"?"
withConfirmationDialog cmsg . withCopyModeDialog withConfirmationDialog cmsg . withCopyModeDialog
$ \cm -> void $ runFileOp (FCopy . CC s cdir $ cm) $ \cm -> void $ runFileOp (FCopy . CC s cdir $ cm)
@ -326,7 +326,7 @@ operationFinal _ myview = withErrorDialog $ do
_ -> return () _ -> return ()
where where
imsg s = case s of imsg s = case s of
(item:[]) -> "\"" ++ P.fpToString (fullPathS item) ++ "\"" (item:[]) -> "\"" ++ P.fpToString (P.fromAbs item) ++ "\""
items -> (show . length $ items) ++ " items" items -> (show . length $ items) ++ " items"

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 FileInfo) , operationBuffer :: TVar FileOperation
, inotify :: MVar INotify , inotify :: MVar INotify
} }