git-annex/Assistant/Threads/MountWatcher.hs

221 lines
6.9 KiB
Haskell
Raw Normal View History

{- git-annex assistant mount watcher, using either dbus or mtab polling
-
- Copyright 2012 Joey Hess <joey@kitenet.net>
-
- Licensed under the GNU GPL version 3 or higher.
-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
module Assistant.Threads.MountWatcher where
import Assistant.Common
import Assistant.ThreadedMonad
import Assistant.DaemonStatus
import Assistant.ScanRemotes
import Assistant.Threads.Pusher (pushToRemotes)
2012-07-29 17:22:08 +00:00
import Assistant.Alert
2012-07-22 19:06:18 +00:00
import qualified Annex
import qualified Git
import Utility.ThreadScheduler
import Utility.Mounts
2012-07-22 19:06:18 +00:00
import Remote.List
import qualified Types.Remote as Remote
import qualified Remote.Git
import qualified Command.Sync
import Assistant.Threads.Merger
import Logs.Remote
import Control.Concurrent
2012-07-20 06:16:09 +00:00
import qualified Control.Exception as E
import qualified Data.Set as S
import Data.Time.Clock
#if WITH_DBUS
import DBus.Client
import DBus
import Data.Word (Word32)
#else
#warning Building without dbus support; will use mtab polling
#endif
thisThread :: ThreadName
thisThread = "MountWatcher"
mountWatcherThread :: ThreadState -> DaemonStatusHandle -> ScanRemoteMap -> IO ()
mountWatcherThread st handle scanremotes =
#if WITH_DBUS
dbusThread st handle scanremotes
#else
pollingThread st handle scanremotes
#endif
#if WITH_DBUS
dbusThread :: ThreadState -> DaemonStatusHandle -> ScanRemoteMap -> IO ()
dbusThread st dstatus scanremotes = E.catch (go =<< connectSession) onerr
where
go client = ifM (checkMountMonitor client)
( do
{- Store the current mount points in an mvar,
- to be compared later. We could in theory
- work out the mount point from the dbus
- message, but this is easier. -}
mvar <- newMVar =<< currentMountPoints
forM_ mountAdded $ \matcher ->
listen client matcher $ \_event -> do
nowmounted <- currentMountPoints
wasmounted <- swapMVar mvar nowmounted
handleMounts st dstatus scanremotes wasmounted nowmounted
, do
runThreadState st $
warning "No known volume monitor available through dbus; falling back to mtab polling"
pollinstead
)
2012-07-20 06:16:09 +00:00
onerr :: E.SomeException -> IO ()
onerr e = do
runThreadState st $
warning $ "Failed to use dbus; falling back to mtab polling (" ++ show e ++ ")"
pollinstead
pollinstead = pollingThread st dstatus scanremotes
type ServiceName = String
listServiceNames :: Client -> IO [ServiceName]
listServiceNames client = do
reply <- callDBus client "ListNames" []
return $ fromMaybe [] $ fromVariant (methodReturnBody reply !! 0)
callDBus :: Client -> MemberName -> [Variant] -> IO MethodReturn
callDBus client name params = call_ client $
(methodCall "/org/freedesktop/DBus" "org.freedesktop.DBus" name)
{ methodCallDestination = Just "org.freedesktop.DBus"
, methodCallBody = params
}
{- Examine the list of services connected to dbus, to see if there
- are any we can use to monitor mounts. If not, will attempt to start one. -}
checkMountMonitor :: Client -> IO Bool
checkMountMonitor client = do
running <- filter (`elem` usableservices)
<$> listServiceNames client
if null running
then startOneService client startableservices
else do
debug thisThread [ "Using running DBUS service"
, Prelude.head running
, "to monitor mount events."
]
return True
where
startableservices = [gvfs]
usableservices = startableservices ++ [kde]
gvfs = "org.gtk.Private.GduVolumeMonitor"
kde = "org.kde.DeviceNotifications"
startOneService :: Client -> [ServiceName] -> IO Bool
startOneService _ [] = return False
startOneService client (x:xs) = do
_ <- callDBus client "StartServiceByName"
[toVariant x, toVariant (0 :: Word32)]
ifM (elem x <$> listServiceNames client)
( do
debug thisThread [ "Started DBUS service"
, x
, "to monitor mount events."
]
return True
, startOneService client xs
)
{- Filter matching events recieved when drives are mounted. -}
mountAdded :: [MatchRule]
mountAdded = [gvfs, kde]
where
gvfs = matchAny
{ matchInterface = Just "org.gtk.Private.RemoteVolumeMonitor"
, matchMember = Just "MountAdded"
}
kde = matchAny
{ matchInterface = Just "org.kde.Solid.Device"
, matchMember = Just "setupDone"
}
#endif
pollingThread :: ThreadState -> DaemonStatusHandle -> ScanRemoteMap -> IO ()
pollingThread st dstatus scanremotes = go =<< currentMountPoints
where
go wasmounted = do
threadDelaySeconds (Seconds 10)
nowmounted <- currentMountPoints
handleMounts st dstatus scanremotes wasmounted nowmounted
go nowmounted
handleMounts :: ThreadState -> DaemonStatusHandle -> ScanRemoteMap -> MountPoints -> MountPoints -> IO ()
handleMounts st dstatus scanremotes wasmounted nowmounted = mapM_ (handleMount st dstatus scanremotes) $
S.toList $ newMountPoints wasmounted nowmounted
handleMount :: ThreadState -> DaemonStatusHandle -> ScanRemoteMap -> Mntent -> IO ()
handleMount st dstatus scanremotes mntent = do
2012-07-29 17:22:08 +00:00
debug thisThread ["detected mount of", dir]
2012-07-22 19:06:18 +00:00
rs <- remotesUnder st dstatus mntent
unless (null rs) $ do
branch <- runThreadState st $ Command.Sync.currentBranch
let nonspecial = filter (Git.repoIsLocal . Remote.repo) rs
2012-07-29 17:22:08 +00:00
unless (null nonspecial) $
void $ alertWhile dstatus (syncMountAlert dir nonspecial) $ do
2012-07-29 17:22:08 +00:00
debug thisThread ["syncing with", show nonspecial]
runThreadState st $ manualPull branch nonspecial
now <- getCurrentTime
pushToRemotes thisThread now st Nothing nonspecial
addScanRemotes scanremotes rs
2012-07-29 17:22:08 +00:00
where
dir = mnt_dir mntent
2012-07-22 19:06:18 +00:00
{- Finds remotes located underneath the mount point.
-
- Updates state to include the remotes.
-
- The config of git remotes is re-read, as it may not have been available
- at startup time, or may have changed (it could even be a different
- repository at the same remote location..)
-}
remotesUnder :: ThreadState -> DaemonStatusHandle -> Mntent -> IO [Remote]
remotesUnder st dstatus mntent = runThreadState st $ do
repotop <- fromRepo Git.repoPath
rs <- remoteList
pairs <- mapM (checkremote repotop) rs
let (waschanged, rs') = unzip pairs
when (any id waschanged) $ do
Annex.changeState $ \s -> s { Annex.remotes = rs' }
updateKnownRemotes dstatus
return $ map snd $ filter fst pairs
where
checkremote repotop r = case Remote.path r of
Just p | under mntent (absPathFrom repotop p) ->
(,) <$> pure True <*> updateremote r
_ -> return (False, r)
updateremote r = do
liftIO $ debug thisThread ["updating", show r]
m <- readRemoteLog
repo <- updaterepo $ Remote.repo r
remoteGen m (Remote.remotetype r) repo
updaterepo repo
| Git.repoIsLocal repo || Git.repoIsLocalUnknown repo =
Remote.Git.configRead repo
| otherwise = return repo
type MountPoints = S.Set Mntent
currentMountPoints :: IO MountPoints
currentMountPoints = S.fromList <$> getMounts
newMountPoints :: MountPoints -> MountPoints -> MountPoints
newMountPoints old new = S.difference new old
2012-07-22 19:06:18 +00:00
{- Checks if a mount point contains a path. The path must be absolute. -}
under :: Mntent -> FilePath -> Bool
under = dirContains . mnt_dir