{- git-annex assistant sanity checker - - Copyright 2012 Joey Hess - - Licensed under the GNU GPL version 3 or higher. -} module Assistant.Threads.SanityChecker ( sanityCheckerThread ) where import Assistant.Common import Assistant.DaemonStatus import Assistant.Alert import qualified Git.LsFiles import Utility.ThreadScheduler import qualified Assistant.Threads.Watcher as Watcher import Data.Time.Clock.POSIX {- This thread wakes up occasionally to make sure the tree is in good shape. -} sanityCheckerThread :: NamedThread sanityCheckerThread = NamedThread "SanityChecker" $ forever $ do waitForNextCheck debug ["starting sanity check"] void $ alertWhile sanityCheckAlert go debug ["sanity check complete"] where go = do dstatus <- getAssistant daemonStatusHandle liftIO $ modifyDaemonStatus_ dstatus $ \s -> s { sanityCheckRunning = True } now <- liftIO $ getPOSIXTime -- before check started r <- either showerr return =<< tryIO <~> check liftIO $ modifyDaemonStatus_ dstatus $ \s -> s { sanityCheckRunning = False , lastSanityCheck = Just now } return r showerr e = do liftAnnex $ warning $ show e return False {- Only run one check per day, from the time of the last check. -} waitForNextCheck :: Assistant () waitForNextCheck = do v <- lastSanityCheck <$> getDaemonStatus now <- liftIO getPOSIXTime liftIO $ threadDelaySeconds $ Seconds $ calcdelay now v where calcdelay _ Nothing = oneDay calcdelay now (Just lastcheck) | lastcheck < now = max oneDay $ oneDay - truncate (now - lastcheck) | otherwise = oneDay oneDay :: Int oneDay = 24 * 60 * 60 {- It's important to stay out of the Annex monad as much as possible while - running potentially expensive parts of this check, since remaining in it - will block the watcher. -} check :: Assistant Bool check = do g <- liftAnnex gitRepo -- Find old unstaged symlinks, and add them to git. (unstaged, cleanup) <- liftIO $ Git.LsFiles.notInRepo False ["."] g now <- liftIO $ getPOSIXTime forM_ unstaged $ \file -> do ms <- liftIO $ catchMaybeIO $ getSymbolicLinkStatus file case ms of Just s | toonew (statusChangeTime s) now -> noop | isSymbolicLink s -> addsymlink file ms _ -> noop liftIO $ void cleanup return True where toonew timestamp now = now < (realToFrac (timestamp + slop) :: POSIXTime) slop = fromIntegral tenMinutes insanity msg = do liftAnnex $ warning msg dstatus <- getAssistant daemonStatusHandle liftIO $ void $ addAlert dstatus $ sanityCheckFixAlert msg addsymlink file s = do Watcher.runHandler Watcher.onAddSymlink file s insanity $ "found unstaged symlink: " ++ file