I don't know how to do it with iteratees, but here is the pipes
-based solution. Some advantages of this version of the solution are:
It will never bring more than one bytestring chunk into memory at a time
It streams the list of files so it won't choke on a directory with a large number of immediate children
It recursively traverses the directory tree, like you asked
Some of this stuff will be in pipes
utility libraries very soon:
import Control.Monad (when, unless)
import Control.Proxy
import Control.Proxy.Safe hiding (readFileS)
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as B8
import System.Directory (readable, getPermissions, doesDirectoryExist)
import System.FilePath ((</>), takeFileName)
import System.Posix (openDirStream, readDirStream, closeDirStream)
import System.IO (openFile, hClose, IOMode(ReadMode), hIsEOF)
contents
:: (CheckP p)
=> FilePath -> () -> Producer (ExceptionP p) FilePath SafeIO ()
contents path () = do
canRead <- tryIO $ fmap readable $ getPermissions path
when canRead $ bracket id (openDirStream path) closeDirStream $ \dirp -> do
let loop = do
file <- tryIO $ readDirStream dirp
case file of
[] -> return ()
_ -> do
respond (path </> file)
loop
loop
contentsRecursive
:: (CheckP p)
=> FilePath -> () -> Producer (ExceptionP p) FilePath SafeIO ()
contentsRecursive path () = loop path
where
loop path = do
contents path () //> \newPath -> do
respond newPath
isDir <- tryIO $ doesDirectoryExist newPath
let isChild = not $ takeFileName newPath `elem` [".", ".."]
when (isDir && isChild) $ loop newPath
readFileS
:: (CheckP p)
=> Int -> FilePath -> () -> Producer (ExceptionP p) B.ByteString SafeIO ()
readFileS chunkSize path () =
bracket id (openFile path ReadMode) hClose $ \handle -> do
let loop = do
eof <- tryIO $ hIsEOF handle
unless eof $ do
bs <- tryIO $ B.hGetSome handle chunkSize
respond bs
loop
loop
firstLine :: (Proxy p) => () -> Consumer p B.ByteString IO ()
firstLine () = runIdentityP loop
where
loop = do
bs <- request ()
let (prefix, suffix) = B8.span (/= '\n') bs
lift $ B8.putStr prefix
if (B.null suffix) then loop else lift $ B8.putStr (B8.pack "\n")
handler :: (CheckP p) => FilePath -> Session (ExceptionP p) SafeIO ()
handler path = do
canRead <- tryIO $ fmap readable $ getPermissions path
isDir <- tryIO $ doesDirectoryExist path
when (not isDir && canRead) $
(readFileS 1024 path >-> try . firstLine) ()
main = runSafeIO $ runProxy $ runEitherK $
contentsRecursive "/home" />/ handler
If you want to learn more about pipes
, you can begin with the tutorial.