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 _, logger 
 13  import sys, os 
 14  from zeroinstall.support import tasks 
 15  from zeroinstall.injector import handler 
 16   
17 -def _escape_xml(s):
18 return s.replace('&', '&amp;').replace('<', '&lt;')
19
20 -class _NetworkState(object):
21 NM_STATE_UNKNOWN = 0 22 NM_STATE_ASLEEP = 10 23 NM_STATE_DISCONNECTED = 20 24 NM_STATE_DISCONNECTING = 30 25 NM_STATE_CONNECTING = 40 26 NM_STATE_CONNECTED_LOCAL = 50 27 NM_STATE_CONNECTED_SITE = 60 28 NM_STATE_CONNECTED_GLOBAL = 70 29 30 # Maps enum values from version <= 0.8 to current (0.9) values 31 v0_8 = { 32 0: NM_STATE_UNKNOWN, 33 1: NM_STATE_ASLEEP, 34 2: NM_STATE_CONNECTING, 35 3: NM_STATE_CONNECTED_GLOBAL, 36 4: NM_STATE_DISCONNECTED, 37 }
38
39 -class BackgroundHandler(handler.Handler):
40 """A Handler for non-interactive background updates. Runs the GUI if interaction is required."""
41 - def __init__(self, title, root):
42 """@type title: str 43 @type root: str""" 44 handler.Handler.__init__(self) 45 self.title = title 46 self.notification_service = None 47 self.network_manager = None 48 self.notification_service_caps = [] 49 self.root = root # If we need to confirm any keys, run the GUI on this 50 self.need_gui = False 51 52 try: 53 import dbus 54 try: 55 from dbus.mainloop.glib import DBusGMainLoop 56 DBusGMainLoop(set_as_default=True) 57 except ImportError: 58 import dbus.glib # Python 2 59 except Exception as ex: 60 logger.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 logger.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 logger.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 logger.warning(_("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 @type pending: L{zeroinstall.injector.iface_cache.PendingFeed}""" 110 111 if os.environ.get('DISPLAY', None): 112 logger.info(_("Can't update feed; signature not yet trusted. Running GUI...")) 113 114 self.need_gui = True 115 116 for dl in self.monitored_downloads: 117 dl.abort() 118 119 raise handler.NoTrustedKeys("need to switch to GUI to confirm keys") 120 else: 121 raise handler.NoTrustedKeys(_("Background update for {iface} needed to confirm keys, but no GUI available!").format( 122 iface = self.root))
123 124
125 - def report_error(self, exception, tb = None):
126 from zeroinstall.injector import download 127 if isinstance(exception, download.DownloadError): 128 tb = None 129 130 if tb: 131 import traceback 132 details = '\n' + '\n'.join(traceback.format_exception(type(exception), exception, tb)) 133 else: 134 try: 135 details = unicode(exception) 136 except: 137 details = repr(exception) 138 self.notify("Zero Install", _("Error updating %(title)s: %(details)s") % {'title': self.title, 'details': details.replace('<', '&lt;')})
139
140 - def notify(self, title, message, timeout = 0, actions = []):
141 """Send a D-BUS notification message if possible. If there is no notification 142 service available, log the message instead. 143 @type title: str 144 @type message: str 145 @type timeout: int""" 146 if not self.notification_service: 147 logger.info('%s: %s', title, message) 148 return None 149 150 LOW = 0 151 NORMAL = 1 152 #CRITICAL = 2 153 154 import dbus.types 155 156 hints = {} 157 if actions: 158 hints['urgency'] = dbus.types.Byte(NORMAL) 159 else: 160 hints['urgency'] = dbus.types.Byte(LOW) 161 162 return self.notification_service.Notify('Zero Install', 163 0, # replaces_id, 164 '', # icon 165 _escape_xml(title), 166 _escape_xml(message), 167 actions, 168 hints, 169 timeout * 1000)
170
171 -def _detach():
172 """Fork a detached grandchild. 173 @return: True if we are the original.""" 174 child = os.fork() 175 if child: 176 pid, status = os.waitpid(child, 0) 177 assert pid == child 178 return True 179 180 # The calling process might be waiting for EOF from its child. 181 # Close our stdout so we don't keep it waiting. 182 # Note: this only fixes the most common case; it could be waiting 183 # on any other FD as well. We should really use gobject.spawn_async 184 # to close *all* FDs. 185 null = os.open(os.devnull, os.O_RDWR) 186 os.dup2(null, 1) 187 os.close(null) 188 189 grandchild = os.fork() 190 if grandchild: 191 os._exit(0) # Parent's waitpid returns and grandchild continues 192 193 return False
194
195 -def _check_for_updates(requirements, verbose, app):
196 """@type requirements: L{zeroinstall.injector.requirements.Requirements} 197 @type verbose: bool 198 @type app: L{zeroinstall.apps.App}""" 199 if app is not None: 200 old_sels = app.get_selections() 201 202 from zeroinstall.injector.driver import Driver 203 from zeroinstall.injector.config import load_config 204 205 background_handler = BackgroundHandler(requirements.interface_uri, requirements.interface_uri) 206 background_config = load_config(background_handler) 207 root_iface = background_config.iface_cache.get_interface(requirements.interface_uri).get_name() 208 background_handler.title = root_iface 209 210 driver = Driver(config = background_config, requirements = requirements) 211 212 logger.info(_("Checking for updates to '%s' in a background process"), root_iface) 213 if verbose: 214 background_handler.notify("Zero Install", _("Checking for updates to '%s'...") % root_iface, timeout = 1) 215 216 network_state = background_handler.get_network_state() 217 if network_state not in (_NetworkState.NM_STATE_CONNECTED_SITE, _NetworkState.NM_STATE_CONNECTED_GLOBAL): 218 logger.info(_("Not yet connected to network (status = %d). Sleeping for a bit..."), network_state) 219 import time 220 time.sleep(120) 221 if network_state in (_NetworkState.NM_STATE_DISCONNECTED, _NetworkState.NM_STATE_ASLEEP): 222 logger.info(_("Still not connected to network. Giving up.")) 223 sys.exit(1) 224 else: 225 logger.info(_("NetworkManager says we're on-line. Good!")) 226 227 background_config.freshness = 0 # Don't bother trying to refresh when getting the interface 228 refresh = driver.solve_with_downloads(force = True) # (causes confusing log messages) 229 tasks.wait_for_blocker(refresh) 230 231 if background_handler.need_gui or not driver.solver.ready or driver.get_uncached_implementations(): 232 if verbose: 233 background_handler.notify("Zero Install", 234 _("Updates ready to download for '%s'.") % root_iface, 235 timeout = 1) 236 237 # Run the GUI if possible... 238 from zeroinstall import helpers 239 gui_args = ['--refresh', '--systray', '--download'] + requirements.get_as_options() 240 new_sels = helpers.get_selections_gui(requirements.interface_uri, gui_args, use_gui = None) 241 if new_sels is None: 242 sys.exit(0) # Cancelled by user 243 elif new_sels is helpers.DontUseGUI: 244 if not driver.solver.ready: 245 background_handler.notify("Zero Install", _("Can't update '%s'") % root_iface) 246 sys.exit(1) 247 248 tasks.wait_for_blocker(driver.download_uncached_implementations()) 249 new_sels = driver.solver.selections 250 251 if app is None: 252 background_handler.notify("Zero Install", 253 _("{name} updated.").format(name = root_iface), 254 timeout = 1) 255 else: 256 if verbose: 257 background_handler.notify("Zero Install", _("No updates to download."), timeout = 1) 258 new_sels = driver.solver.selections 259 260 if app is not None: 261 assert driver.solver.ready 262 from zeroinstall.support import xmltools 263 if not xmltools.nodes_equal(new_sels.toDOM(), old_sels.toDOM()): 264 app.set_selections(new_sels) 265 background_handler.notify("Zero Install", 266 _("{app} updated.").format(app = app.get_name()), 267 timeout = 1) 268 app.set_last_checked() 269 sys.exit(0)
270 271
272 -def spawn_background_update(driver, verbose):
273 """Spawn a detached child process to check for updates. 274 @param driver: driver containing interfaces to update 275 @type driver: L{driver.Driver} 276 @param verbose: whether to notify the user about minor events 277 @type verbose: bool 278 @since: 1.5 (used to take a Policy)""" 279 iface_cache = driver.config.iface_cache 280 # Mark all feeds as being updated. Do this before forking, so that if someone is 281 # running lots of 0launch commands in series on the same program we don't start 282 # huge numbers of processes. 283 for uri in driver.solver.feeds_used: 284 iface_cache.mark_as_checking(uri) 285 286 spawn_background_update2(driver.requirements, verbose)
287
288 -def spawn_background_update2(requirements, verbose, app = None):
289 """Spawn a detached child process to check for updates. 290 @param requirements: requirements for the new selections 291 @type requirements: L{requirements.Requirements} 292 @param verbose: whether to notify the user about minor events 293 @type verbose: bool 294 @param app: application to update (if any) 295 @type app: L{apps.App} | None 296 @since: 1.9""" 297 if _detach(): 298 return 299 300 try: 301 try: 302 _check_for_updates(requirements, verbose, app) 303 except SystemExit: 304 raise 305 except: 306 import traceback 307 traceback.print_exc() 308 sys.stdout.flush() 309 finally: 310 os._exit(1)
311