2012-07-19 17:01:41 +00:00
|
|
|
{- 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
|
|
|
|
|
2012-07-20 23:29:59 +00:00
|
|
|
import Assistant.Common
|
2012-07-19 17:01:41 +00:00
|
|
|
import Assistant.ThreadedMonad
|
|
|
|
import Assistant.DaemonStatus
|
2012-07-23 03:16:56 +00:00
|
|
|
import Assistant.ScanRemotes
|
|
|
|
import Assistant.Threads.Pusher (pushToRemotes)
|
2012-07-22 19:06:18 +00:00
|
|
|
import qualified Annex
|
|
|
|
import qualified Git
|
2012-07-19 17:01:41 +00:00
|
|
|
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
|
2012-07-19 17:01:41 +00:00
|
|
|
|
|
|
|
import Control.Concurrent
|
2012-07-20 06:16:09 +00:00
|
|
|
import qualified Control.Exception as E
|
2012-07-19 17:01:41 +00:00
|
|
|
import qualified Data.Set as S
|
2012-07-23 03:16:56 +00:00
|
|
|
import Data.Time.Clock
|
2012-07-19 17:01:41 +00:00
|
|
|
|
|
|
|
#if WITH_DBUS
|
|
|
|
import DBus.Client
|
2012-07-20 03:34:33 +00:00
|
|
|
import DBus
|
2012-07-20 05:59:21 +00:00
|
|
|
import Data.Word (Word32)
|
2012-07-19 17:01:41 +00:00
|
|
|
#else
|
|
|
|
#warning Building without dbus support; will use mtab polling
|
|
|
|
#endif
|
|
|
|
|
2012-07-20 23:29:59 +00:00
|
|
|
thisThread :: ThreadName
|
|
|
|
thisThread = "MountWatcher"
|
|
|
|
|
2012-07-23 03:16:56 +00:00
|
|
|
mountWatcherThread :: ThreadState -> DaemonStatusHandle -> ScanRemoteMap -> IO ()
|
|
|
|
mountWatcherThread st handle scanremotes =
|
2012-07-19 17:01:41 +00:00
|
|
|
#if WITH_DBUS
|
2012-07-23 03:16:56 +00:00
|
|
|
dbusThread st handle scanremotes
|
2012-07-19 17:01:41 +00:00
|
|
|
#else
|
2012-07-23 03:16:56 +00:00
|
|
|
pollingThread st handle scanremotes
|
2012-07-19 17:01:41 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#if WITH_DBUS
|
2012-07-20 03:34:33 +00:00
|
|
|
|
2012-07-23 03:16:56 +00:00
|
|
|
dbusThread :: ThreadState -> DaemonStatusHandle -> ScanRemoteMap -> IO ()
|
|
|
|
dbusThread st dstatus scanremotes = E.catch (go =<< connectSession) onerr
|
2012-07-19 17:01:41 +00:00
|
|
|
where
|
2012-07-20 03:34:33 +00:00
|
|
|
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
|
2012-07-20 22:14:57 +00:00
|
|
|
forM_ mountAdded $ \matcher ->
|
|
|
|
listen client matcher $ \_event -> do
|
|
|
|
nowmounted <- currentMountPoints
|
|
|
|
wasmounted <- swapMVar mvar nowmounted
|
2012-07-23 03:16:56 +00:00
|
|
|
handleMounts st dstatus scanremotes wasmounted nowmounted
|
2012-07-20 03:34:33 +00:00
|
|
|
, 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 ()
|
2012-07-20 03:34:33 +00:00
|
|
|
onerr e = do
|
|
|
|
runThreadState st $
|
|
|
|
warning $ "Failed to use dbus; falling back to mtab polling (" ++ show e ++ ")"
|
|
|
|
pollinstead
|
2012-07-23 03:16:56 +00:00
|
|
|
pollinstead = pollingThread st dstatus scanremotes
|
2012-07-20 03:34:33 +00:00
|
|
|
|
2012-07-20 22:14:57 +00:00
|
|
|
type ServiceName = String
|
|
|
|
|
|
|
|
listServiceNames :: Client -> IO [ServiceName]
|
|
|
|
listServiceNames client = do
|
2012-07-20 05:59:21 +00:00
|
|
|
reply <- callDBus client "ListNames" []
|
2012-07-20 03:34:33 +00:00
|
|
|
return $ fromMaybe [] $ fromVariant (methodReturnBody reply !! 0)
|
|
|
|
|
2012-07-20 05:59:21 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2012-07-20 22:14:57 +00:00
|
|
|
{- Examine the list of services connected to dbus, to see if there
|
2012-07-20 05:59:21 +00:00
|
|
|
- are any we can use to monitor mounts. If not, will attempt to start one. -}
|
2012-07-20 03:34:33 +00:00
|
|
|
checkMountMonitor :: Client -> IO Bool
|
2012-07-20 22:14:57 +00:00
|
|
|
checkMountMonitor client = do
|
|
|
|
running <- filter (`elem` usableservices)
|
|
|
|
<$> listServiceNames client
|
|
|
|
if null running
|
|
|
|
then startOneService client startableservices
|
|
|
|
else do
|
2012-07-20 23:29:59 +00:00
|
|
|
debug thisThread [ "Using running DBUS service"
|
2012-07-20 22:14:57 +00:00
|
|
|
, Prelude.head running
|
|
|
|
, "to monitor mount events."
|
|
|
|
]
|
|
|
|
return True
|
2012-07-20 03:34:33 +00:00
|
|
|
where
|
2012-07-20 22:14:57 +00:00
|
|
|
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
|
2012-07-20 23:29:59 +00:00
|
|
|
debug thisThread [ "Started DBUS service"
|
2012-07-20 22:14:57 +00:00
|
|
|
, x
|
|
|
|
, "to monitor mount events."
|
|
|
|
]
|
|
|
|
return True
|
|
|
|
, startOneService client xs
|
|
|
|
)
|
2012-07-20 03:34:33 +00:00
|
|
|
|
|
|
|
{- Filter matching events recieved when drives are mounted. -}
|
2012-07-20 22:14:57 +00:00
|
|
|
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"
|
|
|
|
}
|
2012-07-19 17:01:41 +00:00
|
|
|
|
|
|
|
#endif
|
|
|
|
|
2012-07-23 03:16:56 +00:00
|
|
|
pollingThread :: ThreadState -> DaemonStatusHandle -> ScanRemoteMap -> IO ()
|
|
|
|
pollingThread st dstatus scanremotes = go =<< currentMountPoints
|
2012-07-19 17:01:41 +00:00
|
|
|
where
|
|
|
|
go wasmounted = do
|
|
|
|
threadDelaySeconds (Seconds 10)
|
|
|
|
nowmounted <- currentMountPoints
|
2012-07-23 03:16:56 +00:00
|
|
|
handleMounts st dstatus scanremotes wasmounted nowmounted
|
2012-07-19 17:01:41 +00:00
|
|
|
go nowmounted
|
|
|
|
|
2012-07-23 03:16:56 +00:00
|
|
|
handleMounts :: ThreadState -> DaemonStatusHandle -> ScanRemoteMap -> MountPoints -> MountPoints -> IO ()
|
|
|
|
handleMounts st dstatus scanremotes wasmounted nowmounted = mapM_ (handleMount st dstatus scanremotes) $
|
2012-07-19 17:01:41 +00:00
|
|
|
S.toList $ newMountPoints wasmounted nowmounted
|
|
|
|
|
2012-07-23 03:16:56 +00:00
|
|
|
handleMount :: ThreadState -> DaemonStatusHandle -> ScanRemoteMap -> Mntent -> IO ()
|
|
|
|
handleMount st dstatus scanremotes mntent = do
|
2012-07-22 19:06:18 +00:00
|
|
|
debug thisThread ["detected mount of", mnt_dir mntent]
|
|
|
|
rs <- remotesUnder st dstatus mntent
|
|
|
|
unless (null rs) $ do
|
|
|
|
branch <- runThreadState st $ Command.Sync.currentBranch
|
2012-07-23 03:16:56 +00:00
|
|
|
let nonspecial = filter (Git.repoIsLocal . Remote.repo) rs
|
|
|
|
unless (null nonspecial) $ do
|
|
|
|
debug thisThread ["pulling from", show nonspecial]
|
|
|
|
runThreadState st $ manualPull branch nonspecial
|
|
|
|
now <- getCurrentTime
|
|
|
|
pushToRemotes thisThread now st Nothing nonspecial
|
|
|
|
addScanRemotes scanremotes rs
|
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
|
2012-07-19 17:01:41 +00:00
|
|
|
|
2012-07-20 01:25:26 +00:00
|
|
|
type MountPoints = S.Set Mntent
|
2012-07-19 17:01:41 +00:00
|
|
|
|
|
|
|
currentMountPoints :: IO MountPoints
|
2012-07-20 01:25:26 +00:00
|
|
|
currentMountPoints = S.fromList <$> getMounts
|
2012-07-19 17:01:41 +00:00
|
|
|
|
|
|
|
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
|