{-# LANGUAGE CPP #-} -- | -- Module: Filesystem.Enumerator -- Copyright: 2011 John Millikin -- 2011 Michael Snoyman -- License: MIT -- -- Maintainer: jmillikin@gmail.com -- Portability: portable -- -- Enumerator-based API for manipulating the filesystem. module Filesystem.Enumerator ( traverse , enumDirectory ) where import Prelude hiding (FilePath) import Control.Exception (finally) import Control.Monad.IO.Class (MonadIO) import Data.Enumerator import Filesystem import Filesystem.Path (()) import Filesystem.Path.CurrentOS (FilePath, encodeString, decodeString) #ifdef CABAL_OS_WINDOWS import qualified System.Win32 as Win32 #else import qualified System.Posix as Posix #endif -- | Starting at some root directory, traverse the filesystem and enumerate -- every file (or symlink to a file) found. -- -- Note: the option of whether to follow symlinks is currently only checked -- on POSIX platforms, as the @Win32@ package does not support querying -- symlink status. On Windows, symlinks will always be followed. traverse :: MonadIO m => Bool -- ^ Follow directory symlinks (only used on POSIX platforms) -> FilePath -- ^ Root directory -> Enumerator FilePath m a traverse followSymlinks root s = tryIO (listDirectory root) >>= (\ps -> loop ps s) where loop (p:ps) step@(Continue k) = do isFile' <- tryIO (isFile p) if isFile' then k (Chunks [p]) >>== loop ps else do follow' <- tryIO (follow p) if follow' then traverse followSymlinks p step >>== loop ps else loop ps step loop _ step = returnI step follow :: FilePath -> IO Bool #ifdef CABAL_OS_WINDOWS follow = isDirectory #else follow p = do let path = encodeString p stat <- if followSymlinks then Posix.getFileStatus path else Posix.getSymbolicLinkStatus path return (Posix.isDirectory stat) #endif -- | Enumerate entries in a directory. Entries are returned with their full -- path. Entries are read from the directory handle as needed, so this is safe -- to use with very large directories. enumDirectory :: FilePath -> Enumerator FilePath IO a enumDirectory root step = do let boring str = str == "." || str == ".." #ifdef CABAL_OS_WINDOWS let search = root decodeString "*" (h, findData) <- tryIO (Win32.findFirstFile (encodeString search)) let close = Win32.findClose h let iter = checkContinue0 $ \loop k -> do raw <- tryIO (Win32.getFindDataFileName findData) let path = root decodeString raw let checkNext s = do hasNext <- tryIO (Win32.findNextFile h findData) if hasNext then loop s else returnI s if boring raw then checkNext (Continue k) else k (Chunks [path]) >>== checkNext #else dir <- tryIO (Posix.openDirStream (encodeString root)) let close = Posix.closeDirStream dir let iter = checkContinue0 $ \loop k -> do raw <- tryIO (Posix.readDirStream dir) let path = root decodeString raw case raw of "" -> continue k _ | boring raw -> loop (Continue k) _ -> k (Chunks [path]) >>== loop #endif Iteratee (finally (runIteratee (iter step)) close)