1 """
2 PackageKit integration.
3 """
4
5
6
7
8 import os, sys
9 import locale
10 import logging
11 from zeroinstall import _, SafeException
12
13 from zeroinstall.support import tasks
14 from zeroinstall.injector import download, model
15
16 _logger_pk = logging.getLogger('packagekit')
17
18 try:
19 import dbus
20 import dbus.mainloop.glib
21 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
22 except Exception as ex:
23 _logger_pk.info("D-BUS not available: %s", ex)
24 dbus = None
25
26 MAX_PACKAGE_KIT_TRANSACTION_SIZE = 100
30 self._pk = False
31
32 self._candidates = {}
33
34
35
36 self._next_batch = set()
37
38 @property
40 return self.pk is not None
41
42 @property
44 if self._pk is False:
45 if dbus is None:
46 self._pk = None
47 else:
48 try:
49 self._pk = dbus.Interface(dbus.SystemBus().get_object(
50 'org.freedesktop.PackageKit',
51 '/org/freedesktop/PackageKit', False),
52 'org.freedesktop.PackageKit')
53 _logger_pk.info(_('PackageKit dbus service found'))
54 except Exception as ex:
55 _logger_pk.info(_('PackageKit dbus service not found: %s'), ex)
56 self._pk = None
57 return self._pk
58
60 """Add any cached candidates.
61 The candidates are those discovered by a previous call to L{fetch_candidates}.
62 @param package_name: the distribution's name for the package
63 @param factory: a function to add a new implementation to the feed
64 @param prefix: the prefix for the implementation's ID
65 """
66 candidates = self._candidates.get(package_name, None)
67 if candidates is None:
68 return
69
70 if isinstance(candidates, tasks.Blocker):
71 return
72
73 for candidate in candidates:
74 impl_name = '%s:%s:%s:%s' % (prefix, package_name, candidate['version'], candidate['arch'])
75
76 impl = factory(impl_name, only_if_missing = True, installed = candidate['installed'])
77 if impl is None:
78
79 return
80
81 impl.version = model.parse_version(candidate['version'])
82 if candidate['arch'] != '*':
83 impl.machine = candidate['arch']
84
85 def install(handler):
86 packagekit_id = candidate['packagekit_id']
87 dl = PackageKitDownload('packagekit:' + packagekit_id, hint = impl, pk = self.pk, packagekit_id = packagekit_id, expected_size = candidate['size'])
88 handler.monitor_download(dl)
89 return dl.downloaded
90 impl.download_sources.append(model.DistributionSource(package_name, candidate['size'], install))
91
92 @tasks.async
94 assert self.pk
95
96
97 self._next_batch |= set(package_names)
98 yield
99 package_names = self._next_batch
100 self._next_batch = set()
101
102
103
104 known = [self._candidates[p] for p in package_names if p in self._candidates]
105
106
107 in_progress = list(set([b for b in known if isinstance(b, tasks.Blocker)]))
108 _logger_pk.debug('Already downloading: %s', in_progress)
109
110
111 package_names = [p for p in package_names if p not in self._candidates]
112
113 def do_batch(package_names):
114
115 versions = {}
116
117 blocker = None
118
119 def error_cb(sender):
120
121 _logger_pk.info(_('Transaction failed: %s(%s)'), sender.error_code, sender.error_details)
122 blocker.trigger()
123
124 def details_cb(sender):
125 for packagekit_id, info in versions.items():
126 if packagekit_id in sender.details:
127 info.update(sender.details[packagekit_id])
128 info['packagekit_id'] = packagekit_id
129 if (info['name'] not in self._candidates or
130 isinstance(self._candidates[info['name']], tasks.Blocker)):
131 self._candidates[info['name']] = [info]
132 else:
133 self._candidates[info['name']].append(info)
134 else:
135 _logger_pk.info(_('Empty details for %s'), packagekit_id)
136 blocker.trigger()
137
138 def resolve_cb(sender):
139 if sender.package:
140 for packagekit_id, info in sender.package.iteritems():
141 parts = packagekit_id.split(';', 3)
142 if ':' in parts[3]:
143 parts[3] = parts[3].split(':', 1)[0]
144 packagekit_id = ';'.join(parts)
145 versions[packagekit_id] = info
146 tran = _PackageKitTransaction(self.pk, details_cb, error_cb)
147 tran.proxy.GetDetails(versions.keys())
148 else:
149 _logger_pk.info(_('Empty resolve for %s'), package_names)
150 blocker.trigger()
151
152
153 blocker = tasks.Blocker('PackageKit %s' % package_names)
154 for package in package_names:
155 self._candidates[package] = blocker
156
157 try:
158 _logger_pk.debug(_('Ask for %s'), package_names)
159 tran = _PackageKitTransaction(self.pk, resolve_cb, error_cb)
160 tran.proxy.Resolve('none', package_names)
161
162 in_progress.append(blocker)
163 except:
164 __, ex, tb = sys.exc_info()
165 blocker.trigger((ex, tb))
166 raise
167
168
169
170
171 while package_names:
172 next_batch = package_names[:MAX_PACKAGE_KIT_TRANSACTION_SIZE]
173 package_names = package_names[MAX_PACKAGE_KIT_TRANSACTION_SIZE:]
174 do_batch(next_batch)
175
176 while in_progress:
177 yield in_progress
178 in_progress = [b for b in in_progress if not b.happened]
179
181 - def __init__(self, url, hint, pk, packagekit_id, expected_size):
182 self.url = url
183 self.status = download.download_fetching
184 self.hint = hint
185 self.aborted_by_user = False
186
187 self.downloaded = None
188
189 self.expected_size = expected_size
190
191 self.packagekit_id = packagekit_id
192 self._impl = hint
193 self._transaction = None
194 self.pk = pk
195
196 def error_cb(sender):
197 self.status = download.download_failed
198 ex = SafeException('PackageKit install failed: %s' % (sender.error_details or sender.error_code))
199 self.downloaded.trigger(exception = (ex, None))
200
201 def installed_cb(sender):
202 self._impl.installed = True
203 self.status = download.download_complete
204 self.downloaded.trigger()
205
206 def install_packages():
207 package_name = self.packagekit_id
208 self._transaction = _PackageKitTransaction(self.pk, installed_cb, error_cb)
209 self._transaction.compat_call([
210 ('InstallPackages', False, [package_name]),
211 ('InstallPackages', [package_name]),
212 ])
213
214 _auth_wrapper(install_packages)
215
216 self.downloaded = tasks.Blocker('PackageKit install %s' % self.packagekit_id)
217
224
226 if self._transaction is None:
227 return None
228 percentage = self._transaction.getPercentage()
229 if percentage > 100:
230 return None
231 else:
232 return float(percentage) / 100.
233
235 fraction = self.get_current_fraction()
236 if fraction is None:
237 return 0
238 else:
239 if self.expected_size is None:
240 return 0
241 return int(self.expected_size * fraction)
242
244 try:
245 return method(*args)
246 except dbus.exceptions.DBusException as e:
247 if e.get_dbus_name() != \
248 'org.freedesktop.PackageKit.Transaction.RefusedByPolicy':
249 raise
250
251 iface, auth = e.get_dbus_message().split()
252 if not auth.startswith('auth_'):
253 raise
254
255 _logger_pk.debug(_('Authentication required for %s'), auth)
256
257 pk_auth = dbus.SessionBus().get_object(
258 'org.freedesktop.PolicyKit.AuthenticationAgent', '/',
259 'org.gnome.PolicyKit.AuthorizationManager.SingleInstance')
260
261 if not pk_auth.ObtainAuthorization(iface, dbus.UInt32(0),
262 dbus.UInt32(os.getpid()), timeout=300):
263 raise
264
265 return method(*args)
266
268 - def __init__(self, pk, finished_cb=None, error_cb=None):
269 self._finished_cb = finished_cb
270 self._error_cb = error_cb
271 self.error_code = None
272 self.error_details = None
273 self.package = {}
274 self.details = {}
275 self.files = {}
276
277 self.object = dbus.SystemBus().get_object(
278 'org.freedesktop.PackageKit', pk.GetTid(), False)
279 self.proxy = dbus.Interface(self.object,
280 'org.freedesktop.PackageKit.Transaction')
281 self._props = dbus.Interface(self.object, dbus.PROPERTIES_IFACE)
282
283 self._signals = []
284 for signal, cb in [('Finished', self.__finished_cb),
285 ('ErrorCode', self.__error_code_cb),
286 ('StatusChanged', self.__status_changed_cb),
287 ('Package', self.__package_cb),
288 ('Details', self.__details_cb),
289 ('Files', self.__files_cb)]:
290 self._signals.append(self.proxy.connect_to_signal(signal, cb))
291
292 defaultlocale = locale.getdefaultlocale()[0]
293 if defaultlocale is not None:
294 self.compat_call([
295 ('SetHints', ['locale=%s' % defaultlocale]),
296 ('SetLocale', defaultlocale),
297 ])
298
300 result = self.get_prop('Percentage')
301 if result is None:
302 result, __, __, __ = self.proxy.GetProgress()
303 return result
304
305 - def get_prop(self, prop, default = None):
306 try:
307 return self._props.Get('org.freedesktop.PackageKit.Transaction', prop)
308 except:
309 return default
310
311
312
314 for call in calls:
315 method = call[0]
316 args = call[1:]
317 try:
318 dbus_method = self.proxy.get_dbus_method(method)
319 return dbus_method(*args)
320 except dbus.exceptions.DBusException as e:
321 if e.get_dbus_name() not in (
322 'org.freedesktop.DBus.Error.UnknownMethod',
323 'org.freedesktop.DBus.Error.InvalidArgs'):
324 raise
325 raise Exception('Cannot call %r DBus method' % calls)
326
328 _logger_pk.debug(_('Transaction finished: %s'), exit)
329
330 for i in self._signals:
331 i.remove()
332
333 if self.error_code is not None:
334 self._error_cb(self)
335 else:
336 self._finished_cb(self)
337
339 _logger_pk.info(_('Transaction failed: %s(%s)'), details, code)
340 self.error_code = code
341 self.error_details = details
342
344 try:
345 from zeroinstall.injector import distro
346
347 package_name, version, arch, repo_ = id.split(';')
348 clean_version = distro.try_cleanup_distro_version(version)
349 if not clean_version:
350 _logger_pk.info(_("Can't parse distribution version '%(version)s' for package '%(package)s'"), {'version': version, 'package': package_name})
351 return
352 clean_arch = distro.canonical_machine(arch)
353 package = {'version': clean_version,
354 'name': package_name,
355 'arch': clean_arch,
356 'installed': (status == 'installed')}
357 _logger_pk.debug(_('Package: %s %r'), id, package)
358 self.package[str(id)] = package
359 except Exception as ex:
360 _logger_pk.warn("__package_cb(%s, %s, %s): %s", status, id, summary, ex)
361
362 - def __details_cb(self, id, licence, group, detail, url, size):
363 details = {'licence': str(licence),
364 'group': str(group),
365 'detail': str(detail),
366 'url': str(url),
367 'size': int(size)}
368 _logger_pk.debug(_('Details: %s %r'), id, details)
369 self.details[id] = details
370
372 self.files[id] = files.split(';')
373
376