{-# LANGUAGE CPP #-}
module Snapshots (
cleanGhcSnapshots,
cleanMinorSnapshots,
cleanOldStackWork,
listGhcSnapshots,
sizeSnapshots,
sizeStackWork,
removeStackWork
)
where
import Control.Monad.Extra
import Data.Bifunctor (second)
import Data.List.Extra
import Data.Version.Extra
import Numeric.Natural
import SimpleCmd
#if MIN_VERSION_simple_cmd(0,2,1)
hiding (whenM)
#endif
import SimplePrompt (yesNo)
import System.Directory hiding (removeDirectoryRecursive, removeFile)
import System.FilePath
import Text.Printf
import Directories (globDirs, getStackSubdir, getStackWorkDir,
getStackWorkInstall, listCurrentDirectory,
traversePlatforms, traversePlatforms')
import qualified Remove
import Types
import Versions
data SnapshotInstall =
SnapInst { snapHash :: String,
snapGHC :: Version}
deriving Eq
instance Ord SnapshotInstall where
compare s1 s2 = compare (snapGHC s1) (snapGHC s2)
data VersionSnapshots =
VersionSnaps {snapsVersion :: Version,
snapsHashes :: [String]}
deriving Eq
instance Ord VersionSnapshots where
compare s1 s2 = compare (snapsVersion s1) (snapsVersion s2)
instance Show VersionSnapshots where
show (VersionSnaps ver snaps) = showVersion ver ++ ":" ++ show snaps
readVersionedSnaps :: [FilePath] -> [VersionSnapshots]
readVersionedSnaps snaps =
map (uncurry VersionSnaps . second (map snapHash)) .
groupOnKey snapGHC . sort $
map readSnapshot snaps
where
readSnapshot :: FilePath -> SnapshotInstall
readSnapshot pth =
let (hash,ver) = splitFileName pth
-- remove trailing '/'
in SnapInst (init hash) (readVersion ver)
getSnapshotDirs :: Maybe Version -> IO [VersionSnapshots]
getSnapshotDirs mghcver = do
let pat = maybe "*" versionMatch mghcver
readVersionedSnaps <$> globDirs ("*" </> pat)
where
versionMatch :: Version -> String
versionMatch ghcver =
showVersion ghcver ++ if isMajorVersion ghcver then ".*" else ""
sizeSnapshots :: Bool -> Maybe String -> IO ()
sizeSnapshots nothuman msystem =
traversePlatforms' (getStackSubdir "snapshots") msystem $ \plat -> do
cwd <- getCurrentDirectory
cmd_ "du" $ ["-h" | not nothuman] ++ ["-s", cwd </> plat]
listGhcSnapshots :: Maybe String -> Maybe Version -> FilePath -> IO ()
listGhcSnapshots msystem mghcver dir = do
traversePlatforms (return dir) msystem $ do
ghcs <- getSnapshotDirs mghcver
mapM_ printTotalGhcSize ghcs
plural :: String -> Int -> String
plural thing n = show n +-+ thing ++ if n == 1 then "" else "s"
removeVersionSnaps :: Deletion -> FilePath -> VersionSnapshots -> IO ()
removeVersionSnaps deletion cwd versnap = do
let dirs = snapsHashes versnap
dir <- getCurrentDirectory
home <- getHomeDirectory
putStrLn $ plural "dir" (length dirs) +-+ "in" +-+ renderDir home dir </> "*" </> showVersion (snapsVersion versnap) +-+ Remove.wouldBeRemoved deletion
mapM_ (Remove.doRemoveDirectory deletion) dirs
where
renderDir :: FilePath -> FilePath -> FilePath
renderDir home fp =
case stripPrefix (cwd ++ "/") fp of
Just reldir -> reldir
Nothing -> "~" </> dropPrefix (home ++ "/") fp
cleanGhcSnapshots :: Deletion -> FilePath -> Version -> String -> IO ()
cleanGhcSnapshots deletion cwd ghcver platform = do
withCurrentDirectory platform $ do
ghcs <- getSnapshotDirs (Just ghcver)
unless (null ghcs) $
putStrLn (platform ++ ":")
yes <-
if isMajorVersion ghcver && deletePrompt deletion
then yesNo $ "Delete all" +-+ showVersion ghcver +-+ "builds"
else return True
when yes $
mapM_ (removeVersionSnaps deletion cwd) ghcs
cleanMinorSnapshots :: Deletion -> FilePath -> Maybe Version -> String -> IO ()
cleanMinorSnapshots deletion cwd mghcver platform = do
withCurrentDirectory platform $ do
ghcs <- getSnapshotDirs (majorVersion <$> mghcver)
case mghcver of
Nothing -> do
let majors = groupOn (majorVersion . snapsVersion) ghcs
unless (null majors) $
putStrLn (platform ++ ":")
forM_ majors $ \ gmajor ->
when (length gmajor > 1) $
mapM_ (removeVersionSnaps deletion cwd) (init gmajor)
Just ghcver -> do
let newestMinor = if isMajorVersion ghcver
then (snapsVersion . last) ghcs
else ghcver
gminors = filter ((< newestMinor) . snapsVersion) ghcs
unless (null gminors) $
putStrLn (platform ++ ":")
mapM_ (removeVersionSnaps deletion cwd) gminors
cleanOldStackWork :: Deletion -> Natural -> Maybe String -> IO ()
cleanOldStackWork deletion keep msystem = do
traversePlatforms getStackWorkInstall msystem $ do
dirs <- sortOn takeFileName . lines <$> shell ( unwords $ "ls" : ["-d", "*/*"])
let ghcs = sortOn (readVersion . fst) $
groupOnKey takeFileName dirs
mapM_ removeOlder ghcs
where
removeOlder :: (String,[FilePath]) -> IO ()
removeOlder (ghcver,dirs) = do
oldfiles <- drop (fromEnum keep) . reverse <$> sortedByAge
mapM_ (Remove.doRemoveDirectory deletion . takeDirectory) oldfiles
unless (null oldfiles) $
putStrLn $ plural "dir" (length oldfiles) +-+ Remove.wouldBeRemoved deletion +-+ "for" +-+ ghcver
where
sortedByAge = do
fileTimes <- mapM newestTimeStamp dirs
return $ map fst $ sortOn snd fileTimes
newestTimeStamp dir = do
withCurrentDirectory dir $ do
files <- listCurrentDirectory
timestamp <- maximum <$> mapM getModificationTime files
return (dir, timestamp)
sizeStackWork :: Bool -> FilePath -> IO ()
sizeStackWork nothuman dir = do
stackwork <- getStackWorkDir
let path = dir </> stackwork
whenM (doesDirectoryExist path) $
cmd_ "du" $ ["-h" | not nothuman] ++ ["-s", path]
printTotalGhcSize :: VersionSnapshots -> IO ()
printTotalGhcSize versnaps = do
total <- fst . word1 . last <$> cmdLines "du" ("-shc":snapsHashes versnaps)
printf "%4s %-6s (%d dirs)\n" total ((showVersion . snapsVersion) versnaps) (length (snapsHashes versnaps))
removeStackWork :: Deletion -> IO ()
removeStackWork deletion = do
exists <- doesDirectoryExist ".stack-work"
when exists $ do
yes <-
if deletePrompt deletion
then yesNo "Delete .stack-work"
else return True
when yes $
Remove.doRemoveDirectory deletion ".stack-work"
#if !MIN_VERSION_extra(1,7,11)
groupOnKey :: Eq k => (a -> k) -> [a] -> [(k, [a])]
groupOnKey _ [] = []
groupOnKey f (x:xs) = (fx, x:yes) : groupOnKey f no
where
fx = f x
(yes, no) = span (\y -> fx == f y) xs
#endif