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
10
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
19 return s.replace('&', '&').replace('<', '<')
20
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
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
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
47 """A Handler for non-interactive background updates. Runs the GUI if interaction is required."""
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
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
72
73
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
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
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
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
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,
148 '',
149 _escape_xml(title),
150 _escape_xml(message),
151 actions,
152 hints,
153 timeout * 1000)
154
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
165
166
167
168
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)
176
177 return False
178
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
206 refresh = driver.solve_with_downloads(force = True)
207 tasks.wait_for_blocker(refresh)
208
209
210
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
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
232
233
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