summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Assistant.hs12
-rw-r--r--Assistant/Threads/MountWatcher.hs17
-rw-r--r--Assistant/Threads/NetWatcher.hs150
-rw-r--r--Utility/DBus.hs28
4 files changed, 190 insertions, 17 deletions
diff --git a/Assistant.hs b/Assistant.hs
index 350996977..413e5e90e 100644
--- a/Assistant.hs
+++ b/Assistant.hs
@@ -52,11 +52,17 @@
- state about that remote, pulls from it, and queues a push to it,
- as well as an update, and queues it onto the
- ConnectedRemoteChan
- - Thread 14: TransferScanner
+ - Thread 13: NetWatcher
+ - Deals with network connection interruptions, which would cause
+ - transfers to fail, and can be recovered from by waiting for a
+ - network connection, and syncing with all network remotes.
+ - Uses dbus to watch for network connections, or when dbus
+ - cannot be used, assumes there's been one every 30 minutes.
+ - Thread 15: TransferScanner
- Does potentially expensive checks to find data that needs to be
- transferred from or to remotes, and queues Transfers.
- Uses the ScanRemotes map.
- - Thread 15: WebApp
+ - Thread 16: WebApp
- Spawns more threads as necessary to handle clients.
- Displays the DaemonStatus.
-
@@ -110,6 +116,7 @@ import Assistant.Threads.TransferWatcher
import Assistant.Threads.Transferrer
import Assistant.Threads.SanityChecker
import Assistant.Threads.MountWatcher
+import Assistant.Threads.NetWatcher
import Assistant.Threads.TransferScanner
#ifdef WITH_WEBAPP
import Assistant.Threads.WebApp
@@ -165,6 +172,7 @@ startAssistant assistant daemonize webappwaiter = do
, assist $ daemonStatusThread st dstatus
, assist $ sanityCheckerThread st dstatus transferqueue changechan
, assist $ mountWatcherThread st dstatus scanremotes
+ , assist $ netWatcherThread st dstatus scanremotes
, assist $ transferScannerThread st dstatus scanremotes transferqueue
, watch $ watchThread st dstatus transferqueue changechan
]
diff --git a/Assistant/Threads/MountWatcher.hs b/Assistant/Threads/MountWatcher.hs
index 83f582a91..82de186cc 100644
--- a/Assistant/Threads/MountWatcher.hs
+++ b/Assistant/Threads/MountWatcher.hs
@@ -31,6 +31,7 @@ import qualified Data.Set as S
import Data.Time.Clock
#if WITH_DBUS
+import Utility.DBus
import DBus.Client
import DBus
import Data.Word (Word32)
@@ -78,20 +79,6 @@ dbusThread st dstatus scanremotes = E.catch (go =<< connectSession) onerr
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
@@ -164,7 +151,7 @@ handleMount st dstatus scanremotes dir = do
let nonspecial = filter (Git.repoIsLocal . Remote.repo) rs
unless (null nonspecial) $ do
void $ alertWhile dstatus (syncAlert nonspecial) $ do
- debug thisThread ["syncing with", show rs]
+ debug thisThread ["syncing with", show nonspecial]
sync nonspecial =<< runThreadState st (inRepo Git.Branch.current)
addScanRemotes scanremotes nonspecial
where
diff --git a/Assistant/Threads/NetWatcher.hs b/Assistant/Threads/NetWatcher.hs
new file mode 100644
index 000000000..d871a4791
--- /dev/null
+++ b/Assistant/Threads/NetWatcher.hs
@@ -0,0 +1,150 @@
+{- git-annex assistant network connection watcher, using dbus
+ -
+ - Copyright 2012 Joey Hess <joey@kitenet.net>
+ -
+ - Licensed under the GNU GPL version 3 or higher.
+ -}
+
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Assistant.Threads.NetWatcher where
+
+import Assistant.Common
+import Assistant.ThreadedMonad
+import Assistant.DaemonStatus
+import Assistant.ScanRemotes
+import Assistant.Threads.Pusher (pushToRemotes)
+import Assistant.Alert
+import qualified Git
+import Utility.ThreadScheduler
+import Remote.List
+import qualified Types.Remote as Remote
+import Assistant.Threads.Merger
+import qualified Git.Branch
+
+import qualified Control.Exception as E
+import Data.Time.Clock
+
+#if WITH_DBUS
+import Utility.DBus
+import DBus.Client
+import DBus
+import Data.Word (Word32)
+#else
+#warning Building without dbus support; will poll for network connection changes
+#endif
+
+thisThread :: ThreadName
+thisThread = "NetWatcher"
+
+netWatcherThread :: ThreadState -> DaemonStatusHandle -> ScanRemoteMap -> IO ()
+netWatcherThread 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 =<< connectSystem) onerr
+ where
+ go client = ifM (checkNetMonitor client)
+ ( do
+ listenNMConnections client handle
+ listenWicdConnections client handle
+ , do
+ runThreadState st $
+ warning "No known network monitor available through dbus; falling back to polling"
+ pollinstead
+ )
+ onerr :: E.SomeException -> IO ()
+ onerr e = do
+ runThreadState st $
+ warning $ "Failed to use dbus; falling back to polling (" ++ show e ++ ")"
+ pollinstead
+ pollinstead = pollingThread st dstatus scanremotes
+ handle = do
+ debug thisThread ["detected network connection"]
+ handleConnection st dstatus scanremotes
+
+{- Examine the list of services connected to dbus, to see if there
+ - are any we can use to monitor network connections. -}
+checkNetMonitor :: Client -> IO Bool
+checkNetMonitor client = do
+ running <- filter (`elem` [networkmanager, wicd])
+ <$> listServiceNames client
+ case running of
+ [] -> return False
+ (service:_) -> do
+ debug thisThread [ "Using running DBUS service"
+ , service
+ , "to monitor network connection events."
+ ]
+ return True
+ where
+ networkmanager = "org.freedesktop.NetworkManager"
+ wicd = "org.wicd.daemon"
+
+{- Listens for new NetworkManager connections. -}
+listenNMConnections :: Client -> IO () -> IO ()
+listenNMConnections client callback =
+ listen client matcher $ \event ->
+ when (Just True == anyM activeconnection (signalBody event)) $
+ callback
+ where
+ matcher = matchAny
+ { matchInterface = Just "org.freedesktop.NetworkManager.Connection.Active"
+ , matchMember = Just "PropertiesChanged"
+ }
+ nm_connection_activated = toVariant (2 :: Word32)
+ nm_state_key = toVariant ("State" :: String)
+ activeconnection v = do
+ m <- fromVariant v
+ vstate <- lookup nm_state_key $ dictionaryItems m
+ state <- fromVariant vstate
+ return $ state == nm_connection_activated
+
+{- Listens for new Wicd connections. -}
+listenWicdConnections :: Client -> IO () -> IO ()
+listenWicdConnections client callback =
+ listen client matcher $ \event ->
+ when (any (== wicd_success) (signalBody event)) $
+ callback
+ where
+ matcher = matchAny
+ { matchInterface = Just "org.wicd.daemon"
+ , matchMember = Just "ConnectResultsSent"
+ }
+ wicd_success = toVariant ("success" :: String)
+
+#endif
+
+pollingThread :: ThreadState -> DaemonStatusHandle -> ScanRemoteMap -> IO ()
+pollingThread st dstatus scanremotes = runEvery (Seconds 3600) $
+ handleConnection st dstatus scanremotes
+
+handleConnection :: ThreadState -> DaemonStatusHandle -> ScanRemoteMap -> IO ()
+handleConnection st dstatus scanremotes = do
+ rs <- networkRemotes st
+ unless (null rs) $ do
+ let nonspecial = filter (Git.repoIsUrl . Remote.repo) rs
+ unless (null nonspecial) $ do
+ void $ alertWhile dstatus (syncAlert nonspecial) $ do
+ debug thisThread ["syncing with", show nonspecial]
+ sync nonspecial =<< runThreadState st (inRepo Git.Branch.current)
+ addScanRemotes scanremotes nonspecial
+ where
+ sync rs (Just branch) = do
+ runThreadState st $ manualPull (Just branch) rs
+ now <- getCurrentTime
+ pushToRemotes thisThread now st Nothing rs
+ sync _ _ = return True
+
+{- Finds network remotes. -}
+networkRemotes :: ThreadState -> IO [Remote]
+networkRemotes st = runThreadState st $ do
+ rs <- remoteList
+ return $ filter (isNothing . Remote.path) rs
diff --git a/Utility/DBus.hs b/Utility/DBus.hs
new file mode 100644
index 000000000..cfd06f762
--- /dev/null
+++ b/Utility/DBus.hs
@@ -0,0 +1,28 @@
+{- DBus utilities
+ -
+ - Copyright 2012 Joey Hess <joey@kitenet.net>
+ -
+ - Licensed under the GNU GPL version 3 or higher.
+ -}
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module Utility.DBus where
+
+import DBus.Client
+import DBus
+import Data.Maybe
+
+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
+ }