Package zeroinstall :: Package injector :: Module background
[frames] | no frames]

Source Code for Module zeroinstall.injector.background

  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 L{config.Config.freshness} threshold) then we run it anyway, and check for updates using a new 
  4  process that runs quietly in the background. 
  5   
  6  This avoids the need to annoy people with a 'checking for updates' box when they're trying to run things. 
  7  """ 
  8   
  9  # Copyright (C) 2009, Thomas Leonard 
 10  # See the README file for details, or visit http://0install.net. 
 11   
 12  from zeroinstall import _ 
 13  import sys, os 
 14  from logging import info, warn 
 15  from zeroinstall.support import tasks 
 16  from zeroinstall.injector import handler 
 17   
18 -def _escape_xml(s):
19 return s.replace('&', '&amp;').replace('<', '&lt;')
20
21 -def _exec_gui(uri, *args):
22 parent_dir = os.path.dirname(os.path.dirname(__file__)) 23 child_args = [sys.executable, '-u', os.path.join(parent_dir, '0launch-gui/0launch-gui')] + list(args) + [uri] 24 os.execvp(sys.executable, child_args)
25
26 -class _NetworkState:
27 NM_STATE_UNKNOWN = 0 28 NM_STATE_ASLEEP = 10 29 NM_STATE_DISCONNECTED = 20 30 NM_STATE_DISCONNECTING = 30 31 NM_STATE_CONNECTING = 40 32 NM_STATE_CONNECTED_LOCAL = 50 33 NM_STATE_CONNECTED_SITE = 60 34 NM_STATE_CONNECTED_GLOBAL = 70 35 36 # Maps enum values from version <= 0.8 to current (0.9) values 37 v0_8 = { 38 0: NM_STATE_UNKNOWN, 39 1: NM_STATE_ASLEEP, 40 2: NM_STATE_CONNECTING, 41 3: NM_STATE_CONNECTED_GLOBAL, 42 4: NM_STATE_DISCONNECTED, 43 }
44 45
46 -class BackgroundHandler(handler.Handler):
47 """A Handler for non-interactive background updates. Runs the GUI if interaction is required."""
48 - def __init__(self, title, root):
49 handler.Handler.__init__(self) 50 self.title = title 51 self.notification_service = None 52 self.network_manager = None 53 self.notification_service_caps = [] 54 self.root = root # If we need to confirm any keys, run the GUI on this 55 56 try: 57 import dbus 58 import dbus.glib 59 except Exception as ex: 60 info(_("Failed to import D-BUS bindings: %s"), ex) 61 return 62 63 try: 64 session_bus = dbus.SessionBus() 65 remote_object = session_bus.get_object('org.freedesktop.Notifications', 66 '/org/freedesktop/Notifications') 67 68 self.notification_service = dbus.Interface(remote_object, 69 'org.freedesktop.Notifications') 70 71 # The Python bindings insist on printing a pointless introspection 72 # warning to stderr if the service is missing. Force it to be done 73 # now so we can skip it 74 old_stderr = sys.stderr 75 sys.stderr = None 76 try: 77 self.notification_service_caps = [str(s) for s in 78 self.notification_service.GetCapabilities()] 79 finally: 80 sys.stderr = old_stderr 81 except Exception as ex: 82 info(_("No D-BUS notification service available: %s"), ex) 83 84 try: 85 system_bus = dbus.SystemBus() 86 remote_object = system_bus.get_object('org.freedesktop.NetworkManager', 87 '/org/freedesktop/NetworkManager') 88 89 self.network_manager = dbus.Interface(remote_object, 90 'org.freedesktop.NetworkManager') 91 except Exception as ex: 92 info(_("No D-BUS network manager service available: %s"), ex)
93
94 - def get_network_state(self):
95 if self.network_manager: 96 try: 97 state = self.network_manager.state() 98 if state < 10: 99 state = _NetworkState.v0_8.get(state, 100 _NetworkState.NM_STATE_UNKNOWN) 101 return state 102 103 except Exception as ex: 104 warn(_("Error getting network state: %s"), ex) 105 return _NetworkState.NM_STATE_UNKNOWN
106
107 - def confirm_import_feed(self, pending, valid_sigs):
108 """Run the GUI if we need to confirm any keys.""" 109 info(_("Can't update feed; signature not yet trusted. Running GUI...")) 110 _exec_gui(self.root, '--download', '--refresh', '--systray')
111
112 - def report_error(self, exception, tb = None):
113 from zeroinstall.injector import download 114 if isinstance(exception, download.DownloadError): 115 tb = None 116 117 if tb: 118 import traceback 119 details = '\n' + '\n'.join(traceback.format_exception(type(exception), exception, tb)) 120 else: 121 try: 122 details = unicode(exception) 123 except: 124 details = repr(exception) 125 self.notify("Zero Install", _("Error updating %(title)s: %(details)s") % {'title': self.title, 'details': details.replace('<', '&lt;')})
126
127 - def notify(self, title, message, timeout = 0, actions = []):
128 """Send a D-BUS notification message if possible. If there is no notification 129 service available, log the message instead.""" 130 if not self.notification_service: 131 info('%s: %s', title, message) 132 return None 133 134 LOW = 0 135 NORMAL = 1 136 #CRITICAL = 2 137 138 import dbus.types 139 140 hints = {} 141 if actions: 142 hints['urgency'] = dbus.types.Byte(NORMAL) 143 else: 144 hints['urgency'] = dbus.types.Byte(LOW) 145 146 return self.notification_service.Notify('Zero Install', 147 0, # replaces_id, 148 '', # icon 149 _escape_xml(title), 150 _escape_xml(message), 151 actions, 152 hints, 153 timeout * 1000)
154
155 -def _detach():
156 """Fork a detached grandchild. 157 @return: True if we are the original.""" 158 child = os.fork() 159 if child: 160 pid, status = os.waitpid(child, 0) 161 assert pid == child 162 return True 163 164 # The calling process might be waiting for EOF from its child. 165 # Close our stdout so we don't keep it waiting. 166 # Note: this only fixes the most common case; it could be waiting 167 # on any other FD as well. We should really use gobject.spawn_async 168 # to close *all* FDs. 169 null = os.open('/dev/null', os.O_RDWR) 170 os.dup2(null, 1) 171 os.close(null) 172 173 grandchild = os.fork() 174 if grandchild: 175 os._exit(0) # Parent's waitpid returns and grandchild continues 176 177 return False
178
179 -def _check_for_updates(requirements, verbose):
180 from zeroinstall.injector.driver import Driver 181 from zeroinstall.injector.config import load_config 182 183 background_handler = BackgroundHandler(requirements.interface_uri, requirements.interface_uri) 184 background_config = load_config(background_handler) 185 root_iface = background_config.iface_cache.get_interface(requirements.interface_uri).get_name() 186 background_handler.title = root_iface 187 188 driver = Driver(config = background_config, requirements = requirements) 189 190 info(_("Checking for updates to '%s' in a background process"), root_iface) 191 if verbose: 192 background_handler.notify("Zero Install", _("Checking for updates to '%s'...") % root_iface, timeout = 1) 193 194 network_state = background_handler.get_network_state() 195 if network_state not in (_NetworkState.NM_STATE_CONNECTED_SITE, _NetworkState.NM_STATE_CONNECTED_GLOBAL): 196 info(_("Not yet connected to network (status = %d). Sleeping for a bit..."), network_state) 197 import time 198 time.sleep(120) 199 if network_state in (_NetworkState.NM_STATE_DISCONNECTED, _NetworkState.NM_STATE_ASLEEP): 200 info(_("Still not connected to network. Giving up.")) 201 sys.exit(1) 202 else: 203 info(_("NetworkManager says we're on-line. Good!")) 204 205 background_config.freshness = 0 # Don't bother trying to refresh when getting the interface 206 refresh = driver.solve_with_downloads(force = True) # (causes confusing log messages) 207 tasks.wait_for_blocker(refresh) 208 209 # We could even download the archives here, but for now just 210 # update the interfaces. 211 212 if not driver.need_download(): 213 if verbose: 214 background_handler.notify("Zero Install", _("No updates to download."), timeout = 1) 215 sys.exit(0) 216 217 background_handler.notify("Zero Install", 218 _("Updates ready to download for '%s'.") % root_iface, 219 timeout = 1) 220 _exec_gui(requirements.interface_uri, '--refresh', '--systray') 221 sys.exit(1)
222
223 -def spawn_background_update(driver, verbose):
224 """Spawn a detached child process to check for updates. 225 @param driver: driver containing interfaces to update 226 @type driver: L{driver.Driver} 227 @param verbose: whether to notify the user about minor events 228 @type verbose: bool 229 @since: 1.5 (used to take a Policy)""" 230 iface_cache = driver.config.iface_cache 231 # Mark all feeds as being updated. Do this before forking, so that if someone is 232 # running lots of 0launch commands in series on the same program we don't start 233 # huge numbers of processes. 234 for uri in driver.solver.feeds_used: 235 iface_cache.mark_as_checking(uri) 236 237 if _detach(): 238 return 239 240 try: 241 try: 242 _check_for_updates(driver.requirements, verbose) 243 except SystemExit: 244 raise 245 except: 246 import traceback 247 traceback.print_exc() 248 sys.stdout.flush() 249 finally: 250 os._exit(1)
251