""" Check for updates in a background process. If we can start a program immediately, but some of our information is rather old (longer that the freshness threshold) then we run it anyway, and check for updates using a new process that runs quietly in the background. This avoids the need to annoy people with a 'checking for updates' box when they're trying to run things. """ import sys, os from logging import info from zeroinstall.injector.iface_cache import iface_cache from zeroinstall.injector import handler # Copyright (C) 2007, Thomas Leonard # See the README file for details, or visit http://0install.net. try: import dbus import dbus.glib session_bus = dbus.SessionBus() remote_object = session_bus.get_object('org.freedesktop.Notifications', '/org/freedesktop/Notifications') notification_service = dbus.Interface(remote_object, 'org.freedesktop.Notifications') # The Python bindings insist on printing a pointless introspection # warning to stderr if the service is missing. Force it to be done # now so we can skip it old_stderr = sys.stderr sys.stderr = None try: notification_service.GetCapabilities() finally: sys.stderr = old_stderr have_notifications = True except Exception, ex: info("Failed to import D-BUS bindings: %s", ex) have_notifications = False LOW = 0 NORMAL = 1 CRITICAL = 2 def notify(title, message, timeout = 0, actions = []): if not have_notifications: info('%s: %s', title, message) return None import time import dbus.types hints = {} if actions: hints['urgency'] = dbus.types.Byte(NORMAL) else: hints['urgency'] = dbus.types.Byte(LOW) return notification_service.Notify('Zero Install', 0, # replaces_id, '', # icon title, message, actions, hints, timeout * 1000) def _exec_gui(uri, *args): os.execvp('0launch', ['0launch', '--download-only', '--gui'] + list(args) + [uri]) class BackgroundHandler(handler.Handler): def __init__(self, title): handler.Handler.__init__(self) self.title = title def confirm_trust_keys(self, interface, sigs, iface_xml): notify("Zero Install", "Can't update interface; signature not yet trusted. Running GUI...", timeout = 2) _exec_gui(interface.uri, '--refresh') def report_error(self, exception): notify("Zero Install", "Error updating %s: %s" % (title, str(exception))) def _detach(): """Fork a detached grandchild. @return: True if we are the original.""" child = os.fork() if child: 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 # on any other FD as well. We should really use gobject.spawn_async # to close *all* FDs. null = os.open('/dev/null', os.O_RDWR) os.dup2(null, 1) os.close(null) 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() info("Checking for updates to '%s' in a background process", root_iface) if verbose: notify("Zero Install", "Checking for updates to '%s'..." % root_iface, timeout = 1) policy.handler = BackgroundHandler(root_iface) policy.freshness = 0 # Don't bother trying to refresh when getting the interface policy.refresh_all() # (causes confusing log messages) policy.handler.wait_for_downloads() # We could even download the archives here, but for now just # update the interfaces. if not policy.need_download(): if verbose: notify("Zero Install", "No updates to download.", timeout = 1) sys.exit(0) if not have_notifications: notify("Zero Install", "Updates ready to download for '%s'." % root_iface) sys.exit(0) import gobject ctx = gobject.main_context_default() loop = gobject.MainLoop(ctx) def _NotificationClosed(nid, *unused): if nid != our_question: return loop.quit() def _ActionInvoked(nid, action): if nid != our_question: return if action == 'download': _exec_gui(policy.root) loop.quit() notification_service.connect_to_signal('NotificationClosed', _NotificationClosed) notification_service.connect_to_signal('ActionInvoked', _ActionInvoked) our_question = notify("Zero Install", "Updates ready to download for '%s'." % root_iface, actions = ['download', 'Download']) loop.run() def spawn_background_update(policy, verbose): # Mark all feeds as being updated. Do this before forking, so that if someone is # running lots of 0launch commands in series on the same program we don't start # huge numbers of processes. import time from zeroinstall.injector import writer now = int(time.time()) for x in policy.implementation: x.last_check_attempt = now writer.save_interface(x) for f in policy.usable_feeds(x): feed_iface = iface_cache.get_interface(f.uri) feed_iface.last_check_attempt = now writer.save_interface(feed_iface) if _detach(): return try: try: _check_for_updates(policy, verbose) except SystemExit: raise except: import traceback traceback.print_exc() sys.stdout.flush() else: sys.exit(0) finally: os._exit(1)