From 85952e03ff8e79219772f62a492783f1b691e512 Mon Sep 17 00:00:00 2001 From: Thomas Leonard Date: Sun, 27 Apr 2008 11:09:52 +0100 Subject: [PATCH] Check network status with NetworkManager before doing background updates. The logic is: - If we're on-line, check right away. - Otherwise (offline, connecting, unknown), sleep for 20 seconds and if we're not offline at the end, go ahead. This is designed to prevent two problems: - You log in. 0launch tries to check for updates to everything in your session while you're still connecting. These things therefore never get updated. - You're offline, but 0launch displays notifications about failing to update from time-to-time. If you don't use NetworkManager, then the effect of this is to always delay updates by 20 seconds, which shouldn't cause any problems. --- tests/my_dbus.py | 7 ++++ tests/testdownload.py | 7 ++-- zeroinstall/injector/background.py | 68 ++++++++++++++++++++++++++++++-------- 3 files changed, 67 insertions(+), 15 deletions(-) diff --git a/tests/my_dbus.py b/tests/my_dbus.py index a473858..6e77455 100644 --- a/tests/my_dbus.py +++ b/tests/my_dbus.py @@ -4,6 +4,10 @@ class SessionBus: def get_object(self, service, path): return None +class SystemBus: + def get_object(self, service, path): + return None + class Interface: def __init__(self, obj, iface): self.callback = {} @@ -23,6 +27,9 @@ class Interface: return nid + def state(self): + return 3 # NM_STATUS_CONNECTED + def connect_to_signal(self, signal, callback): self.callback[signal] = callback diff --git a/tests/testdownload.py b/tests/testdownload.py index 2d28e56..bffefdb 100755 --- a/tests/testdownload.py +++ b/tests/testdownload.py @@ -276,7 +276,7 @@ class TestDownload(BaseTest): finally: sys.stdout = old_out - def testBackground(self): + def testBackground(self, verbose = False): p = autopolicy.AutoPolicy('http://localhost:8000/Hello.xml') reader.update(iface_cache.iface_cache.get_interface(p.root), 'Hello.xml') p.freshness = 0 @@ -313,7 +313,7 @@ class TestDownload(BaseTest): try: try: os._exit = my_exit - background.spawn_background_update(p, False) + background.spawn_background_update(p, verbose) assert False except SystemExit, ex: self.assertEquals(1, ex.code) @@ -323,6 +323,9 @@ class TestDownload(BaseTest): sys.stdout = old_out assert ran_gui + def testBackgroundVerbose(self): + self.testBackground(verbose = True) + suite = unittest.makeSuite(TestDownload) if __name__ == '__main__': sys.argv.append('-v') diff --git a/zeroinstall/injector/background.py b/zeroinstall/injector/background.py index 916331b..893ab09 100644 --- a/zeroinstall/injector/background.py +++ b/zeroinstall/injector/background.py @@ -7,7 +7,7 @@ This avoids the need to annoy people with a 'checking for updates' box when they """ import sys, os -from logging import info +from logging import info, warn from zeroinstall.support import tasks from zeroinstall.injector.iface_cache import iface_cache from zeroinstall.injector import handler @@ -21,22 +21,35 @@ def _escape_xml(s): def _exec_gui(uri, *args): os.execvp('0launch', ['0launch', '--download-only', '--gui'] + list(args) + [uri]) +class _NetworkState: + NM_STATE_UNKNOWN = 0 + NM_STATE_ASLEEP = 1 + NM_STATE_CONNECTING = 2 + NM_STATE_CONNECTED = 3 + NM_STATE_DISCONNECTED = 4 + class BackgroundHandler(handler.Handler): """A Handler for non-interactive background updates. Runs the GUI if interaction is required.""" def __init__(self, title): handler.Handler.__init__(self) self.title = title + self.notification_service = None + self.network_manager = None try: import dbus import dbus.glib session_bus = dbus.SessionBus() + except Exception, ex: + info("Failed to import D-BUS bindings: %s", ex) + return + try: remote_object = session_bus.get_object('org.freedesktop.Notifications', '/org/freedesktop/Notifications') - - self.notification_service = dbus.Interface(remote_object, + + self.notification_service = dbus.Interface(remote_object, 'org.freedesktop.Notifications') # The Python bindings insist on printing a pointless introspection @@ -48,12 +61,27 @@ class BackgroundHandler(handler.Handler): self.notification_service.GetCapabilities() finally: sys.stderr = old_stderr + except Exception, ex: + info("No D-BUS notification service available: %s", ex) + + try: + system_bus = dbus.SystemBus() + remote_object = system_bus.get_object('org.freedesktop.NetworkManager', + '/org/freedesktop/NetworkManager') - self.have_notifications = True + self.network_manager = dbus.Interface(remote_object, + 'org.freedesktop.NetworkManager') except Exception, ex: - info("Failed to import D-BUS bindings: %s", ex) - self.have_notifications = False - + info("No D-BUS network manager service available: %s", ex) + + def get_network_state(self): + if self.network_manager: + try: + return self.network_manager.state() + except Exception, ex: + warn("Error getting network state: %s", ex) + return _NetworkState.NM_STATE_UNKNOWN + def confirm_trust_keys(self, interface, sigs, iface_xml): """Run the GUI if we need to confirm any keys.""" self.notify("Zero Install", "Can't update interface; signature not yet trusted. Running GUI...", timeout = 2) @@ -65,7 +93,7 @@ class BackgroundHandler(handler.Handler): def notify(self, title, message, timeout = 0, actions = []): """Send a D-BUS notification message if possible. If there is no notification service available, log the message instead.""" - if not self.have_notifications: + if not self.notification_service: info('%s: %s', title, message) return None @@ -98,7 +126,7 @@ def _detach(): pid, status = os.waitpid(child, 0) assert pid == child return True - + # The calling process might be waiting for EOF from its child. # Close our stdout so we don't keep it waiting. # Note: this only fixes the most common case; it could be waiting @@ -111,16 +139,29 @@ def _detach(): grandchild = os.fork() if grandchild: os._exit(0) # Parent's waitpid returns and grandchild continues - + return False def _check_for_updates(policy, verbose): root_iface = iface_cache.get_interface(policy.root).get_name() + + policy.handler = BackgroundHandler(root_iface) + info("Checking for updates to '%s' in a background process", root_iface) if verbose: - handler.notify("Zero Install", "Checking for updates to '%s'..." % root_iface, timeout = 1) + policy.handler.notify("Zero Install", "Checking for updates to '%s'..." % root_iface, timeout = 1) + + network_state = policy.handler.get_network_state() + if network_state != _NetworkState.NM_STATE_CONNECTED: + info("Not yet connected to network (status = %d). Sleeping for a bit...", network_state) + import time + time.sleep(20) + if network_state in (_NetworkState.NM_STATE_DISCONNECTED, _NetworkState.NM_STATE_ASLEEP): + info("Still not connected to network. Giving up.") + sys.exit(1) + else: + info("NetworkManager says we're on-line. Good!") - policy.handler = BackgroundHandler(root_iface) policy.freshness = 0 # Don't bother trying to refresh when getting the interface refresh = policy.refresh_all() # (causes confusing log messages) policy.handler.wait_for_blocker(refresh) @@ -133,7 +174,8 @@ def _check_for_updates(policy, verbose): policy.handler.notify("Zero Install", "No updates to download.", timeout = 1) sys.exit(0) - if not policy.handler.have_notifications: + if not policy.handler.notification_service: + # Can't ask the user to choose, so just notify them policy.handler.notify("Zero Install", "Updates ready to download for '%s'." % root_iface) sys.exit(0) -- 2.11.4.GIT