{-# LANGUAGE NoImplicitPrelude #-} import BasicPrelude import SimpleCmdArgs import qualified Data.ByteString.Char8 as B import qualified Data.Text as T import Network.HTTP.Directory import System.Directory import System.FilePath import System.FilePath.Glob (compile, match) import Paths_findhttp (version) data FileType = TypeFile | TypeDir | TypeSymlink deriving Eq main :: IO () main = simpleCmdArgs (Just version) "find for http" "Find files from an http \"directory\"" $ listFiles <$> depthOpt <*> optional filetypeOpt <*> optional nameOpt <*> strArg "URL/DIR" where nameOpt :: Parser String nameOpt = strOptionWith 'n' "name" "GLOB" "Limit files to glob matches" filetypeOpt :: Parser FileType filetypeOpt = flagWith' TypeFile 'f' "files" "List files only" <|> flagWith' TypeDir 'd' "dirs" "List directories only" <|> flagWith' TypeSymlink 's' "symlinks" "List symlinks only (not http)" depthOpt :: Parser Int depthOpt = optionalWith auto 'm' "maxdepth" "DEPTH" "Maximum search depth (default 10)" 10 listFiles :: Int -> Maybe FileType -> Maybe String -> String -> IO () listFiles maxdepth mfiletype mname dir = if isHttpUrl dir then do mgr <- httpManager findHttp mgr maxdepth mfiletype Nothing mname $ makeDir dir else findDir maxdepth mfiletype mname dir findDir :: Int -> Maybe FileType -> Maybe String -> String -> IO () findDir n _ _ _ | n <= 0 = return () findDir maxdepth mfiletype mname dir = do fs <- sort <$> listDirectory dir mapM_ display fs where display :: String -> IO () display f = do let file = dir f isdir <- doesDirectoryExist file if isdir then do when (fileType TypeDir mfiletype && glob f) $ putStrLn $ T.pack $ addTrailingPathSeparator file findDir (maxdepth-1) mfiletype mname file else do symlink <- pathIsSymbolicLink file if symlink then do tgt <- getSymbolicLinkTarget file when (fileType TypeSymlink mfiletype && glob f) $ putStrLn $ T.pack $ file <> " -> " <> tgt else when (fileType TypeFile mfiletype && glob f) $ putStrLn $ T.pack file glob = maybe (const True) (match . compile) mname fileType :: FileType -> Maybe FileType -> Bool fileType ftype = maybe True (== ftype) findHttp :: Manager -> Int -> Maybe FileType -> Maybe Text -> Maybe String -> String -> IO () findHttp _ n _ _ _ _ | n <= 0 = return () findHttp mgr maxdepth mfiletype mprefix mname url = do fs <- sort <$> httpDirectory mgr url mapM_ (display . T.unpack) fs where display :: String -> IO () display f = do -- optimisation: assume dirs don't contain '.' filetype <- if '.' `elem` f then return TypeFile else if last f == '/' then return TypeDir else httpFileType mgr $ url f if filetype == TypeDir then do let dirname = dropTrailingPathSeparator f when (fileType TypeDir mfiletype && glob dirname) $ putStrLn $ prefix f let dir = addTrailingPathSeparator f findHttp mgr (maxdepth-1) mfiletype (mprefix <> Just (T.pack dir)) mname $ url dir else when (fileType TypeFile mfiletype && glob f) $ putStrLn $ prefix f glob = maybe (const True) (match . compile) mname prefix = (fromMaybe "" mprefix <>) . T.pack makeDir :: String -> String makeDir path = if last path == '/' then path else path <> "/" httpFileType :: Manager -> String -> IO FileType httpFileType mgr url = do mredirect <- httpRedirect mgr $ dropTrailingPathSeparator url return $ case mredirect of Nothing -> TypeFile Just uri | uri == B.pack (addTrailingPathSeparator url) -> TypeDir | otherwise -> TypeSymlink