When we start checking for updates on a feed, record the time. In the GUI, if this...
[zeroinstall.git] / zeroinstall / injector / background.py
blob32882a9a12e0addaa0029501cbc6813c88a5034d
1 """
2 Check for updates in a background process. If we can start a program immediately, but some of our information
3 is rather old (longer that the freshness threshold) then we run it anyway, and check for updates using a new
4 process that runs quietly in the background.
6 This avoids the need to annoy people with a 'checking for updates' box when they're trying to run things.
7 """
9 import sys, os
10 from logging import info
11 from zeroinstall.injector.iface_cache import iface_cache
12 from zeroinstall.injector import handler
14 # Copyright (C) 2007, Thomas Leonard
15 # See the README file for details, or visit http://0install.net.
17 try:
18 import dbus
19 import dbus.glib
21 session_bus = dbus.SessionBus()
23 remote_object = session_bus.get_object('org.freedesktop.Notifications',
24 '/org/freedesktop/Notifications')
26 notification_service = dbus.Interface(remote_object,
27 'org.freedesktop.Notifications')
29 # The Python bindings insist on printing a pointless introspection
30 # warning to stderr if the service is missing. Force it to be done
31 # now so we can skip it
32 old_stderr = sys.stderr
33 sys.stderr = None
34 try:
35 notification_service.GetCapabilities()
36 finally:
37 sys.stderr = old_stderr
39 #notification_service.connect_to_signal('NotificationClosed', _NotificationClosed)
40 #notification_service.connect_to_signal('ActionInvoked', _ActionInvoked)
42 have_notifications = True
43 except Exception, ex:
44 info("Failed to import D-BUS bindings: %s", ex)
45 have_notifications = False
47 LOW = 0
48 NORMAL = 1
49 CRITICAL = 2
51 def notify(title, message, timeout = 0, actions = []):
52 if not have_notifications:
53 info('%s: %s', title, message)
54 return
56 import time
57 import dbus.types
59 hints = {}
60 if actions:
61 hints['urgency'] = dbus.types.Byte(NORMAL)
62 else:
63 hints['urgency'] = dbus.types.Byte(LOW)
65 notification_service.Notify('Zero Install',
66 0, # replaces_id,
67 '', # icon
68 title,
69 message,
70 actions,
71 hints,
72 timeout * 1000)
74 def _exec_gui(uri, *args):
75 os.execvp('0launch', ['0launch', '--download-only', '--gui'] + list(args) + [uri])
77 class BackgroundHandler(handler.Handler):
78 def __init__(self, title):
79 handler.Handler.__init__(self)
80 self.title = title
82 def confirm_trust_keys(self, interface, sigs, iface_xml):
83 notify("Zero Install", "Can't update interface; signature not yet trusted. Running GUI...", timeout = 2)
84 _exec_gui(interface.uri, '--refresh')
86 def report_error(self, exception):
87 notify("Zero Install", "Error updating %s: %s" % (title, str(exception)))
89 def _detach():
90 """Fork a detached grandchild.
91 @return: True if we are the original."""
92 child = os.fork()
93 if child:
94 pid, status = os.waitpid(child, 0)
95 assert pid == child
96 return True
98 grandchild = os.fork()
99 if grandchild:
100 os._exit(0) # Parent's waitpid returns and grandchild continues
102 return False
104 def _check_for_updates(policy, verbose):
105 root_iface = iface_cache.get_interface(policy.root).get_name()
106 info("Checking for updates to '%s' in a background process", root_iface)
107 if verbose:
108 notify("Zero Install", "Checking for updates to '%s'..." % root_iface, timeout = 1)
110 policy.handler = BackgroundHandler(root_iface)
111 policy.refresh_all()
112 policy.handler.wait_for_downloads()
113 # We could even download the archives here, but for now just
114 # update the interfaces.
116 if not policy.need_download():
117 if verbose:
118 notify("Zero Install", "No updates to download.", timeout = 1)
119 sys.exit(0)
121 if not have_notifications:
122 notify("Zero Install", "Updates ready to download for '%s'." % root_iface)
123 sys.exit(0)
125 import gobject
126 ctx = gobject.main_context_default()
127 loop = gobject.MainLoop(ctx)
129 def _NotificationClosed(*unused):
130 loop.quit()
132 def _ActionInvoked(nid, action):
133 if action == 'download':
134 _exec_gui(policy.root)
135 loop.quit()
137 notification_service.connect_to_signal('NotificationClosed', _NotificationClosed)
138 notification_service.connect_to_signal('ActionInvoked', _ActionInvoked)
140 notify("Zero Install", "Updates ready to download for '%s'." % root_iface,
141 actions = ['download', 'Download'])
143 loop.run()
145 def spawn_background_update(policy, verbose):
146 # Mark all feeds as being updated. Do this before forking, so that if someone is
147 # running lots of 0launch commands in series on the same program we don't start
148 # huge numbers of processes.
149 import time
150 from zeroinstall.injector import writer
151 now = int(time.time())
152 for x in policy.implementation:
153 x.last_check_attempt = now
154 writer.save_interface(x)
156 for f in policy.usable_feeds(x):
157 feed_iface = iface_cache.get_interface(f.uri)
158 feed_iface.last_check_attempt = now
159 writer.save_interface(feed_iface)
161 if _detach():
162 return
164 try:
165 try:
166 _check_for_updates(policy, verbose)
167 except SystemExit:
168 raise
169 except:
170 import traceback
171 traceback.print_exc()
172 else:
173 sys.exit(0)
174 finally:
175 os._exit(1)