LIB/GTK: change DirTree again
we now have: * AnchoredFile -- for representing a file with context * File -- for representing a file only Both representations mean "file" in the broader sense, including directories.
This commit is contained in:
parent
5bfea0db10
commit
fe6145d5be
@ -22,6 +22,7 @@ library
|
|||||||
IO.Utils
|
IO.Utils
|
||||||
IO.File
|
IO.File
|
||||||
IO.Error
|
IO.Error
|
||||||
|
MyPrelude
|
||||||
|
|
||||||
build-depends: base >= 4.7,
|
build-depends: base >= 4.7,
|
||||||
data-default,
|
data-default,
|
||||||
@ -50,6 +51,7 @@ executable hsfm-gtk
|
|||||||
GUI.Gtk.Gui
|
GUI.Gtk.Gui
|
||||||
GUI.Gtk.Icons
|
GUI.Gtk.Icons
|
||||||
GUI.Gtk.Utils
|
GUI.Gtk.Utils
|
||||||
|
MyPrelude
|
||||||
build-depends: hsfm,
|
build-depends: hsfm,
|
||||||
base >= 4.7,
|
base >= 4.7,
|
||||||
containers,
|
containers,
|
||||||
|
@ -32,7 +32,6 @@ import Control.Monad.State.Lazy
|
|||||||
|
|
||||||
)
|
)
|
||||||
import Data.Default
|
import Data.Default
|
||||||
import Data.IntMap.Lazy (IntMap)
|
|
||||||
import Data.List
|
import Data.List
|
||||||
(
|
(
|
||||||
delete
|
delete
|
||||||
@ -61,6 +60,10 @@ import Data.Word
|
|||||||
(
|
(
|
||||||
Word64
|
Word64
|
||||||
)
|
)
|
||||||
|
import Safe
|
||||||
|
(
|
||||||
|
atDef
|
||||||
|
)
|
||||||
import System.Directory
|
import System.Directory
|
||||||
(
|
(
|
||||||
doesFileExist
|
doesFileExist
|
||||||
@ -77,6 +80,7 @@ import System.FilePath
|
|||||||
, equalFilePath
|
, equalFilePath
|
||||||
, joinPath
|
, joinPath
|
||||||
, splitDirectories
|
, splitDirectories
|
||||||
|
, takeFileName
|
||||||
, (</>)
|
, (</>)
|
||||||
)
|
)
|
||||||
import System.IO
|
import System.IO
|
||||||
@ -117,7 +121,6 @@ import qualified Data.Bifoldable as BFL
|
|||||||
import qualified Data.Traversable as T
|
import qualified Data.Traversable as T
|
||||||
import qualified System.Posix.Files as PF
|
import qualified System.Posix.Files as PF
|
||||||
import qualified System.Posix.Directory as PFD
|
import qualified System.Posix.Directory as PFD
|
||||||
import qualified Data.IntMap.Lazy as IM
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -131,10 +134,10 @@ import qualified Data.IntMap.Lazy as IM
|
|||||||
type FileName = String
|
type FileName = String
|
||||||
|
|
||||||
|
|
||||||
-- |Represents a directory with it's contents (one level only), while
|
-- |Represents a file. The `anchor` field is the path
|
||||||
-- preserving the context via the anchor.
|
-- to that file without the filename.
|
||||||
data AnchoredDirFile a b =
|
data AnchoredFile a b =
|
||||||
(:/) { anchor :: FilePath, dirTree :: IntMap (DirFile a b) }
|
(:/) { anchor :: FilePath, file :: File a b }
|
||||||
deriving (Eq, Ord, Show)
|
deriving (Eq, Ord, Show)
|
||||||
|
|
||||||
|
|
||||||
@ -143,7 +146,7 @@ data AnchoredDirFile a b =
|
|||||||
-- Handles, Strings representing a file's contents or anything else you can
|
-- 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
|
-- think of. We catch any IO errors in the Failed constructor. an Exception
|
||||||
-- can be converted to a String with 'show'.
|
-- can be converted to a String with 'show'.
|
||||||
data DirFile a b =
|
data File a b =
|
||||||
Failed {
|
Failed {
|
||||||
name :: FileName
|
name :: FileName
|
||||||
, err :: IOException
|
, err :: IOException
|
||||||
@ -152,9 +155,9 @@ data DirFile a b =
|
|||||||
name :: FileName
|
name :: FileName
|
||||||
, dir :: a
|
, dir :: a
|
||||||
}
|
}
|
||||||
| File {
|
| RegFile {
|
||||||
name :: FileName
|
name :: FileName
|
||||||
, file :: b
|
, regFile :: b
|
||||||
} deriving (Show, Eq)
|
} deriving (Show, Eq)
|
||||||
|
|
||||||
|
|
||||||
@ -192,19 +195,19 @@ data FileInfo = FileInfo {
|
|||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
|
|
||||||
instance BF.Bifunctor DirFile where
|
instance BF.Bifunctor File where
|
||||||
bimap = BT.bimapDefault
|
bimap = BT.bimapDefault
|
||||||
|
|
||||||
|
|
||||||
instance BFL.Bifoldable DirFile where
|
instance BFL.Bifoldable File where
|
||||||
bifoldMap = BT.bifoldMapDefault
|
bifoldMap = BT.bifoldMapDefault
|
||||||
|
|
||||||
|
|
||||||
instance BT.Bitraversable DirFile where
|
instance BT.Bitraversable File where
|
||||||
bitraverse f1 f2 (Dir n b) =
|
bitraverse f1 f2 (Dir n b) =
|
||||||
Dir n <$> f1 b
|
Dir n <$> f1 b
|
||||||
bitraverse _ f2 (File n a) =
|
bitraverse _ f2 (RegFile n a) =
|
||||||
File n <$> f2 a
|
RegFile n <$> f2 a
|
||||||
bitraverse _ _ (Failed n e) =
|
bitraverse _ _ (Failed n e) =
|
||||||
pure (Failed n e)
|
pure (Failed n e)
|
||||||
|
|
||||||
@ -213,8 +216,8 @@ instance BT.Bitraversable DirFile where
|
|||||||
-- | First compare constructors: Failed < Dir < File...
|
-- | First compare constructors: Failed < Dir < File...
|
||||||
-- Then compare `name`...
|
-- Then compare `name`...
|
||||||
-- Then compare free variable parameter of `File` constructors
|
-- Then compare free variable parameter of `File` constructors
|
||||||
instance (Ord a, Ord b, Eq a, Eq b) => Ord (DirFile a b) where
|
instance (Ord a, Ord b, Eq a, Eq b) => Ord (File a b) where
|
||||||
compare (File n a) (File n' a') =
|
compare (RegFile n a) (RegFile n' a') =
|
||||||
case compare n n' of
|
case compare n n' of
|
||||||
EQ -> compare a a'
|
EQ -> compare a a'
|
||||||
el -> el
|
el -> el
|
||||||
@ -235,13 +238,33 @@ instance (Ord a, Ord b, Eq a, Eq b) => Ord (DirFile a b) where
|
|||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
|
|
||||||
-- | build an AnchoredDirFile, given the path to a directory, opening the files
|
-- |Read a file into an `AnchoredFile`, filling the free variables via
|
||||||
-- using readFile.
|
-- `getFileInfo`. This also works on directories, but doesn't look at
|
||||||
-- Uses `readDirectoryWith` internally and has the effect of traversing the
|
-- their contents.
|
||||||
-- entire directory structure. See `readDirectoryWithL` for lazy production
|
readFileWith :: (FilePath -> IO a)
|
||||||
-- of a DirFile structure.
|
-> (FilePath -> IO b)
|
||||||
readDirectory :: FilePath -> IO (AnchoredDirFile String String)
|
-> FilePath
|
||||||
readDirectory = readDirectoryWith readFile return
|
-> IO (AnchoredFile a b)
|
||||||
|
readFileWith fd ff fp = do
|
||||||
|
file <- handleDT fn $ do
|
||||||
|
isFile <- doesFileExist fp
|
||||||
|
if isFile
|
||||||
|
then RegFile fn <$> ff fp
|
||||||
|
else Dir fn <$> fd fp
|
||||||
|
return (bd :/ file)
|
||||||
|
where
|
||||||
|
fn = topDir fp
|
||||||
|
bd = baseDir fp
|
||||||
|
|
||||||
|
|
||||||
|
readFile :: FilePath -> IO (AnchoredFile FileInfo FileInfo)
|
||||||
|
readFile = readFileWith getFileInfo getFileInfo
|
||||||
|
|
||||||
|
|
||||||
|
-- |Build a list of AnchoredFile, given the path to a directory, filling
|
||||||
|
-- the free variables via `getFileInfo`.
|
||||||
|
readDirectory :: FilePath -> IO [AnchoredFile FileInfo FileInfo]
|
||||||
|
readDirectory = readDirectoryWith getFileInfo getFileInfo
|
||||||
|
|
||||||
|
|
||||||
-- | same as readDirectory but allows us to, for example, use
|
-- | same as readDirectory but allows us to, for example, use
|
||||||
@ -249,7 +272,7 @@ readDirectory = readDirectoryWith readFile return
|
|||||||
readDirectoryWith :: (FilePath -> IO a)
|
readDirectoryWith :: (FilePath -> IO a)
|
||||||
-> (FilePath -> IO b)
|
-> (FilePath -> IO b)
|
||||||
-> FilePath
|
-> FilePath
|
||||||
-> IO (AnchoredDirFile a b)
|
-> IO [AnchoredFile a b]
|
||||||
readDirectoryWith fd ff p = buildWith' buildAtOnce' fd ff p
|
readDirectoryWith fd ff p = buildWith' buildAtOnce' fd ff p
|
||||||
|
|
||||||
|
|
||||||
@ -261,11 +284,11 @@ readDirectoryWith fd ff p = buildWith' buildAtOnce' fd ff p
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- | builds a DirFile from the contents of the directory passed to it, saving
|
-- | builds a File from the contents of the directory passed to it, saving
|
||||||
-- the base directory in the Anchored* wrapper. Errors are caught in the tree in
|
-- 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
|
-- the Failed constructor. The 'file' fields initially are populated with full
|
||||||
-- paths to the files they are abstracting.
|
-- paths to the files they are abstracting.
|
||||||
build :: FilePath -> IO (AnchoredDirFile FilePath FilePath)
|
build :: FilePath -> IO [AnchoredFile FilePath FilePath]
|
||||||
build = buildWith' buildAtOnce' return return -- we say 'return' here to get
|
build = buildWith' buildAtOnce' return return -- we say 'return' here to get
|
||||||
-- back a tree of FilePaths
|
-- back a tree of FilePaths
|
||||||
|
|
||||||
@ -275,7 +298,7 @@ build = buildWith' buildAtOnce' return return -- we say 'return' here to get
|
|||||||
|
|
||||||
|
|
||||||
type UserIO a = FilePath -> IO a
|
type UserIO a = FilePath -> IO a
|
||||||
type Builder a b = UserIO a -> UserIO b -> FilePath -> IO (IntMap (DirFile a b))
|
type Builder a b = UserIO a -> UserIO b -> FilePath -> IO [File a b]
|
||||||
|
|
||||||
-- remove non-existent file errors, which are artifacts of the "non-atomic"
|
-- remove non-existent file errors, which are artifacts of the "non-atomic"
|
||||||
-- nature of traversing a system firectory tree:
|
-- nature of traversing a system firectory tree:
|
||||||
@ -283,10 +306,10 @@ buildWith' :: Builder a b
|
|||||||
-> UserIO a
|
-> UserIO a
|
||||||
-> UserIO b
|
-> UserIO b
|
||||||
-> FilePath
|
-> FilePath
|
||||||
-> IO (AnchoredDirFile a b)
|
-> IO [AnchoredFile a b]
|
||||||
buildWith' bf' fd ff p =
|
buildWith' bf' fd ff p =
|
||||||
do tree <- bf' fd ff p
|
do tree <- bf' fd ff p
|
||||||
return (p :/ removeNonexistent tree)
|
return $ fmap (p :/) (removeNonexistent tree)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -298,7 +321,7 @@ buildAtOnce' fd ff p = do
|
|||||||
let subf = p </> n
|
let subf = p </> n
|
||||||
do isFile <- doesFileExist subf
|
do isFile <- doesFileExist subf
|
||||||
if isFile
|
if isFile
|
||||||
then File n <$> ff subf
|
then RegFile n <$> ff subf
|
||||||
else Dir n <$> fd subf
|
else Dir n <$> fd subf
|
||||||
|
|
||||||
|
|
||||||
@ -314,22 +337,22 @@ buildAtOnce' fd ff p = do
|
|||||||
|
|
||||||
|
|
||||||
-- | True if any Failed constructors in the tree
|
-- | True if any Failed constructors in the tree
|
||||||
anyFailed :: [DirFile a b] -> Bool
|
anyFailed :: [File a b] -> Bool
|
||||||
anyFailed = not . successful
|
anyFailed = not . successful
|
||||||
|
|
||||||
-- | True if there are no Failed constructors in the tree
|
-- | True if there are no Failed constructors in the tree
|
||||||
successful :: [DirFile a b] -> Bool
|
successful :: [File a b] -> Bool
|
||||||
successful = null . failures
|
successful = null . failures
|
||||||
|
|
||||||
|
|
||||||
-- | returns true if argument is a `Failed` constructor:
|
-- | returns true if argument is a `Failed` constructor:
|
||||||
failed :: DirFile a b -> Bool
|
failed :: File a b -> Bool
|
||||||
failed (Failed _ _) = True
|
failed (Failed _ _) = True
|
||||||
failed _ = False
|
failed _ = False
|
||||||
|
|
||||||
|
|
||||||
-- | returns a list of 'Failed' constructors only:
|
-- | returns a list of 'Failed' constructors only:
|
||||||
failures :: [DirFile a b] -> [DirFile a b]
|
failures :: [File a b] -> [File a b]
|
||||||
failures = filter failed
|
failures = filter failed
|
||||||
|
|
||||||
|
|
||||||
@ -339,27 +362,27 @@ failures = filter failed
|
|||||||
|
|
||||||
-- | Tests equality of two trees, ignoring their free variable portion. Can be
|
-- | 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.
|
-- used to check if any files have been added or deleted, for instance.
|
||||||
equalShape :: DirFile a b -> DirFile c d -> Bool
|
equalShape :: File a b -> File c d -> Bool
|
||||||
equalShape d d' = comparingShape d d' == EQ
|
equalShape d d' = comparingShape d d' == EQ
|
||||||
|
|
||||||
-- TODO: we should use equalFilePath here, but how to sort properly?
|
-- TODO: we should use equalFilePath here, but how to sort properly?
|
||||||
-- with System.Directory.canonicalizePath, before compare?
|
-- with System.Directory.canonicalizePath, before compare?
|
||||||
|
|
||||||
-- | a compare function that ignores the free "file" type variable:
|
-- | a compare function that ignores the free "file" type variable:
|
||||||
comparingShape :: DirFile a b -> DirFile c d -> Ordering
|
comparingShape :: File a b -> File c d -> Ordering
|
||||||
comparingShape (Dir n _) (Dir n' _) = compare n n'
|
comparingShape (Dir n _) (Dir n' _) = compare n n'
|
||||||
-- else simply compare the flat constructors, non-recursively:
|
-- else simply compare the flat constructors, non-recursively:
|
||||||
comparingShape t t' = comparingConstr t t'
|
comparingShape t t' = comparingConstr t t'
|
||||||
|
|
||||||
|
|
||||||
-- HELPER: a non-recursive comparison
|
-- HELPER: a non-recursive comparison
|
||||||
comparingConstr :: DirFile a b -> DirFile a1 b1 -> Ordering
|
comparingConstr :: File a b -> File a1 b1 -> Ordering
|
||||||
comparingConstr (Failed _ _) (Dir _ _) = LT
|
comparingConstr (Failed _ _) (Dir _ _) = LT
|
||||||
comparingConstr (Failed _ _) (File _ _) = LT
|
comparingConstr (Failed _ _) (RegFile _ _) = LT
|
||||||
comparingConstr (File _ _) (Failed _ _) = GT
|
comparingConstr (RegFile _ _) (Failed _ _) = GT
|
||||||
comparingConstr (File _ _) (Dir _ _) = GT
|
comparingConstr (RegFile _ _) (Dir _ _) = GT
|
||||||
comparingConstr (Dir _ _) (Failed _ _) = GT
|
comparingConstr (Dir _ _) (Failed _ _) = GT
|
||||||
comparingConstr (Dir _ _) (File _ _) = LT
|
comparingConstr (Dir _ _) (RegFile _ _) = LT
|
||||||
-- else compare on the names of constructors that are the same, without
|
-- else compare on the names of constructors that are the same, without
|
||||||
-- looking at the contents of Dir constructors:
|
-- looking at the contents of Dir constructors:
|
||||||
comparingConstr t t' = compare (name t) (name t')
|
comparingConstr t t' = compare (name t) (name t')
|
||||||
@ -377,11 +400,11 @@ comparingConstr t t' = compare (name t) (name t')
|
|||||||
|
|
||||||
|
|
||||||
---- CONSTRUCTOR IDENTIFIERS ----
|
---- CONSTRUCTOR IDENTIFIERS ----
|
||||||
isFileC :: DirFile a b -> Bool
|
isFileC :: File a b -> Bool
|
||||||
isFileC (File _ _) = True
|
isFileC (RegFile _ _) = True
|
||||||
isFileC _ = False
|
isFileC _ = False
|
||||||
|
|
||||||
isDirC :: DirFile a b -> Bool
|
isDirC :: File a b -> Bool
|
||||||
isDirC (Dir _ _) = True
|
isDirC (Dir _ _) = True
|
||||||
isDirC _ = False
|
isDirC _ = False
|
||||||
|
|
||||||
@ -400,37 +423,38 @@ baseDir = joinPath . init . splitDirectories
|
|||||||
---- IO HELPERS: ----
|
---- IO HELPERS: ----
|
||||||
|
|
||||||
|
|
||||||
|
-- |Go up one directory in the filesystem hierarchy.
|
||||||
|
goUp :: AnchoredFile FileInfo FileInfo -> IO (AnchoredFile FileInfo FileInfo)
|
||||||
|
goUp af@("" :/ _) = return af
|
||||||
|
goUp (bp :/ _) = Data.DirTree.readFile bp
|
||||||
|
|
||||||
----- the let expression is an annoying hack, because dropFileName "." == ""
|
|
||||||
----- and getDirectoryContents fails epically on ""
|
getContents :: AnchoredFile FileInfo FileInfo
|
||||||
-- prepares the directory contents list. we sort so that we can be sure of
|
-> IO [AnchoredFile FileInfo FileInfo]
|
||||||
-- a consistent fold/traversal order on the same directory:
|
getContents (bp :/ Dir n _) = readDirectory (bp </> n)
|
||||||
getDirsFiles :: FilePath -> IO (IntMap FilePath)
|
getContents _ = return []
|
||||||
|
|
||||||
|
|
||||||
|
-- |Get all files of a given directory and return them as a List.
|
||||||
|
getDirsFiles :: FilePath -> IO [FilePath]
|
||||||
getDirsFiles fp = do
|
getDirsFiles fp = do
|
||||||
dirstream <- PFD.openDirStream fp
|
dirstream <- PFD.openDirStream fp
|
||||||
let mdirs :: Int -> IntMap FilePath -> IO (IntMap FilePath)
|
let mdirs :: [FilePath] -> IO [FilePath]
|
||||||
mdirs ix dirs = do
|
mdirs dirs = do
|
||||||
dir <- PFD.readDirStream dirstream
|
dir <- PFD.readDirStream dirstream
|
||||||
if dir == ""
|
if dir == ""
|
||||||
then return dirs
|
then return dirs
|
||||||
else mdirs (ix + 1) (instertF ix dir dirs)
|
else mdirs (instert dir dirs)
|
||||||
dirs <- mdirs 0 IM.empty
|
dirs <- mdirs []
|
||||||
PFD.closeDirStream dirstream
|
PFD.closeDirStream dirstream
|
||||||
return dirs
|
return dirs
|
||||||
where
|
where
|
||||||
instertF ix dir dirs = case dir of
|
instert dir dirs = case dir of
|
||||||
"." -> dirs
|
"." -> dirs
|
||||||
".." -> dirs
|
".." -> dirs
|
||||||
_ -> IM.insert ix dir dirs
|
_ -> dir : dirs
|
||||||
|
|
||||||
|
|
||||||
-- |Read a filepath and return transform it into our `AnchoredDirFile`
|
|
||||||
-- with the free variables of both the File and Dir constructors filled
|
|
||||||
-- with `FileInfo`.
|
|
||||||
readPath :: FilePath
|
|
||||||
-> IO (AnchoredDirFile FileInfo FileInfo)
|
|
||||||
readPath = readDirectoryWith getFileInfo getFileInfo
|
|
||||||
|
|
||||||
|
|
||||||
-- |Gets all file information.
|
-- |Gets all file information.
|
||||||
getFileInfo :: FilePath -> IO FileInfo
|
getFileInfo :: FilePath -> IO FileInfo
|
||||||
@ -463,8 +487,8 @@ getFileInfo fp = do
|
|||||||
|
|
||||||
|
|
||||||
-- |Gets the free variable. Returns Nothing if the constructor is of `Failed`.
|
-- |Gets the free variable. Returns Nothing if the constructor is of `Failed`.
|
||||||
getFreeVar :: DirFile a a -> Maybe a
|
getFreeVar :: File a a -> Maybe a
|
||||||
getFreeVar (File _ f) = Just f
|
getFreeVar (RegFile _ f) = Just f
|
||||||
getFreeVar (Dir _ d) = Just d
|
getFreeVar (Dir _ d) = Just d
|
||||||
getFreeVar _ = Nothing
|
getFreeVar _ = Nothing
|
||||||
|
|
||||||
@ -474,7 +498,7 @@ getFreeVar _ = Nothing
|
|||||||
|
|
||||||
-- handles an IO exception by returning a Failed constructor filled with that
|
-- handles an IO exception by returning a Failed constructor filled with that
|
||||||
-- exception:
|
-- exception:
|
||||||
handleDT :: FileName -> IO (DirFile a b) -> IO (DirFile a b)
|
handleDT :: FileName -> IO (File a b) -> IO (File a b)
|
||||||
handleDT n = handle (return . Failed n)
|
handleDT n = handle (return . Failed n)
|
||||||
|
|
||||||
|
|
||||||
@ -484,8 +508,8 @@ handleDT n = handle (return . Failed n)
|
|||||||
-- So we filter those errors out because the user should not see errors
|
-- So we filter those errors out because the user should not see errors
|
||||||
-- raised by the internal implementation of this module:
|
-- raised by the internal implementation of this module:
|
||||||
-- This leaves the error if it exists in the top (user-supplied) level:
|
-- This leaves the error if it exists in the top (user-supplied) level:
|
||||||
removeNonexistent :: IntMap (DirFile a b) -> IntMap (DirFile a b)
|
removeNonexistent :: [File a b] -> [File a b]
|
||||||
removeNonexistent = IM.filter isOkConstructor
|
removeNonexistent = filter isOkConstructor
|
||||||
where isOkConstructor c = not (failed c) || isOkError c
|
where isOkConstructor c = not (failed c) || isOkError c
|
||||||
isOkError = not . isDoesNotExistErrorType . ioeGetErrorType . err
|
isOkError = not . isDoesNotExistErrorType . ioeGetErrorType . err
|
||||||
|
|
||||||
@ -495,20 +519,11 @@ removeNonexistent = IM.filter isOkConstructor
|
|||||||
---- OTHER: ----
|
---- OTHER: ----
|
||||||
|
|
||||||
|
|
||||||
-- TODO: use Maybe type?
|
fullPath :: AnchoredFile a b -> FilePath
|
||||||
dirLookup :: AnchoredDirFile a b -> Int -> DirFile a b
|
fullPath (bp :/ f) = bp </> name f
|
||||||
dirLookup df ix =
|
|
||||||
fromMaybe errTree (IM.lookup ix . dirTree $ df)
|
|
||||||
where
|
|
||||||
errTree = Failed "Not found!!!"
|
|
||||||
(userError $ "Failed to lookup index " ++ show ix)
|
|
||||||
|
|
||||||
|
|
||||||
subDirName :: AnchoredDirFile a b -> Int -> FilePath
|
fromFreeVar :: (Default d) => (a -> d) -> File a a -> d
|
||||||
subDirName df ix = anchor df </> name (dirLookup df ix)
|
|
||||||
|
|
||||||
|
|
||||||
fromFreeVar :: (Default d) => (a -> d) -> DirFile a a -> d
|
|
||||||
fromFreeVar f df = maybeD f $ getFreeVar df
|
fromFreeVar f df = maybeD f $ getFreeVar df
|
||||||
|
|
||||||
|
|
||||||
@ -517,12 +532,12 @@ maybeD = maybe def
|
|||||||
|
|
||||||
|
|
||||||
-- |Pack the modification time
|
-- |Pack the modification time
|
||||||
packModTime :: DirFile FileInfo FileInfo
|
packModTime :: File FileInfo FileInfo
|
||||||
-> String
|
-> String
|
||||||
packModTime = fromFreeVar
|
packModTime = fromFreeVar
|
||||||
$ show . posixSecondsToUTCTime . realToFrac . modificationTime
|
$ show . posixSecondsToUTCTime . realToFrac . modificationTime
|
||||||
|
|
||||||
packPermissions :: DirFile FileInfo FileInfo
|
packPermissions :: File FileInfo FileInfo
|
||||||
-> String
|
-> String
|
||||||
packPermissions dt = fromFreeVar (pStr . permissions) dt
|
packPermissions dt = fromFreeVar (pStr . permissions) dt
|
||||||
where
|
where
|
||||||
|
@ -41,8 +41,6 @@ import System.Glib.UTFString
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
import qualified Data.IntMap.Lazy as IM
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -107,14 +105,13 @@ urlGoTo mygui myview = do
|
|||||||
--
|
--
|
||||||
-- * 'fsState' reads
|
-- * 'fsState' reads
|
||||||
open :: Row -> MyGUI -> MyView -> IO ()
|
open :: Row -> MyGUI -> MyView -> IO ()
|
||||||
open row mygui myview = do
|
open row mygui myview =
|
||||||
fS <- readTVarIO $ fsState myview
|
case row of
|
||||||
case IM.lookup row (dirTree fS) of
|
r@(_ :/ Dir _ _) -> do
|
||||||
Just dt@(Dir n _) -> do
|
nv <- Data.DirTree.readFile $ fullPath r
|
||||||
newP <- readPath (anchor fS </> n)
|
refreshTreeView' mygui myview nv
|
||||||
refreshTreeView' mygui myview newP
|
r@(_ :/ RegFile _ _) ->
|
||||||
Just dt@(File n _) ->
|
withErrorDialog $ openFile $ fullPath r
|
||||||
withErrorDialog $ openFile (anchor fS </> n)
|
|
||||||
_ -> return ()
|
_ -> return ()
|
||||||
|
|
||||||
|
|
||||||
@ -124,24 +121,23 @@ open row mygui myview = do
|
|||||||
--
|
--
|
||||||
-- * 'fsState' reads
|
-- * 'fsState' reads
|
||||||
del :: Row -> MyGUI -> MyView -> IO ()
|
del :: Row -> MyGUI -> MyView -> IO ()
|
||||||
del row mygui myview = do
|
del row mygui myview =
|
||||||
fS <- readTVarIO $ fsState myview
|
case row of
|
||||||
case dirLookup fS row of
|
r@(_ :/ Dir _ _) -> do
|
||||||
dt@(Dir n _) -> do
|
let fp = fullPath r
|
||||||
let fp = anchor fS </> n
|
subADT <- readDirectory fp
|
||||||
subADT <- readPath fp
|
|
||||||
let cmsg = "Really delete directory \"" ++ fp ++ "\"?"
|
let cmsg = "Really delete directory \"" ++ fp ++ "\"?"
|
||||||
cmsg2 = "Directory \"" ++ fp ++
|
cmsg2 = "Directory \"" ++ fp ++
|
||||||
"\" is not empty! Delete all contents?"
|
"\" is not empty! Delete all contents?"
|
||||||
withConfirmationDialog cmsg $
|
withConfirmationDialog cmsg $
|
||||||
if IM.null (dirTree subADT)
|
if null subADT
|
||||||
then withErrorDialog (deleteDir fp
|
then withErrorDialog (deleteDir fp
|
||||||
>> refreshTreeView mygui myview Nothing)
|
>> refreshTreeView mygui myview Nothing)
|
||||||
else withConfirmationDialog cmsg2 $ withErrorDialog
|
else withConfirmationDialog cmsg2 $ withErrorDialog
|
||||||
(deleteDirRecursive fp
|
(deleteDirRecursive fp
|
||||||
>> refreshTreeView mygui myview Nothing)
|
>> refreshTreeView mygui myview Nothing)
|
||||||
dt@(File _ _) -> do
|
r@(_ :/ RegFile _ _) -> do
|
||||||
let fp = subDirName fS row
|
let fp = fullPath r
|
||||||
cmsg = "Really delete file \"" ++ fp ++ "\"?"
|
cmsg = "Really delete file \"" ++ fp ++ "\"?"
|
||||||
withConfirmationDialog cmsg
|
withConfirmationDialog cmsg
|
||||||
$ withErrorDialog (deleteFile fp
|
$ withErrorDialog (deleteFile fp
|
||||||
@ -155,9 +151,8 @@ del row mygui myview = do
|
|||||||
-- * 'operationBuffer' writes
|
-- * 'operationBuffer' writes
|
||||||
-- * 'fsState' reads
|
-- * 'fsState' reads
|
||||||
copyInit :: Row -> MyGUI -> MyView -> IO ()
|
copyInit :: Row -> MyGUI -> MyView -> IO ()
|
||||||
copyInit row mygui myview = do
|
copyInit row mygui myview =
|
||||||
fsState <- readTVarIO $ fsState myview
|
writeTVarIO (operationBuffer myview) (FCopy . CP1 $ fullPath row)
|
||||||
writeTVarIO (operationBuffer myview) (FCopy . CP1 $ subDirName fsState row)
|
|
||||||
|
|
||||||
|
|
||||||
-- |Finalizes a file copy operation.
|
-- |Finalizes a file copy operation.
|
||||||
@ -171,7 +166,7 @@ copyFinal mygui myview = do
|
|||||||
op <- readTVarIO (operationBuffer myview)
|
op <- readTVarIO (operationBuffer myview)
|
||||||
case op of
|
case op of
|
||||||
FCopy (CP1 source) -> do
|
FCopy (CP1 source) -> do
|
||||||
dest <- anchor <$> readTVarIO (fsState myview)
|
dest <- fullPath <$> readTVarIO (fsState myview)
|
||||||
isFile <- doesFileExist source
|
isFile <- doesFileExist source
|
||||||
let cmsg = "Really copy file \"" ++ source
|
let cmsg = "Really copy file \"" ++ source
|
||||||
++ "\"" ++ " to \"" ++ dest ++ "\"?"
|
++ "\"" ++ " to \"" ++ dest ++ "\"?"
|
||||||
@ -194,5 +189,5 @@ upDir mygui myview = do
|
|||||||
rawModel' <- readTVarIO $ rawModel myview
|
rawModel' <- readTVarIO $ rawModel myview
|
||||||
sortedModel' <- readTVarIO $ sortedModel myview
|
sortedModel' <- readTVarIO $ sortedModel myview
|
||||||
fS <- readTVarIO $ fsState myview
|
fS <- readTVarIO $ fsState myview
|
||||||
newP <- readPath (baseDir . anchor $ fS)
|
nv <- goUp fS
|
||||||
refreshTreeView' mygui myview newP
|
refreshTreeView' mygui myview nv
|
||||||
|
@ -56,7 +56,7 @@ data FMSettings = MkFMSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type Row = Int
|
type Row = AnchoredFile FileInfo FileInfo
|
||||||
|
|
||||||
|
|
||||||
-- |This describes the contents of the treeView and is separated from MyGUI,
|
-- |This describes the contents of the treeView and is separated from MyGUI,
|
||||||
@ -65,7 +65,7 @@ data MyView = MkMyView {
|
|||||||
rawModel :: TVar (ListStore Row)
|
rawModel :: TVar (ListStore Row)
|
||||||
, sortedModel :: TVar (TypedTreeModelSort Row)
|
, sortedModel :: TVar (TypedTreeModelSort Row)
|
||||||
, filteredModel :: TVar (TypedTreeModelFilter Row)
|
, filteredModel :: TVar (TypedTreeModelFilter Row)
|
||||||
, fsState :: TVar (AnchoredDirFile FileInfo FileInfo)
|
, fsState :: TVar (AnchoredFile FileInfo FileInfo)
|
||||||
, operationBuffer :: TVar FileOperation
|
, operationBuffer :: TVar FileOperation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,6 +63,7 @@ import GUI.Gtk.Utils
|
|||||||
import IO.Error
|
import IO.Error
|
||||||
import IO.File
|
import IO.File
|
||||||
import IO.Utils
|
import IO.Utils
|
||||||
|
import MyPrelude
|
||||||
import System.Directory
|
import System.Directory
|
||||||
(
|
(
|
||||||
executable
|
executable
|
||||||
@ -92,8 +93,6 @@ import System.Process
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
import Data.IntMap.Lazy (IntMap)
|
|
||||||
import qualified Data.IntMap.Lazy as IM
|
|
||||||
|
|
||||||
|
|
||||||
-- TODO: simplify where we modify the TVars
|
-- TODO: simplify where we modify the TVars
|
||||||
@ -130,7 +129,7 @@ startMainWindow startdir = do
|
|||||||
filePix <- getIcon IFile 24
|
filePix <- getIcon IFile 24
|
||||||
errorPix <- getIcon IError 24
|
errorPix <- getIcon IError 24
|
||||||
|
|
||||||
fsState <- readPath startdir >>= newTVarIO
|
fsState <- Data.DirTree.readFile startdir >>= newTVarIO
|
||||||
|
|
||||||
operationBuffer <- newTVarIO None
|
operationBuffer <- newTVarIO None
|
||||||
|
|
||||||
@ -162,7 +161,8 @@ startMainWindow startdir = do
|
|||||||
"statusBar"
|
"statusBar"
|
||||||
|
|
||||||
-- create initial list store model with unsorted data
|
-- create initial list store model with unsorted data
|
||||||
rawModel <- newTVarIO =<< listStoreNew . IM.keys . dirTree
|
rawModel <- newTVarIO =<< listStoreNew
|
||||||
|
=<< Data.DirTree.getContents
|
||||||
=<< readTVarIO fsState
|
=<< readTVarIO fsState
|
||||||
|
|
||||||
filteredModel <- newTVarIO =<< (\x -> treeModelFilterNew x [])
|
filteredModel <- newTVarIO =<< (\x -> treeModelFilterNew x [])
|
||||||
|
@ -34,10 +34,9 @@ import Graphics.UI.Gtk
|
|||||||
import GUI.Gtk.Data
|
import GUI.Gtk.Data
|
||||||
import IO.Error
|
import IO.Error
|
||||||
import IO.Utils
|
import IO.Utils
|
||||||
|
import MyPrelude
|
||||||
|
|
||||||
|
|
||||||
import qualified Data.IntMap.Lazy as IM
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-----------------
|
-----------------
|
||||||
@ -89,12 +88,13 @@ withRow mygui myview io = do
|
|||||||
-- Interaction with mutable references:
|
-- Interaction with mutable references:
|
||||||
--
|
--
|
||||||
-- * 'fsState' writes
|
-- * 'fsState' writes
|
||||||
fileListStore :: AnchoredDirFile FileInfo FileInfo -- ^ current dir
|
fileListStore :: AnchoredFile FileInfo FileInfo -- ^ current dir
|
||||||
-> MyView
|
-> MyView
|
||||||
-> IO (ListStore Row)
|
-> IO (ListStore Row)
|
||||||
fileListStore dt myview = do
|
fileListStore dt myview = do
|
||||||
writeTVarIO (fsState myview) dt
|
writeTVarIO (fsState myview) dt
|
||||||
listStoreNew (IM.keys . dirTree $ dt)
|
cs <- Data.DirTree.getContents dt
|
||||||
|
listStoreNew cs
|
||||||
|
|
||||||
|
|
||||||
-- |Re-reads the current directory or the given one and updates the TreeView.
|
-- |Re-reads the current directory or the given one and updates the TreeView.
|
||||||
@ -114,13 +114,13 @@ refreshTreeView :: MyGUI
|
|||||||
-> IO ()
|
-> IO ()
|
||||||
refreshTreeView mygui myview mfp = do
|
refreshTreeView mygui myview mfp = do
|
||||||
fsState <- readTVarIO $ fsState myview
|
fsState <- readTVarIO $ fsState myview
|
||||||
let cfp = anchor fsState
|
let cfp = fullPath fsState
|
||||||
fp = fromMaybe cfp mfp
|
fp = fromMaybe cfp mfp
|
||||||
|
|
||||||
-- TODO catch exceptions
|
-- TODO catch exceptions
|
||||||
dirSanityThrow fp
|
dirSanityThrow fp
|
||||||
|
|
||||||
newFsState <- readPath fp
|
newFsState <- Data.DirTree.readFile fp
|
||||||
newRawModel <- fileListStore newFsState myview
|
newRawModel <- fileListStore newFsState myview
|
||||||
writeTVarIO (rawModel myview) newRawModel
|
writeTVarIO (rawModel myview) newRawModel
|
||||||
constructTreeView mygui myview
|
constructTreeView mygui myview
|
||||||
@ -133,7 +133,7 @@ refreshTreeView mygui myview mfp = do
|
|||||||
-- * 'rawModel' writes
|
-- * 'rawModel' writes
|
||||||
refreshTreeView' :: MyGUI
|
refreshTreeView' :: MyGUI
|
||||||
-> MyView
|
-> MyView
|
||||||
-> AnchoredDirFile FileInfo FileInfo
|
-> AnchoredFile FileInfo FileInfo
|
||||||
-> IO ()
|
-> IO ()
|
||||||
refreshTreeView' mygui myview dt = do
|
refreshTreeView' mygui myview dt = do
|
||||||
newRawModel <- fileListStore dt myview
|
newRawModel <- fileListStore dt myview
|
||||||
@ -163,10 +163,9 @@ constructTreeView mygui myview = do
|
|||||||
render' = renderTxt mygui
|
render' = renderTxt mygui
|
||||||
|
|
||||||
fsState <- readTVarIO $ fsState myview
|
fsState <- readTVarIO $ fsState myview
|
||||||
let dirL = dirLookup fsState
|
|
||||||
|
|
||||||
-- update urlBar, this will break laziness slightly, probably
|
-- update urlBar, this will break laziness slightly, probably
|
||||||
let urlpath = anchor fsState
|
let urlpath = fullPath fsState
|
||||||
entrySetText (urlBar mygui) urlpath
|
entrySetText (urlBar mygui) urlpath
|
||||||
|
|
||||||
rawModel' <- readTVarIO $ rawModel myview
|
rawModel' <- readTVarIO $ rawModel myview
|
||||||
@ -176,7 +175,7 @@ constructTreeView mygui myview = do
|
|||||||
writeTVarIO (filteredModel myview) filteredModel'
|
writeTVarIO (filteredModel myview) filteredModel'
|
||||||
treeModelFilterSetVisibleFunc filteredModel' $ \iter -> do
|
treeModelFilterSetVisibleFunc filteredModel' $ \iter -> do
|
||||||
hidden <- showHidden <$> readTVarIO (settings mygui)
|
hidden <- showHidden <$> readTVarIO (settings mygui)
|
||||||
row <- (name . dirL) <$> treeModelGetRow rawModel' iter
|
row <- (name . file) <$> treeModelGetRow rawModel' iter
|
||||||
if hidden
|
if hidden
|
||||||
then return True
|
then return True
|
||||||
else return $ not ("." `isPrefixOf` row)
|
else return $ not ("." `isPrefixOf` row)
|
||||||
@ -187,20 +186,20 @@ constructTreeView mygui myview = do
|
|||||||
treeSortableSetSortFunc sortedModel' 1 $ \iter1 iter2 -> do
|
treeSortableSetSortFunc sortedModel' 1 $ \iter1 iter2 -> do
|
||||||
cIter1 <- treeModelFilterConvertIterToChildIter filteredModel' iter1
|
cIter1 <- treeModelFilterConvertIterToChildIter filteredModel' iter1
|
||||||
cIter2 <- treeModelFilterConvertIterToChildIter filteredModel' iter2
|
cIter2 <- treeModelFilterConvertIterToChildIter filteredModel' iter2
|
||||||
row1 <- dirL <$> treeModelGetRow rawModel' cIter1
|
row1 <- treeModelGetRow rawModel' cIter1
|
||||||
row2 <- dirL <$> treeModelGetRow rawModel' cIter2
|
row2 <- treeModelGetRow rawModel' cIter2
|
||||||
return $ compare row1 row2
|
return $ compare row1 row2
|
||||||
treeSortableSetSortColumnId sortedModel' 1 SortAscending
|
treeSortableSetSortColumnId sortedModel' 1 SortAscending
|
||||||
|
|
||||||
-- set values
|
-- set values
|
||||||
treeModelSetColumn rawModel' (makeColumnIdPixbuf 0)
|
treeModelSetColumn rawModel' (makeColumnIdPixbuf 0)
|
||||||
(dirtreePix . dirL)
|
(dirtreePix . file)
|
||||||
treeModelSetColumn rawModel' (makeColumnIdString 1)
|
treeModelSetColumn rawModel' (makeColumnIdString 1)
|
||||||
(name . dirL)
|
(name . file)
|
||||||
treeModelSetColumn rawModel' (makeColumnIdString 2)
|
treeModelSetColumn rawModel' (makeColumnIdString 2)
|
||||||
(packModTime . dirL)
|
(packModTime . file)
|
||||||
treeModelSetColumn rawModel' (makeColumnIdString 3)
|
treeModelSetColumn rawModel' (makeColumnIdString 3)
|
||||||
(packPermissions . dirL)
|
(packPermissions . file)
|
||||||
|
|
||||||
-- update treeview model
|
-- update treeview model
|
||||||
treeViewSetModel treeView' sortedModel'
|
treeViewSetModel treeView' sortedModel'
|
||||||
@ -208,7 +207,7 @@ constructTreeView mygui myview = do
|
|||||||
return ()
|
return ()
|
||||||
where
|
where
|
||||||
dirtreePix (Dir {}) = folderPix mygui
|
dirtreePix (Dir {}) = folderPix mygui
|
||||||
dirtreePix (File {}) = filePix mygui
|
dirtreePix (RegFile {}) = filePix mygui
|
||||||
dirtreePix (Failed {}) = errorPix mygui
|
dirtreePix (Failed {}) = errorPix mygui
|
||||||
|
|
||||||
|
|
||||||
|
@ -67,6 +67,7 @@ import qualified System.Posix.Files as PF
|
|||||||
|
|
||||||
-- TODO: modify the DTZipper directly after file operations!?
|
-- TODO: modify the DTZipper directly after file operations!?
|
||||||
-- TODO: file operations should be threaded and not block the UI
|
-- TODO: file operations should be threaded and not block the UI
|
||||||
|
-- TODO: canonicalize paths?
|
||||||
|
|
||||||
|
|
||||||
-- |Data type describing an actual file operation that can be
|
-- |Data type describing an actual file operation that can be
|
||||||
|
9
src/MyPrelude.hs
Normal file
9
src/MyPrelude.hs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
module MyPrelude where
|
||||||
|
|
||||||
|
|
||||||
|
import Data.List
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
listIndices :: [a] -> [Int]
|
||||||
|
listIndices = findIndices (const True)
|
Loading…
Reference in New Issue
Block a user