2015-12-17 03:42:22 +00:00
|
|
|
{-# OPTIONS_HADDOCK ignore-exports #-}
|
|
|
|
|
2015-12-17 15:25:37 +00:00
|
|
|
module IO.File where
|
2015-12-17 03:42:22 +00:00
|
|
|
|
|
|
|
|
|
|
|
import Control.Exception
|
|
|
|
(
|
|
|
|
throw
|
|
|
|
)
|
|
|
|
import Control.Monad
|
|
|
|
(
|
|
|
|
unless
|
|
|
|
, void
|
|
|
|
)
|
2015-12-17 22:08:02 +00:00
|
|
|
import Data.DirTree
|
|
|
|
import Data.DirTree.Zipper
|
2015-12-18 03:22:13 +00:00
|
|
|
import Data.Foldable
|
|
|
|
(
|
|
|
|
for_
|
|
|
|
)
|
2015-12-17 03:42:22 +00:00
|
|
|
import IO.Error
|
2015-12-18 14:28:56 +00:00
|
|
|
import IO.Utils
|
2015-12-17 03:42:22 +00:00
|
|
|
import System.Directory
|
|
|
|
(
|
2015-12-17 22:08:02 +00:00
|
|
|
canonicalizePath
|
2015-12-18 14:28:56 +00:00
|
|
|
, createDirectory
|
2015-12-18 03:22:13 +00:00
|
|
|
, createDirectoryIfMissing
|
2015-12-17 22:08:02 +00:00
|
|
|
, doesDirectoryExist
|
2015-12-17 15:25:37 +00:00
|
|
|
, doesFileExist
|
2015-12-17 03:42:22 +00:00
|
|
|
, executable
|
2015-12-17 15:25:37 +00:00
|
|
|
, removeDirectory
|
2015-12-18 14:28:56 +00:00
|
|
|
, removeDirectoryRecursive
|
2015-12-17 15:25:37 +00:00
|
|
|
, removeFile
|
2015-12-17 03:42:22 +00:00
|
|
|
)
|
2015-12-17 16:46:55 +00:00
|
|
|
import System.FilePath
|
2015-12-17 03:42:22 +00:00
|
|
|
(
|
2015-12-17 22:08:02 +00:00
|
|
|
equalFilePath
|
|
|
|
, isAbsolute
|
|
|
|
, takeFileName
|
|
|
|
, takeDirectory
|
|
|
|
, (</>)
|
2015-12-17 03:42:22 +00:00
|
|
|
)
|
2015-12-18 03:22:13 +00:00
|
|
|
import System.Posix.Files
|
|
|
|
(
|
|
|
|
createSymbolicLink
|
|
|
|
, readSymbolicLink
|
|
|
|
)
|
2015-12-17 03:42:22 +00:00
|
|
|
import System.Process
|
|
|
|
(
|
|
|
|
spawnProcess
|
|
|
|
, ProcessHandle
|
|
|
|
)
|
|
|
|
|
2015-12-17 22:08:02 +00:00
|
|
|
import qualified System.Directory as SD
|
2015-12-17 03:42:22 +00:00
|
|
|
|
2015-12-17 22:08:02 +00:00
|
|
|
|
|
|
|
-- TODO: modify the DTZipper directly after file operations!?
|
2015-12-17 22:11:18 +00:00
|
|
|
-- TODO: file operations should be threaded and not block the UI
|
2015-12-17 22:08:02 +00:00
|
|
|
|
|
|
|
|
|
|
|
-- |Data type describing an actual file operation that can be
|
|
|
|
-- carried out via `doFile`. Useful to build up a list of operations
|
|
|
|
-- or delay operations.
|
2015-12-18 03:22:13 +00:00
|
|
|
data FileOperation = FCopy DTInfoZipper DTInfoZipper
|
2015-12-18 03:24:47 +00:00
|
|
|
| FMove FilePath FilePath
|
|
|
|
| FDelete DTInfoZipper
|
|
|
|
| FOpen DTInfoZipper
|
|
|
|
| FExecute DTInfoZipper [String]
|
|
|
|
| None
|
|
|
|
|
|
|
|
|
2015-12-18 14:28:56 +00:00
|
|
|
data DirCopyMode = Strict
|
|
|
|
| Merge
|
|
|
|
| Replace
|
2015-12-17 22:08:02 +00:00
|
|
|
|
|
|
|
|
2015-12-18 03:22:13 +00:00
|
|
|
runFileOp :: FileOperation -> IO ()
|
|
|
|
runFileOp (FCopy from@(File {}, _) to) = copyFileToDir from to
|
2015-12-18 14:28:56 +00:00
|
|
|
runFileOp (FCopy from@(Dir {}, _) to) = copyDir Merge from to
|
2015-12-17 22:08:02 +00:00
|
|
|
runFileOp (FDelete fp) = easyDelete fp
|
|
|
|
runFileOp (FOpen fp) = void $ openFile fp
|
|
|
|
runFileOp (FExecute fp args) = void $ executeFile fp args
|
|
|
|
runFileOp _ = return ()
|
|
|
|
|
|
|
|
|
2015-12-18 14:28:56 +00:00
|
|
|
|
2015-12-18 13:21:57 +00:00
|
|
|
-- TODO: allow renaming
|
2015-12-18 14:28:56 +00:00
|
|
|
-- |Copies a directory to the given destination. If the target directory
|
|
|
|
-- already exists, performs a semi-defined merge, overwriting already
|
|
|
|
-- existing files.
|
|
|
|
copyDir :: DirCopyMode
|
|
|
|
-> DTInfoZipper -- ^ source dir
|
2015-12-18 03:22:13 +00:00
|
|
|
-> DTInfoZipper -- ^ destination dir
|
|
|
|
-> IO ()
|
2015-12-18 14:28:56 +00:00
|
|
|
copyDir cm from@(Dir fn _ _, _) to@(Dir {}, _) = do
|
2015-12-18 03:22:13 +00:00
|
|
|
let fromp = getFullPath from
|
|
|
|
top = getFullPath to
|
|
|
|
destdir = getFullPath to </> fn
|
|
|
|
|
|
|
|
dirSanityThrow fromp
|
|
|
|
dirSanityThrow top
|
|
|
|
throwDestinationInSource fromp top
|
|
|
|
|
2015-12-18 14:42:24 +00:00
|
|
|
createDestdir destdir
|
2015-12-18 14:28:56 +00:00
|
|
|
|
2015-12-18 13:21:57 +00:00
|
|
|
newDest <- zipLazy mkDirInfo mkFileInfo destdir
|
2015-12-18 03:22:13 +00:00
|
|
|
|
2015-12-18 13:21:57 +00:00
|
|
|
for_ (goAllDown from) $ \f ->
|
|
|
|
-- TODO: maybe do this strict?
|
2015-12-18 03:22:13 +00:00
|
|
|
case f of
|
|
|
|
-- recreate symlink
|
2015-12-18 14:42:24 +00:00
|
|
|
sz@(Dir { name = n, dir = (DirInfo { sym = True }) }, _) ->
|
|
|
|
recreateSymlink newDest n sz
|
2015-12-18 03:22:13 +00:00
|
|
|
sz@(Dir {}, _) ->
|
2015-12-18 14:28:56 +00:00
|
|
|
copyDir cm sz newDest
|
2015-12-18 03:22:13 +00:00
|
|
|
sz@(File {}, _) ->
|
|
|
|
copyFileToDir sz newDest
|
2015-12-18 14:42:24 +00:00
|
|
|
where
|
|
|
|
createDestdir destdir =
|
|
|
|
case cm of
|
|
|
|
Merge ->
|
|
|
|
createDirectoryIfMissing False destdir
|
|
|
|
Strict -> do
|
|
|
|
throwDirDoesExist destdir
|
|
|
|
createDirectory destdir
|
|
|
|
Replace -> do
|
|
|
|
whenM (doesDirectoryExist destdir) (removeDirectoryRecursive destdir)
|
|
|
|
createDirectory destdir
|
|
|
|
recreateSymlink newDest n sz = do
|
|
|
|
let sympoint = getFullPath newDest </> n
|
|
|
|
|
|
|
|
case cm of
|
|
|
|
Merge ->
|
|
|
|
-- delete old file/dir to be able to create symlink
|
|
|
|
for_ (goDown n newDest) $ \odtz ->
|
|
|
|
easyDelete odtz
|
|
|
|
_ -> return ()
|
|
|
|
|
|
|
|
symname <- readSymbolicLink (getFullPath sz)
|
|
|
|
createSymbolicLink symname sympoint
|
2015-12-18 14:28:56 +00:00
|
|
|
|
|
|
|
copyDir _ from@(File _ _, _) _ = throw $ NotADir (getFullPath from)
|
|
|
|
copyDir _ _ to@(File _ _, _) = throw $ NotADir (getFullPath to)
|
2015-12-18 03:22:13 +00:00
|
|
|
|
|
|
|
|
2015-12-17 22:08:02 +00:00
|
|
|
-- |Copies the given file.
|
|
|
|
--
|
|
|
|
-- This will throw an exception if any of the filepaths are not absolute
|
|
|
|
-- and an exception if the source file does not exist.
|
|
|
|
--
|
|
|
|
-- If the destination file already exists, it will be replaced.
|
|
|
|
copyFile :: DTZipper a b -- ^ source file
|
|
|
|
-> FilePath -- ^ destination file
|
|
|
|
-> IO ()
|
|
|
|
copyFile from@(File name _, _) to = do
|
|
|
|
let fp = getFullPath from
|
|
|
|
fileSanityThrow fp
|
|
|
|
throwNotAbsolute to
|
|
|
|
throwDirDoesExist to
|
|
|
|
toC <- canonicalizePath (takeDirectory to)
|
|
|
|
let to' = toC </> takeFileName to
|
|
|
|
throwSameFile fp to'
|
|
|
|
SD.copyFile fp to'
|
2015-12-18 03:22:13 +00:00
|
|
|
copyFile from _ = throw $ NotAFile (getFullPath from)
|
2015-12-17 22:08:02 +00:00
|
|
|
|
|
|
|
|
|
|
|
-- |Copies the given file to the given dir with the same filename.
|
|
|
|
--
|
|
|
|
-- This is just a convenience wrapper around `copyFile`.
|
|
|
|
copyFileToDir :: DTZipper a b -- ^ source file
|
|
|
|
-> DTZipper a b -- ^ destination
|
|
|
|
-> IO ()
|
|
|
|
copyFileToDir from@(File name _, _) to@(Dir {}, _) = do
|
|
|
|
let dp = getFullPath to
|
|
|
|
dirSanityThrow dp
|
|
|
|
copyFile from (dp </> name)
|
|
|
|
copyFileToDir from (Dir {}, _) = throw $ NotAFile (getFullPath from)
|
|
|
|
copyFileToDir _ to = throw $ NotADir (getFullPath to)
|
|
|
|
|
|
|
|
|
|
|
|
-- |Copies the given file, regardless of whether the destination is
|
|
|
|
-- a file or a directory. This is a wrapper around `copyFile` and
|
|
|
|
-- `copyFileToDir`.
|
|
|
|
easyCopyFile :: DTZipper a b -> Either FilePath (DTZipper a b) -> IO ()
|
|
|
|
easyCopyFile from (Left to) = copyFile from to
|
|
|
|
easyCopyFile from (Right to) = copyFileToDir from to
|
2015-12-17 15:25:37 +00:00
|
|
|
|
|
|
|
|
2015-12-18 03:22:13 +00:00
|
|
|
-- |Deletes the given file or symlink.
|
2015-12-17 15:25:37 +00:00
|
|
|
--
|
|
|
|
-- This will throw an exception if the filepath is not absolute
|
|
|
|
-- or the file does not exist.
|
|
|
|
--
|
|
|
|
-- It also throws exceptions from `removeFile`.
|
2015-12-18 03:22:13 +00:00
|
|
|
deleteFile :: DTInfoZipper -> IO ()
|
2015-12-17 22:08:02 +00:00
|
|
|
deleteFile dtz@(File {}, _) = do
|
|
|
|
let fp = getFullPath dtz
|
2015-12-17 15:25:37 +00:00
|
|
|
fileSanityThrow fp
|
|
|
|
removeFile fp
|
2015-12-18 03:22:13 +00:00
|
|
|
deleteFile dtz@(Dir { dir = (DirInfo { sym = True }) }, _) = do
|
|
|
|
let fp = getFullPath dtz
|
|
|
|
throwNotAbsolute fp
|
|
|
|
removeFile fp
|
2015-12-17 22:08:02 +00:00
|
|
|
deleteFile dtz = throw $ NotAFile (getFullPath dtz)
|
2015-12-17 15:25:37 +00:00
|
|
|
|
|
|
|
|
2015-12-18 03:22:13 +00:00
|
|
|
-- |Deletes the given directory. Does not work on symlinks.
|
2015-12-17 15:25:37 +00:00
|
|
|
--
|
|
|
|
-- This will throw an exception if the filepath is not absolute
|
|
|
|
-- or the directory does not exist.
|
|
|
|
--
|
|
|
|
-- It also throws exceptions from `removeDirectory`.
|
2015-12-18 03:22:13 +00:00
|
|
|
deleteDir :: DTInfoZipper -> IO ()
|
2015-12-17 22:08:02 +00:00
|
|
|
deleteDir dtz@(Dir {}, _) = do
|
|
|
|
let fp = getFullPath dtz
|
2015-12-17 15:25:37 +00:00
|
|
|
dirSanityThrow fp
|
|
|
|
removeDirectory fp
|
2015-12-17 22:08:02 +00:00
|
|
|
deleteDir dtz = throw $ NotADir (getFullPath dtz)
|
|
|
|
|
|
|
|
|
2015-12-18 14:28:56 +00:00
|
|
|
-- |Deletes the given directory recursively. Does not work on symlinks.
|
|
|
|
--
|
|
|
|
-- This will throw an exception if the filepath is not absolute
|
|
|
|
-- or the directory does not exist.
|
|
|
|
--
|
|
|
|
-- It also throws exceptions from `removeDirectoryRecursive`.
|
|
|
|
deleteDirRecursive :: DTInfoZipper -> IO ()
|
|
|
|
deleteDirRecursive dtz@(Dir {}, _) = do
|
|
|
|
let fp = getFullPath dtz
|
|
|
|
dirSanityThrow fp
|
|
|
|
removeDirectoryRecursive fp
|
|
|
|
deleteDirRecursive dtz = throw $ NotADir (getFullPath dtz)
|
|
|
|
|
|
|
|
|
2015-12-17 22:08:02 +00:00
|
|
|
-- |Deletes a file or directory, whatever it may be.
|
2015-12-18 03:22:13 +00:00
|
|
|
easyDelete :: DTInfoZipper -> IO ()
|
2015-12-17 22:08:02 +00:00
|
|
|
easyDelete dtz@(File {}, _) = deleteFile dtz
|
2015-12-18 03:22:13 +00:00
|
|
|
easyDelete dtz@(Dir { dir = (DirInfo { sym = True }) }, _) = deleteFile dtz
|
2015-12-17 22:08:02 +00:00
|
|
|
easyDelete dtz@(Dir {}, _) = deleteDir dtz
|
2015-12-17 15:25:37 +00:00
|
|
|
|
|
|
|
|
2015-12-17 03:42:22 +00:00
|
|
|
-- |Opens a file appropriately by invoking xdg-open.
|
|
|
|
--
|
|
|
|
-- This will throw an exception if the filepath is not absolute
|
|
|
|
-- or the file does not exist.
|
2015-12-17 22:08:02 +00:00
|
|
|
openFile :: DTZipper a b
|
2015-12-17 03:42:22 +00:00
|
|
|
-> IO ProcessHandle
|
2015-12-17 22:08:02 +00:00
|
|
|
openFile dtz@(File {}, _) = do
|
|
|
|
let fp = getFullPath dtz
|
2015-12-17 03:42:22 +00:00
|
|
|
fileSanityThrow fp
|
|
|
|
spawnProcess "xdg-open" [fp]
|
2015-12-17 22:08:02 +00:00
|
|
|
openFile dtz = throw $ NotAFile (getFullPath dtz)
|
2015-12-17 03:42:22 +00:00
|
|
|
|
|
|
|
|
|
|
|
-- |Executes a program with the given arguments.
|
|
|
|
--
|
|
|
|
-- This will throw an exception if the filepath is not absolute
|
|
|
|
-- or the file does not exist. It will also throw an exception
|
|
|
|
-- if the file is not executable.
|
2015-12-18 03:22:13 +00:00
|
|
|
executeFile :: DTInfoZipper -- ^ program
|
2015-12-17 03:42:22 +00:00
|
|
|
-> [String] -- ^ arguments
|
|
|
|
-> IO ProcessHandle
|
2015-12-18 03:22:13 +00:00
|
|
|
executeFile dtz@(File { file = (FileInfo { permissions = p }) }, _) args = do
|
2015-12-17 22:08:02 +00:00
|
|
|
let fp = getFullPath dtz
|
2015-12-17 03:42:22 +00:00
|
|
|
fileSanityThrow fp
|
|
|
|
unless (executable p) (throw $ FileNotExecutable fp)
|
|
|
|
spawnProcess fp args
|
2015-12-17 22:08:02 +00:00
|
|
|
executeFile dtz _ = throw $ NotAFile (getFullPath dtz)
|
2015-12-17 03:42:22 +00:00
|
|
|
|