fdo-trash-0.0.0.0: fdo-trash.hs
import System.Environment(getArgs,getProgName)
import System.Console.GetOpt(getOpt,ArgOrder(..),OptDescr(..),ArgDescr(..),usageInfo)
import System.FilePath.Posix((</>),(<.>),isAbsolute,takeFileName,dropTrailingPathSeparator)
import Data.Time(getCurrentTime,diffUTCTime,addUTCTime)
import Data.List(intercalate)
import Freedesktop.Trash(TrashFile(..),trashGetOrphans,getTrashPaths,trashGetFiles,trashRestore,expungeTrash,moveToTrash)
import Control.Monad(when)
import System.Exit(exitSuccess)
import Paths_fdo_trash(version)
import Data.Version(showVersion)
import System.Directory(createDirectoryIfMissing,canonicalizePath)
import Text.Parsec(parse,many1,(<|>),char,oneOf,eof,option,digit,ParseError)
minSecs = 60
hourSecs = 60 *minSecs
daySecs = 24 *hourSecs
monthSecs = 30 *daySecs
yearSecs = 365*daySecs
timeOffsetString = do
sign <- (char '-' >> return (-1))
<|> (option 1 (char '+' >> return 1))
num <- option 1 (fmap read $ many1 digit)
mult <- option 'd' (oneOf "SMHdmy")
let multNum = case mult of
'S' -> 1
'M' -> minSecs
'H' -> hourSecs
'd' -> daySecs
'm' -> monthSecs
'y' -> yearSecs
_ -> undefined
eof
return $ sign*num*multNum
printVersion = fmap (++ '-' : showVersion version) getProgName >>= putStrLn >> exitSuccess
castFloat :: (Real a, Fractional b) => a -> b
castFloat = fromRational.toRational
actions =
[ ("purge", fdoPurge)
, ("rm", fdoRm)
, ("unrm", fdoUnRm)
]
--compilerOpts :: [String] -> IO (Options, [String])
parseOpts defaultOptions options exe argv =
case getOpt Permute options argv of
(o,n,[] ) -> return (foldl (flip id) defaultOptions o, n)
(_,_,errs) -> ioError (userError (concat errs ++ usageInfo header options))
where header = "Usage: " ++ exe ++ " [OPTION...] parameters..."
--Rm
data RmOptions = RmOptions
{ rmTimeOffset :: Either ParseError Double
, rmVersion :: Bool
, rmHelp :: Bool
, rmTrash :: Maybe String
} deriving(Show)
rmDefaults = RmOptions
{ rmTimeOffset = Right 0
, rmVersion = False
, rmHelp = False
, rmTrash = Nothing
}
rmOptions =
[ Option ['V'] ["version"] (NoArg (\opts -> opts{rmVersion=True})) "Show version number"
, Option ['h'] ["help"] (NoArg (\opts -> opts{rmHelp=True})) "Print help"
, Option ['t'] ["time-offset"] (ReqArg (\offset opts ->
opts{rmTimeOffset=parse timeOffsetString "" offset}) "offset")
"Specify time offset suffixes ymdHMS supported, default: 0d"
, Option ['T'] ["trash-path"] (ReqArg (\path opts -> opts{rmTrash=Just path}) "path")
"Override Trash path autodetection."
]
doRm time iPath fPath fileName = do
absFile <- canonicalizePath fileName
moveToTrash $ TrashFile
(iPath </> takeFileName fileName <.> "trashinfo")
(fPath </> takeFileName fileName)
absFile
time
0
fdoRm args = do
(myOpts, realArgs) <- parseOpts rmDefaults rmOptions "fdo-rm" args
when (rmVersion myOpts) printVersion
when (rmHelp myOpts) $ putStrLn
(usageInfo "Usage: fdo-rm [OPTION...] parameters..." rmOptions)
>> exitSuccess
(iPath,fPath) <- maybe
getTrashPaths
(\p -> return (p </> "info", p </> "files"))
(rmTrash myOpts)
createDirectoryIfMissing True iPath
createDirectoryIfMissing True fPath
timeOffset <- either
(\x -> ioError (userError $ "Invalid time format" ++ show x))
(\x -> return x)
(rmTimeOffset myOpts)
time <- fmap (addUTCTime $ castFloat timeOffset) getCurrentTime
mapM_ (doRm time iPath fPath) (map dropTrailingPathSeparator realArgs)
--Purge
data PurgeOptions = PurgeOptions
{ purgeThreshold :: Double
, purgeAgePow :: Double
, purgeSizePow :: Double
, purgeVersion :: Bool
, purgeHelp :: Bool
, purgeTrash :: Maybe String
} deriving(Show)
purgeDefaults = PurgeOptions
{ purgeThreshold = 10**6
, purgeAgePow = 1
, purgeSizePow = 0.1
, purgeHelp = False
, purgeVersion = False
, purgeTrash = Nothing
}
purgeOptions =
[ Option ['V'] ["version"] (NoArg (\opts -> opts{purgeVersion=True})) "Show version number"
, Option ['h'] ["help"] (NoArg (\opts -> opts{purgeHelp=True})) "Print help"
, Option ['a'] ["age"] (ReqArg (\secs opts -> opts{purgeThreshold=read secs}) "secs")
("Specify maximium file age default: " ++ (show $ purgeThreshold purgeDefaults))
, Option ['A'] ["age-power"] (ReqArg (\pow opts -> opts{purgeAgePow=read pow}) "pow")
("Specify age power for threshold formula size^sizepow*age^agepow, default: " ++
(show $ purgeAgePow purgeDefaults))
, Option ['S'] ["size-power"] (ReqArg (\pow opts -> opts{purgeSizePow=read pow}) "pow")
("Specify size power for threshold formula size^sizepow*age^agepow, default: "
++ (show $ purgeSizePow purgeDefaults))
, Option ['T'] ["trash-path"] (ReqArg (\path opts -> opts{purgeTrash=Just path}) "path")
"Override Trash path autodetection."
]
fdoPurge args = do
(myOpts, _) <- parseOpts purgeDefaults purgeOptions "fdo-purge" args
when (purgeVersion myOpts) printVersion
when (purgeHelp myOpts) $ putStrLn
(usageInfo "Usage: fdo-purge [OPTION...] parameters..." purgeOptions)
>> exitSuccess
(iPath,fPath) <- maybe
getTrashPaths
(\p -> return (p </> "info", p </> "files"))
(purgeTrash myOpts)
createDirectoryIfMissing True iPath
createDirectoryIfMissing True fPath
now <- getCurrentTime
(iExtra,dExtra) <- trashGetOrphans iPath fPath
ayx <- fmap
(filter (\x -> (max 0 $ castFloat $ diffUTCTime now $ deleteTime x)**(purgeAgePow myOpts) *
(max 1 $ fromIntegral $ totalSize x)**(purgeSizePow myOpts) > purgeThreshold myOpts))
$ trashGetFiles iPath fPath
when (not$null iExtra) $ putStrLn "Orphan files detected:\n" >> print iExtra
when (not$null dExtra) $ putStrLn "Orphan files detected:\n" >> print dExtra
mapM_ expungeTrash ayx
--Unrm
data UnRmOptions = UnRmOptions
{ unRmOrigDir :: Bool
, unRmVersion :: Bool
, unRmHelp :: Bool
, unRmOutFile :: Maybe String
, unRmSelect :: Maybe Int
, unRmTrash :: Maybe String
} deriving(Show)
unRmDefaults = UnRmOptions
{ unRmOrigDir = False
, unRmHelp = False
, unRmVersion = False
, unRmOutFile = Nothing
, unRmSelect = Nothing
, unRmTrash = Nothing
}
unRmOptions =
[ Option ['V'] ["version"] (NoArg (\opts -> opts{unRmVersion=True})) "Show version number"
, Option ['h'] ["help"] (NoArg (\opts -> opts{unRmHelp=True})) "Print help"
, Option ['O'] ["original-name"] (NoArg (\opts -> opts{unRmOrigDir=True}))
"output file to original path, default: ., conflicts with -o"
, Option ['o'] ["output-file"] (ReqArg (\out opts -> opts{unRmOutFile=Just out}) "filepath")
"Specify output file, conflicts with -O"
, Option ['s'] ["select"] (ReqArg (\index opts -> opts{unRmSelect=Just $ read index}) "index")
"Select file with index if multiple files match"
, Option ['T'] ["trash-path"] (ReqArg (\path opts -> opts{unRmTrash=Just path}) "path")
"Override Trash path autodetection."
]
doRestore file opts saveFile = maybe
(if (unRmOrigDir opts)
then trashRestore file Nothing
else trashRestore file (Just saveFile))
(\out -> trashRestore file (Just out))
(unRmOutFile opts)
doUnRm files opts saveFile = do
case (length files') of
0 -> putStrLn $ "No such file: " ++ saveFile
1 -> doRestore (head files') opts saveFile
_ -> maybe
(putStrLn $ "Multiple matches:\n" ++ unlines (zipWith (++) (map (\x -> show x ++ ": ") [(0::Int)..]) (map origPath files') ))
(\index -> if (index < length files' && index >= 0)
then doRestore (files' !! index) opts saveFile
else putStrLn $ "Index " ++ show index ++ " out of bounds!")
(unRmSelect opts)
where files' = if (isAbsolute saveFile)
then filter (\x -> origPath x == saveFile) files
else filter (\x -> takeFileName (origPath x) == takeFileName saveFile) files
fdoUnRm args = do
(myOpts, realArgs) <- parseOpts unRmDefaults unRmOptions "fdo-unrm" args
when (unRmVersion myOpts) printVersion
when (unRmHelp myOpts) $ putStrLn
(usageInfo "Usage: fdo-unrm [OPTION...] parameters..." unRmOptions)
>> exitSuccess
(iPath,fPath) <- maybe
getTrashPaths
(\p -> return (p </> "info", p </> "files"))
(unRmTrash myOpts)
createDirectoryIfMissing True iPath
createDirectoryIfMissing True fPath
files <- trashGetFiles iPath fPath
mapM_ (doUnRm files myOpts) realArgs
--Main
main :: IO ()
main = do
args <- getArgs
exe <- getProgName
let actionsStr = intercalate "|" $ map fst actions
thisAction = maybe
( if (null args)
then Nothing
else maybe
(Nothing)
(\x -> Just (tail args, x))
(lookup (args !! 0) actions) )
(\x -> Just (args,x))
(lookup (drop 4 exe) actions)
maybe
(putStrLn $ "No action specified\nUsage: " ++ exe ++ " <" ++ actionsStr ++ "> params")
(\(a,f) -> f a)
thisAction