aboutsummaryrefslogtreecommitdiff
path: root/ch09/9_d_2.hs
diff options
context:
space:
mode:
authorJan Sucan <jan@jansucan.com>2023-11-14 13:14:43 +0100
committerJan Sucan <jan@jansucan.com>2023-11-14 20:12:31 +0100
commit0f88d03b4aa4a11df5926e771526c9c05d398c9f (patch)
tree9d1330bd9fcfbf169d9caee707effd4dd4dd0285 /ch09/9_d_2.hs
parentb69c0554aec47443fb126de32ccb75b5d8c47d3e (diff)
9_d_2: Add solution
Diffstat (limited to 'ch09/9_d_2.hs')
-rw-r--r--ch09/9_d_2.hs139
1 files changed, 139 insertions, 0 deletions
diff --git a/ch09/9_d_2.hs b/ch09/9_d_2.hs
new file mode 100644
index 0000000..07deb9e
--- /dev/null
+++ b/ch09/9_d_2.hs
@@ -0,0 +1,139 @@
+-- Add the ability to find out who owns a directory entry to your code. Make
+-- this information available to predicates.
+
+-- For simpler implementation, numeric user IDs are used instead of user names.
+
+{-- From examples/examples/ch09/FoldDir.hs modified according to the assignment
+ --
+ -- Added missing import of takeBaseName.
+ -- Fixed appending correct root path to directory entries when constructing
+ -- their full paths.
+ --}
+import Control.Exception
+import Control.Monad (liftM)
+import Data.Char (toLower)
+
+import System.FilePath ((</>), takeFileName, takeExtension, takeBaseName)
+import System.Posix.Directory (DirStream, openDirStream, readDirStream, closeDirStream)
+import System.Posix.Types (EpochTime, FileOffset, FileMode, UserID)
+import System.Posix.Files (getFileStatus, fileSize, modificationTime, fileMode, isDirectory,
+ fileOwner)
+
+
+data Iterate seed = Done { unwrap :: seed }
+ | Skip { unwrap :: seed }
+ | Continue { unwrap :: seed }
+ deriving (Show)
+
+type Iterator seed = seed -> Info -> Iterate seed
+
+
+foldTree :: Iterator a -> a -> FilePath -> IO a
+
+foldTree iter initSeed path = do
+ endSeed <- fold initSeed path
+ return (unwrap endSeed)
+ where
+ fold seed subpath = getUsefulContents subpath >>= walk seed subpath
+
+ walk seed root (name:names) = do
+ let path' = root </> name
+ info <- getInfo path'
+ case iter seed info of
+ done@(Done _) -> return done
+ Skip seed' -> walk seed' root names
+ Continue seed'
+ | infoIsDirectory info -> do
+ next <- fold seed' path'
+ case next of
+ done@(Done _) -> return done
+ seed'' -> walk (unwrap seed'') root names
+ | otherwise -> walk seed' root names
+ walk seed _ _ = return (Continue seed)
+{-- End of code from examples --}
+
+
+{-- From examples/examples/ch09/ControlledVisit.hs modified according to the assignment --}
+data Info = Info {
+ infoPath :: FilePath
+ , infoIsDirectory :: Bool
+ , infoOwner :: UserID
+ , infoPerms :: Maybe FileMode
+ , infoSize :: Maybe FileOffset
+ , infoModTime :: Maybe EpochTime
+ } deriving (Eq, Ord, Show)
+
+getInfo :: FilePath -> IO Info
+
+maybeIO :: IO a -> IO (Maybe a)
+maybeIO act = handle (\(SomeException _) -> return Nothing) (Just `liftM` act)
+
+getInfo path = do
+ fileStatus <- getFileStatus path
+ let isDir = isDirectory fileStatus
+ let owner = fileOwner fileStatus
+ perms <- maybeIO (return (fileMode fileStatus))
+ size <- maybeIO (return (fileSize fileStatus))
+ modified <- maybeIO (return (modificationTime fileStatus))
+ return (Info path isDir owner perms size modified)
+
+
+getDirContents :: FilePath -> IO [String]
+getDirContents path = do
+ entries <- bracket (openDirStream path) closeDirStream (readStream [])
+ return entries
+ where
+ readStream :: [String] -> DirStream -> IO [String]
+ readStream entries ds = do
+ e <- readDirStream ds
+ if e == ""
+ then return entries
+ else readStream (entries ++ [e]) ds
+
+
+getUsefulContents :: FilePath -> IO [String]
+getUsefulContents path = do
+ names <- handle (\(SomeException _) -> return []) (getDirContents path)
+ return (filter (`notElem` [".", ".."]) names)
+{-- End of code from examples --}
+
+
+filesOwnedBy :: UserID -> Iterator [FilePath]
+filesOwnedBy userId paths info
+ | owner == userId
+ = Continue (path : paths)
+ | otherwise
+ = Continue paths
+ where owner = infoOwner info
+ path = infoPath info
+
+
+-- Contents of the test directory containing files owned by users jan (user ID
+-- 1000), root (user ID 0), and nobody (user ID 99). The directories are owned
+-- by 'jan':
+--
+-- test-9_d_2/
+-- test-9_d_2/dirA
+-- test-9_d_2/dirA/root
+-- test-9_d_2/dirA/jan
+-- test-9_d_2/dirA/root2
+-- test-9_d_2/dirB
+-- test-9_d_2/dirB/nobody2
+-- test-9_d_2/dirB/root
+-- test-9_d_2/dirB/nobody
+-- test-9_d_2/root
+-- test-9_d_2/jan
+-- test-9_d_2/nobody
+
+-- ghci> :l 9_d_2.hs
+-- [1 of 1] Compiling Main ( 9_d_2.hs, interpreted )
+-- Ok, one module loaded.
+
+-- ghci> foldTree (filesOwnedBy 1000) [] "test-9_d_2"
+-- ["test-9_d_2/jan","test-9_d_2/dirB","test-9_d_2/dirA/jan","test-9_d_2/dirA"]
+
+-- ghci> foldTree (filesOwnedBy 0) [] "test-9_d_2"
+-- ["test-9_d_2/root","test-9_d_2/dirB/root","test-9_d_2/dirA/root2","test-9_d_2/dirA/root"]
+
+-- ghci> foldTree (filesOwnedBy 99) [] "test-9_d_2"
+-- ["test-9_d_2/nobody","test-9_d_2/dirB/nobody","test-9_d_2/dirB/nobody2"]