2012-06-13 16:36:33 +00:00
|
|
|
{- git-annex assistant daemon status
|
|
|
|
-
|
|
|
|
- Copyright 2012 Joey Hess <joey@kitenet.net>
|
2012-06-23 05:20:40 +00:00
|
|
|
-
|
|
|
|
- Licensed under the GNU GPL version 3 or higher.
|
2012-06-13 16:36:33 +00:00
|
|
|
-}
|
|
|
|
|
|
|
|
module Assistant.DaemonStatus where
|
|
|
|
|
|
|
|
import Common.Annex
|
2012-06-13 18:02:40 +00:00
|
|
|
import Assistant.ThreadedMonad
|
2012-06-13 21:54:23 +00:00
|
|
|
import Utility.ThreadScheduler
|
|
|
|
import Utility.TempFile
|
2012-07-28 20:01:50 +00:00
|
|
|
import Utility.NotificationBroadcaster
|
2012-07-05 16:21:22 +00:00
|
|
|
import Logs.Transfer
|
|
|
|
import qualified Command.Sync
|
2012-06-13 16:36:33 +00:00
|
|
|
|
|
|
|
import Control.Concurrent
|
|
|
|
import System.Posix.Types
|
2012-06-13 17:35:15 +00:00
|
|
|
import Data.Time.Clock.POSIX
|
|
|
|
import Data.Time
|
|
|
|
import System.Locale
|
2012-07-02 20:11:04 +00:00
|
|
|
import qualified Data.Map as M
|
2012-06-13 16:36:33 +00:00
|
|
|
|
|
|
|
data DaemonStatus = DaemonStatus
|
|
|
|
-- False when the daemon is performing its startup scan
|
|
|
|
{ scanComplete :: Bool
|
|
|
|
-- Time when a previous process of the daemon was running ok
|
2012-06-13 17:35:15 +00:00
|
|
|
, lastRunning :: Maybe POSIXTime
|
2012-06-13 21:54:23 +00:00
|
|
|
-- True when the sanity checker is running
|
|
|
|
, sanityCheckRunning :: Bool
|
|
|
|
-- Last time the sanity checker ran
|
|
|
|
, lastSanityCheck :: Maybe POSIXTime
|
2012-07-02 20:11:04 +00:00
|
|
|
-- Currently running file content transfers
|
2012-07-05 20:34:20 +00:00
|
|
|
, currentTransfers :: TransferMap
|
2012-07-05 16:21:22 +00:00
|
|
|
-- Ordered list of remotes to talk to.
|
|
|
|
, knownRemotes :: [Remote]
|
2012-07-28 20:01:50 +00:00
|
|
|
-- Clients can use this to wait on changes to the DaemonStatus
|
|
|
|
, notificationBroadcaster :: NotificationBroadcaster
|
2012-06-13 16:36:33 +00:00
|
|
|
}
|
|
|
|
|
2012-07-05 20:34:20 +00:00
|
|
|
type TransferMap = M.Map Transfer TransferInfo
|
|
|
|
|
2012-06-13 16:36:33 +00:00
|
|
|
type DaemonStatusHandle = MVar DaemonStatus
|
|
|
|
|
2012-07-28 20:01:50 +00:00
|
|
|
newDaemonStatus :: IO DaemonStatus
|
|
|
|
newDaemonStatus = do
|
|
|
|
nb <- newNotificationBroadcaster
|
|
|
|
return $ DaemonStatus
|
|
|
|
{ scanComplete = False
|
|
|
|
, lastRunning = Nothing
|
|
|
|
, sanityCheckRunning = False
|
|
|
|
, lastSanityCheck = Nothing
|
|
|
|
, currentTransfers = M.empty
|
|
|
|
, knownRemotes = []
|
|
|
|
, notificationBroadcaster = nb
|
|
|
|
}
|
2012-06-13 16:36:33 +00:00
|
|
|
|
|
|
|
getDaemonStatus :: DaemonStatusHandle -> Annex DaemonStatus
|
|
|
|
getDaemonStatus = liftIO . readMVar
|
|
|
|
|
2012-07-06 22:44:13 +00:00
|
|
|
modifyDaemonStatus_ :: DaemonStatusHandle -> (DaemonStatus -> DaemonStatus) -> Annex ()
|
2012-07-28 20:01:50 +00:00
|
|
|
modifyDaemonStatus_ handle a = do
|
|
|
|
nb <- liftIO $ modifyMVar handle $ \s -> return
|
|
|
|
(a s, notificationBroadcaster s)
|
|
|
|
liftIO $ sendNotification nb
|
2012-07-06 22:44:13 +00:00
|
|
|
|
|
|
|
modifyDaemonStatus :: DaemonStatusHandle -> (DaemonStatus -> (DaemonStatus, b)) -> Annex b
|
2012-07-28 20:01:50 +00:00
|
|
|
modifyDaemonStatus handle a = do
|
|
|
|
(b, nb) <- liftIO $ modifyMVar handle $ \s -> do
|
|
|
|
let (s', b) = a s
|
|
|
|
return $ (s', (b, notificationBroadcaster s))
|
|
|
|
liftIO $ sendNotification nb
|
|
|
|
return b
|
2012-06-13 17:35:15 +00:00
|
|
|
|
2012-07-22 19:06:18 +00:00
|
|
|
{- Updates the cached ordered list of remotes from the list in Annex
|
|
|
|
- state. -}
|
|
|
|
updateKnownRemotes :: DaemonStatusHandle -> Annex ()
|
|
|
|
updateKnownRemotes dstatus = do
|
|
|
|
remotes <- Command.Sync.syncRemotes []
|
|
|
|
modifyDaemonStatus_ dstatus $
|
|
|
|
\s -> s { knownRemotes = remotes }
|
|
|
|
|
2012-06-13 18:02:40 +00:00
|
|
|
{- Load any previous daemon status file, and store it in the MVar for this
|
2012-07-02 20:11:04 +00:00
|
|
|
- process to use as its DaemonStatus. Also gets current transfer status. -}
|
2012-06-13 18:02:40 +00:00
|
|
|
startDaemonStatus :: Annex DaemonStatusHandle
|
|
|
|
startDaemonStatus = do
|
|
|
|
file <- fromRepo gitAnnexDaemonStatusFile
|
|
|
|
status <- liftIO $
|
2012-07-28 20:01:50 +00:00
|
|
|
catchDefaultIO (readDaemonStatusFile file) =<< newDaemonStatus
|
2012-07-02 20:11:04 +00:00
|
|
|
transfers <- M.fromList <$> getTransfers
|
2012-07-05 16:21:22 +00:00
|
|
|
remotes <- Command.Sync.syncRemotes []
|
2012-06-13 21:54:23 +00:00
|
|
|
liftIO $ newMVar status
|
|
|
|
{ scanComplete = False
|
|
|
|
, sanityCheckRunning = False
|
2012-07-02 20:11:04 +00:00
|
|
|
, currentTransfers = transfers
|
2012-07-05 16:21:22 +00:00
|
|
|
, knownRemotes = remotes
|
2012-06-13 21:54:23 +00:00
|
|
|
}
|
2012-06-13 18:02:40 +00:00
|
|
|
|
2012-07-28 20:01:50 +00:00
|
|
|
{- This writes the daemon status to disk, when it changes, but no more
|
|
|
|
- frequently than once every ten minutes.
|
|
|
|
-}
|
2012-06-13 18:02:40 +00:00
|
|
|
daemonStatusThread :: ThreadState -> DaemonStatusHandle -> IO ()
|
|
|
|
daemonStatusThread st handle = do
|
2012-07-28 20:01:50 +00:00
|
|
|
bhandle <- runThreadState st $
|
|
|
|
liftIO . newNotificationHandle
|
|
|
|
=<< notificationBroadcaster <$> getDaemonStatus handle
|
2012-06-13 18:02:40 +00:00
|
|
|
checkpoint
|
2012-07-28 20:01:50 +00:00
|
|
|
runEvery (Seconds tenMinutes) $ do
|
|
|
|
liftIO $ waitNotification bhandle
|
|
|
|
checkpoint
|
2012-06-13 18:02:40 +00:00
|
|
|
where
|
|
|
|
checkpoint = runThreadState st $ do
|
|
|
|
file <- fromRepo gitAnnexDaemonStatusFile
|
|
|
|
status <- getDaemonStatus handle
|
|
|
|
liftIO $ writeDaemonStatusFile file status
|
|
|
|
|
2012-06-13 17:35:15 +00:00
|
|
|
{- Don't just dump out the structure, because it will change over time,
|
|
|
|
- and parts of it are not relevant. -}
|
|
|
|
writeDaemonStatusFile :: FilePath -> DaemonStatus -> IO ()
|
|
|
|
writeDaemonStatusFile file status =
|
|
|
|
viaTmp writeFile file =<< serialized <$> getPOSIXTime
|
|
|
|
where
|
|
|
|
serialized now = unlines
|
|
|
|
[ "lastRunning:" ++ show now
|
|
|
|
, "scanComplete:" ++ show (scanComplete status)
|
2012-06-13 21:54:23 +00:00
|
|
|
, "sanityCheckRunning:" ++ show (sanityCheckRunning status)
|
2012-06-13 23:25:47 +00:00
|
|
|
, "lastSanityCheck:" ++ maybe "" show (lastSanityCheck status)
|
2012-06-13 17:35:15 +00:00
|
|
|
]
|
|
|
|
|
|
|
|
readDaemonStatusFile :: FilePath -> IO DaemonStatus
|
2012-07-28 20:01:50 +00:00
|
|
|
readDaemonStatusFile file = parse <$> newDaemonStatus <*> readFile file
|
2012-06-13 17:35:15 +00:00
|
|
|
where
|
2012-07-28 20:01:50 +00:00
|
|
|
parse status = foldr parseline status . lines
|
2012-06-13 17:35:15 +00:00
|
|
|
parseline line status
|
|
|
|
| key == "lastRunning" = parseval readtime $ \v ->
|
|
|
|
status { lastRunning = Just v }
|
|
|
|
| key == "scanComplete" = parseval readish $ \v ->
|
|
|
|
status { scanComplete = v }
|
2012-06-13 21:54:23 +00:00
|
|
|
| key == "sanityCheckRunning" = parseval readish $ \v ->
|
|
|
|
status { sanityCheckRunning = v }
|
|
|
|
| key == "lastSanityCheck" = parseval readtime $ \v ->
|
|
|
|
status { lastSanityCheck = Just v }
|
2012-06-13 17:35:15 +00:00
|
|
|
| otherwise = status -- unparsable line
|
|
|
|
where
|
|
|
|
(key, value) = separate (== ':') line
|
|
|
|
parseval parser a = maybe status a (parser value)
|
|
|
|
readtime s = do
|
|
|
|
d <- parseTime defaultTimeLocale "%s%Qs" s
|
|
|
|
Just $ utcTimeToPOSIXSeconds d
|
|
|
|
|
|
|
|
{- Checks if a time stamp was made after the daemon was lastRunning.
|
|
|
|
-
|
|
|
|
- Some slop is built in; this really checks if the time stamp was made
|
|
|
|
- at least ten minutes after the daemon was lastRunning. This is to
|
|
|
|
- ensure the daemon shut down cleanly, and deal with minor clock skew.
|
|
|
|
-
|
|
|
|
- If the daemon has never ran before, this always returns False.
|
|
|
|
-}
|
|
|
|
afterLastDaemonRun :: EpochTime -> DaemonStatus -> Bool
|
2012-06-13 18:02:40 +00:00
|
|
|
afterLastDaemonRun timestamp status = maybe False (< t) (lastRunning status)
|
2012-06-13 17:35:15 +00:00
|
|
|
where
|
|
|
|
t = realToFrac (timestamp + slop) :: POSIXTime
|
2012-06-13 18:19:21 +00:00
|
|
|
slop = fromIntegral tenMinutes
|
|
|
|
|
|
|
|
tenMinutes :: Int
|
|
|
|
tenMinutes = 10 * 60
|
2012-07-05 20:34:20 +00:00
|
|
|
|
|
|
|
{- Mutates the transfer map. -}
|
|
|
|
adjustTransfers :: DaemonStatusHandle -> (TransferMap -> TransferMap) -> Annex ()
|
2012-07-06 22:44:13 +00:00
|
|
|
adjustTransfers dstatus a = modifyDaemonStatus_ dstatus $
|
2012-07-05 20:34:20 +00:00
|
|
|
\s -> s { currentTransfers = a (currentTransfers s) }
|
2012-07-06 22:44:13 +00:00
|
|
|
|
|
|
|
{- Removes a transfer from the map, and returns its info. -}
|
|
|
|
removeTransfer :: DaemonStatusHandle -> Transfer -> Annex (Maybe TransferInfo)
|
|
|
|
removeTransfer dstatus t = modifyDaemonStatus dstatus go
|
|
|
|
where
|
|
|
|
go s =
|
|
|
|
let (info, ts) = M.updateLookupWithKey
|
|
|
|
(\_k _v -> Nothing)
|
|
|
|
t (currentTransfers s)
|
|
|
|
in (s { currentTransfers = ts }, info)
|