{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE UnicodeSyntax #-} -- This module represents an abstraction of a file tree. The motivating use case -- is abstracting the difference between a real file system tree, rooted in some -- directory, and the contents of a zip archive. Where possible, the methods -- associated with a FileCollection mimic those provided by -- directory:System.Directory, but take an extra leading parameter denoting -- the root to work from module Archive.FileCollection ( ) where import qualified System.Directory as SD import Codec.Archive.Zip import Data.Monoid((<>)) import qualified Data.List as L import Data.Maybe(mapMaybe,listToMaybe) class FileCollection d where createDirectory :: d → FilePath → IO d createDirectoryIfMissing :: d → FilePath → IO d removeDirectory :: d → FilePath → IO d removeDirectoryRecursive :: d → FilePath → IO d renameDirectory :: d → FilePath → FilePath → IO d getDirectoryContents :: d → FilePath → IO [FilePath] removeFile :: d → FilePath → IO d renameFile :: d → FilePath → FilePath → IO d copyFile :: d → FilePath → FilePath → IO d -- These don't work recursively findFile :: d → [FilePath] → String → IO (Maybe FilePath) findFiles :: d → [FilePath] → String → IO [FilePath] doesFileExist :: d → FilePath → IO Bool doesDirectoryExist :: d → FilePath → IO Bool combine :: FilePath → FilePath → FilePath combine root relative = root <> ('/':relative) combineRunReturn action root rel = do let full = combine root rel _ ← action full return full combineRunReturn2 action root old new = do let fullOld = combine root old fullNew = combine root new _ ← action fullOld fullNew return fullNew -- This represents native files instance FileCollection [Char] where createDirectory = combineRunReturn SD.createDirectory createDirectoryIfMissing = combineRunReturn (SD.createDirectoryIfMissing True) removeDirectory = combineRunReturn SD.removeDirectory removeDirectoryRecursive = combineRunReturn SD.removeDirectoryRecursive renameDirectory = combineRunReturn2 SD.renameDirectory getDirectoryContents root rel = SD.getDirectoryContents $ combine root rel removeFile = combineRunReturn SD.removeFile renameFile = combineRunReturn2 SD.renameFile copyFile = combineRunReturn2 SD.copyFile findFile root subs = SD.findFile (map (combine root) subs) findFiles root subs = SD.findFiles (map (combine root) subs) doesFileExist root rel = SD.doesFileExist $ combine root rel doesDirectoryExist root rel = SD.doesDirectoryExist $ combine root rel -- Returns all paths located beneath root in the heirarchy subFiles arch path = filter (L.isPrefixOf path) $ map eRelativePath $ zEntries arch getDirectoryContents' arch path = map (drop l) $ subFiles arch path where l = length path instance FileCollection Archive where -- Creating a directory doesn't really mean anything in a zip archive, since -- every entry has its own path fully specified createDirectory a = return . const a createDirectoryIfMissing a = return . const a removeDirectory a = return . const a removeDirectoryRecursive arch path = return $ foldr deleteEntryFromArchive arch $ subFiles arch path renameDirectory arch oldPath newPath = return $ foldr addEntryToArchive (foldr deleteEntryFromArchive arch oldNames) newEntries where oldNames = subFiles arch oldPath oldEntries = mapMaybe (`findEntryByPath` arch) oldNames newEntries = map rename oldEntries rename e@Entry{ eRelativePath = rp } = e { eRelativePath = newPath <> drop l rp } l = length oldPath getDirectoryContents arch path = return $ getDirectoryContents' arch path removeFile arch path = return $ deleteEntryFromArchive path arch renameFile arch old new | old == new = return arch | otherwise = copyFile arch old new >>= flip removeFile old copyFile arch old new | old == new = return arch | otherwise = case newEntry of (Just e) → return $ addEntryToArchive e arch Nothing → return arch where oldEntry = findEntryByPath old arch newEntry = changeName new <$> oldEntry changeName new e = e { eRelativePath = new } findFile arch subs target = listToMaybe <$> findFiles arch subs target findFiles arch subs target = return $ map (<>target) $ filter (elem target . getDirectoryContents' arch) subs doesFileExist arch path= case findEntryByPath path arch of Just e → if null $ subFiles arch $ eRelativePath e then return True else return False Nothing → return False doesDirectoryExist arch path= case findEntryByPath path arch of Just e → if null $ subFiles arch $ eRelativePath e then return False else return True Nothing → return False