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{policy.Policy.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.
10 from logging
import info
, warn
11 from zeroinstall
.support
import tasks
12 from zeroinstall
.injector
.iface_cache
import iface_cache
13 from zeroinstall
.injector
import handler
15 # Copyright (C) 2009, Thomas Leonard
16 # See the README file for details, or visit http://0install.net.
19 return s
.replace('&', '&').replace('<', '<')
21 def _exec_gui(uri
, *args
):
22 os
.execvp('0launch', ['0launch', '--download-only', '--gui'] + list(args
) + [uri
])
27 NM_STATE_CONNECTING
= 2
28 NM_STATE_CONNECTED
= 3
29 NM_STATE_DISCONNECTED
= 4
31 class BackgroundHandler(handler
.Handler
):
32 """A Handler for non-interactive background updates. Runs the GUI if interaction is required."""
33 def __init__(self
, title
):
34 handler
.Handler
.__init
__(self
)
36 self
.notification_service
= None
37 self
.network_manager
= None
43 session_bus
= dbus
.SessionBus()
45 info("Failed to import D-BUS bindings: %s", ex
)
49 remote_object
= session_bus
.get_object('org.freedesktop.Notifications',
50 '/org/freedesktop/Notifications')
52 self
.notification_service
= dbus
.Interface(remote_object
,
53 'org.freedesktop.Notifications')
55 # The Python bindings insist on printing a pointless introspection
56 # warning to stderr if the service is missing. Force it to be done
57 # now so we can skip it
58 old_stderr
= sys
.stderr
61 self
.notification_service
.GetCapabilities()
63 sys
.stderr
= old_stderr
65 info("No D-BUS notification service available: %s", ex
)
68 system_bus
= dbus
.SystemBus()
69 remote_object
= system_bus
.get_object('org.freedesktop.NetworkManager',
70 '/org/freedesktop/NetworkManager')
72 self
.network_manager
= dbus
.Interface(remote_object
,
73 'org.freedesktop.NetworkManager')
75 info("No D-BUS network manager service available: %s", ex
)
77 def get_network_state(self
):
78 if self
.network_manager
:
80 return self
.network_manager
.state()
82 warn("Error getting network state: %s", ex
)
83 return _NetworkState
.NM_STATE_UNKNOWN
85 def confirm_trust_keys(self
, interface
, sigs
, iface_xml
):
86 """Run the GUI if we need to confirm any keys."""
87 self
.notify("Zero Install", "Can't update interface; signature not yet trusted. Running GUI...", timeout
= 2)
88 _exec_gui(interface
.uri
, '--refresh')
90 def report_error(self
, exception
, tb
= None):
91 self
.notify("Zero Install", "Error updating %s: %s" % (self
.title
, str(exception
)))
93 def notify(self
, title
, message
, timeout
= 0, actions
= []):
94 """Send a D-BUS notification message if possible. If there is no notification
95 service available, log the message instead."""
96 if not self
.notification_service
:
97 info('%s: %s', title
, message
)
108 hints
['urgency'] = dbus
.types
.Byte(NORMAL
)
110 hints
['urgency'] = dbus
.types
.Byte(LOW
)
112 return self
.notification_service
.Notify('Zero Install',
116 _escape_xml(message
),
122 """Fork a detached grandchild.
123 @return: True if we are the original."""
126 pid
, status
= os
.waitpid(child
, 0)
130 # The calling process might be waiting for EOF from its child.
131 # Close our stdout so we don't keep it waiting.
132 # Note: this only fixes the most common case; it could be waiting
133 # on any other FD as well. We should really use gobject.spawn_async
134 # to close *all* FDs.
135 null
= os
.open('/dev/null', os
.O_RDWR
)
139 grandchild
= os
.fork()
141 os
._exit
(0) # Parent's waitpid returns and grandchild continues
145 def _check_for_updates(policy
, verbose
):
146 root_iface
= iface_cache
.get_interface(policy
.root
).get_name()
148 policy
.handler
= BackgroundHandler(root_iface
)
150 info("Checking for updates to '%s' in a background process", root_iface
)
152 policy
.handler
.notify("Zero Install", "Checking for updates to '%s'..." % root_iface
, timeout
= 1)
154 network_state
= policy
.handler
.get_network_state()
155 if network_state
!= _NetworkState
.NM_STATE_CONNECTED
:
156 info("Not yet connected to network (status = %d). Sleeping for a bit...", network_state
)
159 if network_state
in (_NetworkState
.NM_STATE_DISCONNECTED
, _NetworkState
.NM_STATE_ASLEEP
):
160 info("Still not connected to network. Giving up.")
163 info("NetworkManager says we're on-line. Good!")
165 policy
.freshness
= 0 # Don't bother trying to refresh when getting the interface
166 refresh
= policy
.refresh_all() # (causes confusing log messages)
167 policy
.handler
.wait_for_blocker(refresh
)
169 # We could even download the archives here, but for now just
170 # update the interfaces.
172 if not policy
.need_download():
174 policy
.handler
.notify("Zero Install", "No updates to download.", timeout
= 1)
177 if not policy
.handler
.notification_service
:
178 # Can't ask the user to choose, so just notify them
179 policy
.handler
.notify("Zero Install", "Updates ready to download for '%s'." % root_iface
)
182 notification_closed
= tasks
.Blocker("wait for notification response")
184 def _NotificationClosed(nid
, *unused
):
185 if nid
!= our_question
: return
186 notification_closed
.trigger()
188 def _ActionInvoked(nid
, action
):
189 if nid
!= our_question
: return
190 if action
== 'download':
191 _exec_gui(policy
.root
)
192 notification_closed
.trigger()
194 policy
.handler
.notification_service
.connect_to_signal('NotificationClosed', _NotificationClosed
)
195 policy
.handler
.notification_service
.connect_to_signal('ActionInvoked', _ActionInvoked
)
197 our_question
= policy
.handler
.notify("Zero Install", "Updates ready to download for '%s'." % root_iface
,
198 actions
= ['download', 'Download'])
200 policy
.handler
.wait_for_blocker(notification_closed
)
202 def spawn_background_update(policy
, verbose
):
203 """Spawn a detached child process to check for updates.
204 @param policy: policy containing interfaces to update
205 @type policy: L{policy.Policy}
206 @param verbose: whether to notify the user about minor events
207 @type verbose: bool"""
208 # Mark all feeds as being updated. Do this before forking, so that if someone is
209 # running lots of 0launch commands in series on the same program we don't start
210 # huge numbers of processes.
211 for x
in policy
.implementation
:
212 iface_cache
.mark_as_checking(x
.uri
) # Main feed
213 for f
in policy
.usable_feeds(x
):
214 iface_cache
.mark_as_checking(f
.uri
) # Extra feeds
221 _check_for_updates(policy
, verbose
)
226 traceback
.print_exc()