2015-12-17 03:42:22 +00:00
|
|
|
{-# OPTIONS_HADDOCK ignore-exports #-}
|
|
|
|
|
2015-12-19 15:13:48 +00:00
|
|
|
-- |This module provides data types for representing directories/files
|
|
|
|
-- and related operations on it, mostly internal stuff, not actual IO actions.
|
|
|
|
--
|
|
|
|
-- It doesn't allow to represent the whole filesystem, since that's only
|
|
|
|
-- possible through IO laziness, which introduces too much internal state.
|
2015-12-17 03:42:22 +00:00
|
|
|
module Data.DirTree where
|
|
|
|
|
|
|
|
|
|
|
|
import Control.Applicative
|
|
|
|
(
|
|
|
|
(<*>)
|
|
|
|
, (<$>)
|
|
|
|
, (<|>)
|
|
|
|
, pure
|
|
|
|
)
|
|
|
|
import Control.Arrow
|
|
|
|
(
|
|
|
|
first
|
|
|
|
)
|
|
|
|
import Control.Exception
|
|
|
|
(
|
|
|
|
handle
|
|
|
|
)
|
|
|
|
import Control.Exception.Base
|
|
|
|
(
|
|
|
|
IOException
|
|
|
|
)
|
|
|
|
import Control.Monad.State.Lazy
|
|
|
|
(
|
|
|
|
|
|
|
|
)
|
|
|
|
import Data.Default
|
2015-12-19 15:13:48 +00:00
|
|
|
import Data.IntMap.Lazy (IntMap)
|
2015-12-17 03:42:22 +00:00
|
|
|
import Data.List
|
|
|
|
(
|
|
|
|
delete
|
|
|
|
, sort
|
|
|
|
, sortBy
|
|
|
|
, (\\)
|
|
|
|
)
|
2015-12-19 15:13:48 +00:00
|
|
|
import Data.Maybe
|
|
|
|
(
|
|
|
|
fromMaybe
|
|
|
|
)
|
|
|
|
import Data.Ord
|
|
|
|
(
|
|
|
|
comparing
|
|
|
|
)
|
|
|
|
import Data.Time.Clock.POSIX
|
|
|
|
(
|
|
|
|
POSIXTime
|
|
|
|
, posixSecondsToUTCTime
|
|
|
|
)
|
|
|
|
import Data.Traversable
|
2015-12-17 03:42:22 +00:00
|
|
|
(
|
2015-12-19 15:13:48 +00:00
|
|
|
for
|
2015-12-17 03:42:22 +00:00
|
|
|
)
|
|
|
|
import Data.Word
|
|
|
|
(
|
|
|
|
Word64
|
|
|
|
)
|
|
|
|
import System.Directory
|
|
|
|
(
|
2015-12-19 15:13:48 +00:00
|
|
|
doesFileExist
|
|
|
|
, executable
|
2015-12-17 03:42:22 +00:00
|
|
|
, getPermissions
|
2015-12-19 15:13:48 +00:00
|
|
|
, readable
|
2015-12-17 03:42:22 +00:00
|
|
|
, searchable
|
2015-12-19 15:13:48 +00:00
|
|
|
, writable
|
|
|
|
, Permissions
|
2015-12-17 03:42:22 +00:00
|
|
|
)
|
|
|
|
import System.FilePath
|
|
|
|
(
|
|
|
|
combine
|
|
|
|
, equalFilePath
|
|
|
|
, joinPath
|
|
|
|
, splitDirectories
|
|
|
|
, (</>)
|
|
|
|
)
|
|
|
|
import System.IO
|
|
|
|
(
|
|
|
|
IOMode
|
|
|
|
, Handle
|
|
|
|
, openFile
|
|
|
|
)
|
|
|
|
import System.IO.Error
|
|
|
|
(
|
|
|
|
ioeGetErrorType
|
|
|
|
, isDoesNotExistErrorType
|
|
|
|
)
|
|
|
|
import System.IO.Unsafe
|
|
|
|
(
|
|
|
|
unsafeInterleaveIO
|
|
|
|
)
|
|
|
|
import System.Locale
|
|
|
|
(
|
|
|
|
defaultTimeLocale
|
|
|
|
, rfc822DateFormat
|
|
|
|
)
|
2015-12-19 15:13:48 +00:00
|
|
|
import System.Posix.Types
|
|
|
|
(
|
|
|
|
DeviceID
|
|
|
|
, EpochTime
|
|
|
|
, FileID
|
|
|
|
, FileMode
|
|
|
|
, FileOffset
|
|
|
|
, GroupID
|
|
|
|
, LinkCount
|
|
|
|
, UserID
|
|
|
|
)
|
|
|
|
|
2015-12-17 03:42:22 +00:00
|
|
|
import qualified Data.Bitraversable as BT
|
|
|
|
import qualified Data.Bifunctor as BF
|
|
|
|
import qualified Data.Bifoldable as BFL
|
|
|
|
import qualified Data.Traversable as T
|
2015-12-19 15:13:48 +00:00
|
|
|
import qualified System.Posix.Files as PF
|
|
|
|
import qualified System.Posix.Directory as PFD
|
|
|
|
import qualified Data.IntMap.Lazy as IM
|
|
|
|
|
2015-12-17 03:42:22 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
----------------------------
|
|
|
|
--[ BASE TYPES ]--
|
|
|
|
----------------------------
|
|
|
|
|
|
|
|
|
|
|
|
-- |Weak type to distinguish between FilePath and FileName.
|
|
|
|
type FileName = String
|
|
|
|
|
|
|
|
|
2015-12-19 15:13:48 +00:00
|
|
|
-- |Represents a directory with it's contents (one level only), while
|
|
|
|
-- preserving the context via the anchor.
|
|
|
|
data AnchoredDirFile a b =
|
|
|
|
(:/) { anchor :: FilePath, dirTree :: IntMap (DirFile a b) }
|
2015-12-17 03:42:22 +00:00
|
|
|
deriving (Eq, Ord, Show)
|
|
|
|
|
|
|
|
|
|
|
|
-- |The String in the name field is always a file name, never a full path.
|
|
|
|
-- The free type variable is used in the File/Dir constructor and can hold
|
|
|
|
-- Handles, Strings representing a file's contents or anything else you can
|
|
|
|
-- think of. We catch any IO errors in the Failed constructor. an Exception
|
|
|
|
-- can be converted to a String with 'show'.
|
2015-12-19 15:13:48 +00:00
|
|
|
data DirFile a b =
|
2015-12-17 03:42:22 +00:00
|
|
|
Failed {
|
|
|
|
name :: FileName
|
|
|
|
, err :: IOException
|
|
|
|
}
|
|
|
|
| Dir {
|
2015-12-19 15:13:48 +00:00
|
|
|
name :: FileName
|
|
|
|
, dir :: a
|
2015-12-17 03:42:22 +00:00
|
|
|
}
|
|
|
|
| File {
|
|
|
|
name :: FileName
|
|
|
|
, file :: b
|
2015-12-19 15:13:48 +00:00
|
|
|
} deriving (Show, Eq)
|
|
|
|
|
|
|
|
|
|
|
|
-- |All possible file information we could ever need.
|
|
|
|
data FileInfo = FileInfo {
|
|
|
|
deviceID :: DeviceID
|
|
|
|
, fileID :: FileID
|
|
|
|
, fileMode :: FileMode
|
|
|
|
, linkCount :: LinkCount
|
|
|
|
, fileOwner :: UserID
|
|
|
|
, fileGroup :: GroupID
|
|
|
|
, specialDeviceID :: DeviceID
|
|
|
|
, fileSize :: FileOffset
|
|
|
|
, accessTime :: EpochTime
|
|
|
|
, modificationTime :: EpochTime
|
|
|
|
, statusChangeTime :: EpochTime
|
|
|
|
, accessTimeHiRes :: POSIXTime
|
|
|
|
, modificationTimeHiRes :: POSIXTime
|
|
|
|
, statusChangeTimeHiRes :: POSIXTime
|
|
|
|
, isBlockDevice :: Bool
|
|
|
|
, isCharacterDevice :: Bool
|
|
|
|
, isNamedPipe :: Bool
|
|
|
|
, isRegularFile :: Bool
|
|
|
|
, isDirectory :: Bool
|
|
|
|
, isSymbolicLink :: Bool
|
|
|
|
, isSocket :: Bool
|
|
|
|
, permissions :: Permissions
|
|
|
|
} deriving (Show, Eq, Ord)
|
2015-12-17 03:42:22 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
----------------------------
|
|
|
|
--[ INSTANCES ]--
|
|
|
|
----------------------------
|
|
|
|
|
|
|
|
|
2015-12-19 15:13:48 +00:00
|
|
|
instance BF.Bifunctor DirFile where
|
2015-12-17 03:42:22 +00:00
|
|
|
bimap = BT.bimapDefault
|
|
|
|
|
|
|
|
|
2015-12-19 15:13:48 +00:00
|
|
|
instance BFL.Bifoldable DirFile where
|
2015-12-17 03:42:22 +00:00
|
|
|
bifoldMap = BT.bifoldMapDefault
|
|
|
|
|
|
|
|
|
2015-12-19 15:13:48 +00:00
|
|
|
instance BT.Bitraversable DirFile where
|
|
|
|
bitraverse f1 f2 (Dir n b) =
|
|
|
|
Dir n <$> f1 b
|
2015-12-17 03:42:22 +00:00
|
|
|
bitraverse _ f2 (File n a) =
|
|
|
|
File n <$> f2 a
|
|
|
|
bitraverse _ _ (Failed n e) =
|
|
|
|
pure (Failed n e)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- | First compare constructors: Failed < Dir < File...
|
|
|
|
-- Then compare `name`...
|
|
|
|
-- Then compare free variable parameter of `File` constructors
|
2015-12-19 15:13:48 +00:00
|
|
|
instance (Ord a, Ord b, Eq a, Eq b) => Ord (DirFile a b) where
|
2015-12-17 03:42:22 +00:00
|
|
|
compare (File n a) (File n' a') =
|
|
|
|
case compare n n' of
|
|
|
|
EQ -> compare a a'
|
|
|
|
el -> el
|
2015-12-19 15:13:48 +00:00
|
|
|
compare (Dir n b) (Dir n' b') =
|
2015-12-17 03:42:22 +00:00
|
|
|
case compare n n' of
|
2015-12-19 15:13:48 +00:00
|
|
|
EQ -> compare b b'
|
2015-12-17 03:42:22 +00:00
|
|
|
el -> el
|
|
|
|
-- after comparing above we can hand off to shape ord function:
|
|
|
|
compare d d' = comparingShape d d'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
----------------------------
|
|
|
|
--[ HIGH LEVEL FUNCTIONS ]--
|
|
|
|
----------------------------
|
|
|
|
|
|
|
|
|
2015-12-19 15:13:48 +00:00
|
|
|
-- | build an AnchoredDirFile, given the path to a directory, opening the files
|
2015-12-17 03:42:22 +00:00
|
|
|
-- using readFile.
|
|
|
|
-- Uses `readDirectoryWith` internally and has the effect of traversing the
|
|
|
|
-- entire directory structure. See `readDirectoryWithL` for lazy production
|
2015-12-19 15:13:48 +00:00
|
|
|
-- of a DirFile structure.
|
|
|
|
readDirectory :: FilePath -> IO (AnchoredDirFile String String)
|
2015-12-17 03:42:22 +00:00
|
|
|
readDirectory = readDirectoryWith readFile return
|
|
|
|
|
|
|
|
|
|
|
|
-- | same as readDirectory but allows us to, for example, use
|
|
|
|
-- ByteString.readFile to return a tree of ByteStrings.
|
|
|
|
readDirectoryWith :: (FilePath -> IO a)
|
|
|
|
-> (FilePath -> IO b)
|
|
|
|
-> FilePath
|
2015-12-19 15:13:48 +00:00
|
|
|
-> IO (AnchoredDirFile a b)
|
2015-12-17 03:42:22 +00:00
|
|
|
readDirectoryWith fd ff p = buildWith' buildAtOnce' fd ff p
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-----------------------------
|
|
|
|
--[ LOWER LEVEL FUNCTIONS ]--
|
|
|
|
-----------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-12-19 15:13:48 +00:00
|
|
|
-- | builds a DirFile from the contents of the directory passed to it, saving
|
2015-12-17 03:42:22 +00:00
|
|
|
-- the base directory in the Anchored* wrapper. Errors are caught in the tree in
|
|
|
|
-- the Failed constructor. The 'file' fields initially are populated with full
|
|
|
|
-- paths to the files they are abstracting.
|
2015-12-19 15:13:48 +00:00
|
|
|
build :: FilePath -> IO (AnchoredDirFile FilePath FilePath)
|
2015-12-17 03:42:22 +00:00
|
|
|
build = buildWith' buildAtOnce' return return -- we say 'return' here to get
|
|
|
|
-- back a tree of FilePaths
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- -- -- helpers: -- -- --
|
|
|
|
|
|
|
|
|
|
|
|
type UserIO a = FilePath -> IO a
|
2015-12-19 15:13:48 +00:00
|
|
|
type Builder a b = UserIO a -> UserIO b -> FilePath -> IO (IntMap (DirFile a b))
|
2015-12-17 03:42:22 +00:00
|
|
|
|
|
|
|
-- remove non-existent file errors, which are artifacts of the "non-atomic"
|
|
|
|
-- nature of traversing a system firectory tree:
|
|
|
|
buildWith' :: Builder a b
|
|
|
|
-> UserIO a
|
|
|
|
-> UserIO b
|
|
|
|
-> FilePath
|
2015-12-19 15:13:48 +00:00
|
|
|
-> IO (AnchoredDirFile a b)
|
2015-12-17 03:42:22 +00:00
|
|
|
buildWith' bf' fd ff p =
|
|
|
|
do tree <- bf' fd ff p
|
2015-12-19 15:13:48 +00:00
|
|
|
return (p :/ removeNonexistent tree)
|
2015-12-17 03:42:22 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- IO function passed to our builder and finally executed here:
|
|
|
|
buildAtOnce' :: Builder a b
|
2015-12-19 15:13:48 +00:00
|
|
|
buildAtOnce' fd ff p = do
|
|
|
|
contents <- getDirsFiles p
|
|
|
|
for contents $ \n -> handleDT n $ do
|
|
|
|
let subf = p </> n
|
|
|
|
do isFile <- doesFileExist subf
|
|
|
|
if isFile
|
|
|
|
then File n <$> ff subf
|
|
|
|
else Dir n <$> fd subf
|
2015-12-17 03:42:22 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-----------------
|
|
|
|
--[ UTILITIES ]--
|
|
|
|
-----------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
---- HANDLING FAILURES ----
|
|
|
|
|
|
|
|
|
|
|
|
-- | True if any Failed constructors in the tree
|
2015-12-19 15:13:48 +00:00
|
|
|
anyFailed :: [DirFile a b] -> Bool
|
2015-12-17 03:42:22 +00:00
|
|
|
anyFailed = not . successful
|
|
|
|
|
|
|
|
-- | True if there are no Failed constructors in the tree
|
2015-12-19 15:13:48 +00:00
|
|
|
successful :: [DirFile a b] -> Bool
|
2015-12-17 03:42:22 +00:00
|
|
|
successful = null . failures
|
|
|
|
|
|
|
|
|
|
|
|
-- | returns true if argument is a `Failed` constructor:
|
2015-12-19 15:13:48 +00:00
|
|
|
failed :: DirFile a b -> Bool
|
2015-12-17 03:42:22 +00:00
|
|
|
failed (Failed _ _) = True
|
|
|
|
failed _ = False
|
|
|
|
|
|
|
|
|
|
|
|
-- | returns a list of 'Failed' constructors only:
|
2015-12-19 15:13:48 +00:00
|
|
|
failures :: [DirFile a b] -> [DirFile a b]
|
|
|
|
failures = filter failed
|
2015-12-17 03:42:22 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
---- ORDERING AND EQUALITY ----
|
|
|
|
|
|
|
|
|
|
|
|
-- | Tests equality of two trees, ignoring their free variable portion. Can be
|
|
|
|
-- used to check if any files have been added or deleted, for instance.
|
2015-12-19 15:13:48 +00:00
|
|
|
equalShape :: DirFile a b -> DirFile c d -> Bool
|
2015-12-17 03:42:22 +00:00
|
|
|
equalShape d d' = comparingShape d d' == EQ
|
|
|
|
|
2015-12-19 15:13:48 +00:00
|
|
|
-- TODO: we should use equalFilePath here, but how to sort properly?
|
|
|
|
-- with System.Directory.canonicalizePath, before compare?
|
2015-12-17 03:42:22 +00:00
|
|
|
|
|
|
|
-- | a compare function that ignores the free "file" type variable:
|
2015-12-19 15:13:48 +00:00
|
|
|
comparingShape :: DirFile a b -> DirFile c d -> Ordering
|
|
|
|
comparingShape (Dir n _) (Dir n' _) = compare n n'
|
|
|
|
-- else simply compare the flat constructors, non-recursively:
|
2015-12-17 03:42:22 +00:00
|
|
|
comparingShape t t' = comparingConstr t t'
|
|
|
|
|
|
|
|
|
|
|
|
-- HELPER: a non-recursive comparison
|
2015-12-19 15:13:48 +00:00
|
|
|
comparingConstr :: DirFile a b -> DirFile a1 b1 -> Ordering
|
|
|
|
comparingConstr (Failed _ _) (Dir _ _) = LT
|
2015-12-17 03:42:22 +00:00
|
|
|
comparingConstr (Failed _ _) (File _ _) = LT
|
|
|
|
comparingConstr (File _ _) (Failed _ _) = GT
|
2015-12-19 15:13:48 +00:00
|
|
|
comparingConstr (File _ _) (Dir _ _) = GT
|
|
|
|
comparingConstr (Dir _ _) (Failed _ _) = GT
|
|
|
|
comparingConstr (Dir _ _) (File _ _) = LT
|
2015-12-17 03:42:22 +00:00
|
|
|
-- else compare on the names of constructors that are the same, without
|
|
|
|
-- looking at the contents of Dir constructors:
|
|
|
|
comparingConstr t t' = compare (name t) (name t')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
---- OTHER ----
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
---------------
|
|
|
|
--[ HELPERS ]--
|
|
|
|
---------------
|
|
|
|
|
|
|
|
|
|
|
|
---- CONSTRUCTOR IDENTIFIERS ----
|
2015-12-19 15:13:48 +00:00
|
|
|
isFileC :: DirFile a b -> Bool
|
2015-12-17 03:42:22 +00:00
|
|
|
isFileC (File _ _) = True
|
|
|
|
isFileC _ = False
|
|
|
|
|
2015-12-19 15:13:48 +00:00
|
|
|
isDirC :: DirFile a b -> Bool
|
|
|
|
isDirC (Dir _ _) = True
|
|
|
|
isDirC _ = False
|
2015-12-17 03:42:22 +00:00
|
|
|
|
|
|
|
|
|
|
|
---- PATH CONVERSIONS ----
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- extracting pathnames and base names:
|
|
|
|
topDir, baseDir :: FilePath -> FilePath
|
|
|
|
topDir = last . splitDirectories
|
|
|
|
baseDir = joinPath . init . splitDirectories
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
---- IO HELPERS: ----
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
----- the let expression is an annoying hack, because dropFileName "." == ""
|
|
|
|
----- and getDirectoryContents fails epically on ""
|
|
|
|
-- prepares the directory contents list. we sort so that we can be sure of
|
|
|
|
-- a consistent fold/traversal order on the same directory:
|
2015-12-19 15:13:48 +00:00
|
|
|
getDirsFiles :: FilePath -> IO (IntMap FilePath)
|
|
|
|
getDirsFiles fp = do
|
|
|
|
dirstream <- PFD.openDirStream fp
|
|
|
|
let mdirs :: Int -> IntMap FilePath -> IO (IntMap FilePath)
|
|
|
|
mdirs ix dirs = do
|
|
|
|
dir <- PFD.readDirStream dirstream
|
|
|
|
if dir == ""
|
|
|
|
then return dirs
|
|
|
|
else mdirs (ix + 1) (instertF ix dir dirs)
|
|
|
|
dirs <- mdirs 0 IM.empty
|
|
|
|
PFD.closeDirStream dirstream
|
|
|
|
return dirs
|
|
|
|
where
|
|
|
|
instertF ix dir dirs = case dir of
|
|
|
|
"." -> dirs
|
|
|
|
".." -> dirs
|
|
|
|
_ -> IM.insert ix dir dirs
|
2015-12-17 03:42:22 +00:00
|
|
|
|
|
|
|
|
2015-12-19 15:13:48 +00:00
|
|
|
-- |Read a filepath and return transform it into our `AnchoredDirFile`
|
|
|
|
-- with the free variables of both the File and Dir constructors filled
|
|
|
|
-- with `FileInfo`.
|
2015-12-17 03:42:22 +00:00
|
|
|
readPath :: FilePath
|
2015-12-19 15:13:48 +00:00
|
|
|
-> IO (AnchoredDirFile FileInfo FileInfo)
|
|
|
|
readPath = readDirectoryWith getFileInfo getFileInfo
|
|
|
|
|
|
|
|
|
|
|
|
-- |Gets all file information.
|
|
|
|
getFileInfo :: FilePath -> IO FileInfo
|
|
|
|
getFileInfo fp = do
|
|
|
|
fs <- PF.getSymbolicLinkStatus fp
|
|
|
|
perms <- getPermissions fp
|
|
|
|
return $ FileInfo
|
|
|
|
(PF.deviceID fs)
|
|
|
|
(PF.fileID fs)
|
|
|
|
(PF.fileMode fs)
|
|
|
|
(PF.linkCount fs)
|
|
|
|
(PF.fileOwner fs)
|
|
|
|
(PF.fileGroup fs)
|
|
|
|
(PF.specialDeviceID fs)
|
|
|
|
(PF.fileSize fs)
|
|
|
|
(PF.accessTime fs)
|
|
|
|
(PF.modificationTime fs)
|
|
|
|
(PF.statusChangeTime fs)
|
|
|
|
(PF.accessTimeHiRes fs)
|
|
|
|
(PF.modificationTimeHiRes fs)
|
|
|
|
(PF.statusChangeTimeHiRes fs)
|
|
|
|
(PF.isBlockDevice fs)
|
|
|
|
(PF.isCharacterDevice fs)
|
|
|
|
(PF.isNamedPipe fs)
|
|
|
|
(PF.isRegularFile fs)
|
|
|
|
(PF.isDirectory fs)
|
|
|
|
(PF.isSymbolicLink fs)
|
|
|
|
(PF.isSocket fs)
|
|
|
|
perms
|
|
|
|
|
|
|
|
|
|
|
|
-- |Gets the free variable. Returns Nothing if the constructor is of `Failed`.
|
|
|
|
getFreeVar :: DirFile a a -> Maybe a
|
2015-12-17 03:42:22 +00:00
|
|
|
getFreeVar (File _ f) = Just f
|
2015-12-19 15:13:48 +00:00
|
|
|
getFreeVar (Dir _ d) = Just d
|
2015-12-17 03:42:22 +00:00
|
|
|
getFreeVar _ = Nothing
|
|
|
|
|
|
|
|
|
|
|
|
---- FAILURE HELPERS: ----
|
|
|
|
|
|
|
|
|
|
|
|
-- handles an IO exception by returning a Failed constructor filled with that
|
|
|
|
-- exception:
|
2015-12-19 15:13:48 +00:00
|
|
|
handleDT :: FileName -> IO (DirFile a b) -> IO (DirFile a b)
|
2015-12-17 03:42:22 +00:00
|
|
|
handleDT n = handle (return . Failed n)
|
|
|
|
|
|
|
|
|
|
|
|
-- DoesNotExist errors not present at the topmost level could happen if a
|
|
|
|
-- named file or directory is deleted after being listed by
|
|
|
|
-- getDirectoryContents but before we can get it into memory.
|
|
|
|
-- So we filter those errors out because the user should not see errors
|
|
|
|
-- raised by the internal implementation of this module:
|
|
|
|
-- This leaves the error if it exists in the top (user-supplied) level:
|
2015-12-19 15:13:48 +00:00
|
|
|
removeNonexistent :: IntMap (DirFile a b) -> IntMap (DirFile a b)
|
|
|
|
removeNonexistent = IM.filter isOkConstructor
|
2015-12-17 03:42:22 +00:00
|
|
|
where isOkConstructor c = not (failed c) || isOkError c
|
|
|
|
isOkError = not . isDoesNotExistErrorType . ioeGetErrorType . err
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
---- OTHER: ----
|
|
|
|
|
|
|
|
|
2015-12-19 15:13:48 +00:00
|
|
|
-- TODO: use Maybe type?
|
|
|
|
dirLookup :: AnchoredDirFile a b -> Int -> DirFile a b
|
|
|
|
dirLookup df ix =
|
|
|
|
fromMaybe errTree (IM.lookup ix . dirTree $ df)
|
|
|
|
where
|
|
|
|
errTree = Failed "Not found!!!"
|
|
|
|
(userError $ "Failed to lookup index " ++ show ix)
|
2015-12-17 03:42:22 +00:00
|
|
|
|
|
|
|
|
2015-12-19 15:13:48 +00:00
|
|
|
subDirName :: AnchoredDirFile a b -> Int -> FilePath
|
|
|
|
subDirName df ix = anchor df </> name (dirLookup df ix)
|
2015-12-17 03:42:22 +00:00
|
|
|
|
|
|
|
|
2015-12-19 15:13:48 +00:00
|
|
|
fromFreeVar :: (Default d) => (a -> d) -> DirFile a a -> d
|
|
|
|
fromFreeVar f df = maybeD f $ getFreeVar df
|
2015-12-17 03:42:22 +00:00
|
|
|
|
|
|
|
|
|
|
|
maybeD :: (Default b) => (a -> b) -> Maybe a -> b
|
|
|
|
maybeD = maybe def
|
|
|
|
|
|
|
|
|
|
|
|
-- |Pack the modification time
|
2015-12-19 15:13:48 +00:00
|
|
|
packModTime :: DirFile FileInfo FileInfo
|
2015-12-17 03:42:22 +00:00
|
|
|
-> String
|
|
|
|
packModTime = fromFreeVar
|
2015-12-19 15:13:48 +00:00
|
|
|
$ show . posixSecondsToUTCTime . realToFrac . modificationTime
|
2015-12-17 03:42:22 +00:00
|
|
|
|
2015-12-19 15:13:48 +00:00
|
|
|
packPermissions :: DirFile FileInfo FileInfo
|
2015-12-17 03:42:22 +00:00
|
|
|
-> String
|
|
|
|
packPermissions dt = fromFreeVar (pStr . permissions) dt
|
|
|
|
where
|
|
|
|
pStr perm = str perm readable "r"
|
|
|
|
++ str perm writable "w"
|
|
|
|
++ str perm (if isDirC dt then searchable else executable)
|
|
|
|
"x"
|
|
|
|
str perm f ch
|
|
|
|
| f perm = ch
|
|
|
|
| otherwise = "-"
|