module Main (main) where import Control.Exception import Control.Monad import Data.List import Data.Monoid import System.Console.GetOpt import System.Environment import System.Exit import System.Mem import Keys import FindFiles data Options = Opt {optRecursive :: !Bool ,optDryRun :: !Bool ,optVerbose :: !Bool ,optDebug :: !Bool} deriving (Show) options :: [OptDescr (Options -> Options)] options = [ Option ['v'] ["verbose"] (NoArg $ \opt -> opt {optVerbose = True}) "show which actions are being taken", Option ['p'] ["dry-run", "pretend"] (NoArg $ \opt -> opt {optDryRun = True}) "do not change files, just show the actions", Option ['r','R'] ["recursive"] (NoArg $ \opt -> opt {optRecursive = True}) "recurse into subdirectories as well", Option ['d'] ["debug"] (NoArg $ \opt -> opt {optDebug = True}) "show a lot of debug information"] noOpts :: Options noOpts = Opt False False False False getOpts :: IO (Options, [String]) getOpts = do args <- getArgs case getOpt RequireOrder options args of (opts, dirs@(_:_), []) -> return (foldl' (flip id) noOpts opts, dirs) (_, dirs, errs) -> putStrLn (usage dirs errs) >> exitFailure usage :: [FilePath] -> [String] -> String usage dirs errs = (concat $ dirErrs ++ errs) ++ '\n' : usageInfo header options where dirErrs | null dirs = ["no directories given\n"] | otherwise = [] header = "Usage: make-hard-links [OPTIONS] DIR1 [DIR2 ...]" verbose, debug :: Options -> IO () -> IO () verbose opts = when (optDebug opts || optVerbose opts) debug opts = when (optDebug opts) main :: IO () main = do (opts, dirs) <- getOpts when (optDryRun opts && not (optVerbose opts || optDebug opts)) $ putStrLn $ "WARNING: Requesting --dry-run without --verbose " ++ "or --debug is useless!" (potential, files) <- step1 opts dirs potential `seq` files `seq` performGC step2 opts potential files -- 1) Potential copies step1 :: Options -> [FilePath] -> IO ([[InodeKey]], InodeKey -> [FilePath]) step1 opts dirs = do verbose opts $ putStrLn "Finding potential copies..." (pot, files) <- (potentialCopies . mconcat) `fmap` mapM (getDirectoryFiles $ optRecursive opts) dirs debug opts $ do putStrLn "Potential copies found:" if null pot then putStrLn "NONE!" else mapM_ print $ map (map files) pot when (null pot) $ do verbose opts $ putStrLn "Nothing to do, exiting..." exitSuccess return (pot, files) -- 2) Real copies step2 :: Options -> [[InodeKey]] -> (InodeKey -> [FilePath]) -> IO () step2 opts potential files = do verbose opts $ putStrLn "Comparing files and creating hard links..." let test inodes = do debug opts $ putStrLn $ "Testing " ++ show (map files inodes) let path inode = (head $ files inode, inode) cps <- findRealCopies $ map path inodes let cps' = filter (not . null . tail) cps debug opts $ putStrLn $ "...found " ++ show (map (concatMap files) cps') return cps' let potLength = length potential forM_ (zip [(1::Int)..] potential) $ \(i,p) -> do verbose opts $ putStrLn $ show i ++ "/" ++ show potLength ++ "..." handle (\(exc::SomeException) -> print exc) $ test p >>= mapM_ (makeHardLinks (optVerbose opts || optDebug opts) (optDryRun opts) files)