2 PackageKit integration.
5 # Copyright (C) 2010, Aleksey Lim
6 # See the README file for details, or visit http://0install.net.
12 import dbus
.mainloop
.glib
13 from zeroinstall
import _
, SafeException
15 from zeroinstall
.support
import tasks
16 from zeroinstall
.injector
import download
, model
18 dbus
.mainloop
.glib
.DBusGMainLoop(set_as_default
=True)
19 _logger_pk
= logging
.getLogger('packagekit')
25 self
._candidates
= {} # { package_name : (version, arch, size) | Blocker }
29 return self
.pk
is not None
35 self
._pk
= dbus
.Interface(dbus
.SystemBus().get_object(
36 'org.freedesktop.PackageKit',
37 '/org/freedesktop/PackageKit', False),
38 'org.freedesktop.PackageKit')
39 _logger_pk
.info(_('PackageKit dbus service found'))
41 _logger_pk
.info(_('PackageKit dbus service not found'))
45 def get_candidates(self
, package_name
, factory
, prefix
):
46 """Add any cached candidates.
47 The candidates are those discovered by a previous call to L{fetch_candidates}.
48 @param package_name: the distribution's name for the package
49 @param factory: a function to add a new implementation to the feed
50 @param prefix: the prefix for the implementation's ID
52 candidate
= self
._candidates
.get(package_name
, None)
56 if isinstance(candidate
, tasks
.Blocker
):
57 return # Fetch still in progress
59 impl_name
= '%s:%s:%s' % (prefix
, package_name
, candidate
['version'])
61 impl
= factory(impl_name
, only_if_missing
= True, installed
= candidate
['installed'])
63 # (checking this way because the cached candidate['installed'] may be stale)
64 return # Already installed
66 impl
.version
= model
.parse_version(candidate
['version'])
67 if candidate
['arch'] != '*':
68 impl
.machine
= candidate
['arch']
71 packagekit_id
= candidate
['packagekit_id']
72 def download_factory(url
, hint
):
73 return PackageKitDownload(url
, hint
, pk
= self
.pk
, packagekit_id
= packagekit_id
)
74 dl
= handler
.get_download('packagekit:' + packagekit_id
, factory
= download_factory
, hint
= impl
)
75 dl
.expected_size
= candidate
['size']
77 impl
.download_sources
.append(model
.DistributionSource(package_name
, candidate
['size'], install
))
80 def fetch_candidates(self
, package_names
):
83 known
= [self
._candidates
[p
] for p
in package_names
if p
in self
._candidates
]
84 in_progress
= [b
for b
in known
if isinstance(b
, tasks
.Blocker
)]
85 _logger_pk
.debug('Already downloading: %s', in_progress
)
87 # Filter out the ones we've already fetched
88 package_names
= [p
for p
in package_names
if p
not in self
._candidates
]
96 # Note: probably just means the package wasn't found
97 _logger_pk
.info(_('Transaction failed: %s(%s)'), sender
.error_code
, sender
.error_details
)
100 def details_cb(sender
):
102 for packagekit_id
, info
in versions
.items():
103 info
.update(sender
.details
[packagekit_id
])
104 info
['packagekit_id'] = packagekit_id
105 self
._candidates
[info
['name']] = info
107 _logger_pk
.warn(_('Empty details for %s'), package_names
)
110 def resolve_cb(sender
):
112 versions
.update(sender
.package
)
113 tran
= _PackageKitTransaction(self
.pk
, details_cb
, error_cb
)
114 tran
.proxy
.GetDetails(versions
.keys())
116 _logger_pk
.info(_('Empty resolve for %s'), package_names
)
120 blocker
= tasks
.Blocker('PackageKit %s' % package_names
)
121 for package
in package_names
:
122 self
._candidates
[package
] = blocker
124 _logger_pk
.debug(_('Ask for %s'), package_names
)
125 tran
= _PackageKitTransaction(self
.pk
, resolve_cb
, error_cb
)
126 tran
.proxy
.Resolve('none', package_names
)
128 in_progress
.append(blocker
)
132 in_progress
= [b
for b
in in_progress
if not b
.happened
]
134 class PackageKitDownload(download
.Download
):
135 def __init__(self
, url
, hint
, pk
, packagekit_id
):
136 download
.Download
.__init
__(self
, url
, hint
)
138 self
.packagekit_id
= packagekit_id
140 self
._transaction
= None
144 assert self
.status
== download
.download_starting
145 assert self
.downloaded
is None
147 def error_cb(sender
):
148 self
.status
= download
.download_failed
149 ex
= SafeException('PackageKit install failed: %s' % (sender
.error_details
or sender
.error_code
))
150 self
.downloaded
.trigger(exception
= (ex
, None))
152 def installed_cb(sender
):
153 self
._impl
.installed
= True;
154 self
.status
= download
.download_complete
155 self
.downloaded
.trigger()
157 def install_packages():
158 package_name
= self
.packagekit_id
159 self
._transaction
= _PackageKitTransaction(self
.pk
, installed_cb
, error_cb
)
160 self
._transaction
.compat_call('InstallPackages',
161 [([package_name
]), (False, [package_name
])])
163 _auth_wrapper(install_packages
)
165 self
.status
= download
.download_fetching
166 self
.downloaded
= tasks
.Blocker('PackageKit install %s' % self
.packagekit_id
)
169 _logger_pk
.debug(_('Cancel transaction'))
170 self
.aborted_by_user
= True
171 self
._transaction
.proxy
.Cancel()
172 self
.status
= download
.download_failed
173 self
.downloaded
.trigger()
175 def get_current_fraction(self
):
176 if self
._transaction
is None:
178 (percentage
, subpercentage_
, elapsed_
, remaining_
) = \
179 self
._transaction
.proxy
.GetProgress()
183 return float(percentage
) / 100.
185 def get_bytes_downloaded_so_far(self
):
186 fraction
= self
.get_current_fraction()
190 return int(self
.expected_size
* fraction
)
192 def _auth_wrapper(method
, *args
):
195 except dbus
.exceptions
.DBusException
, e
:
196 if e
.get_dbus_name() != \
197 'org.freedesktop.PackageKit.Transaction.RefusedByPolicy':
200 iface
, auth
= e
.get_dbus_message().split()
201 if not auth
.startswith('auth_'):
204 _logger_pk
.debug(_('Authentication required for %s'), auth
)
206 pk_auth
= dbus
.SessionBus().get_object(
207 'org.freedesktop.PolicyKit.AuthenticationAgent', '/',
208 'org.gnome.PolicyKit.AuthorizationManager.SingleInstance')
210 if not pk_auth
.ObtainAuthorization(iface
, dbus
.UInt32(0),
211 dbus
.UInt32(os
.getpid()), timeout
=300):
216 class _PackageKitTransaction(object):
217 def __init__(self
, pk
, finished_cb
=None, error_cb
=None):
218 self
._finished
_cb
= finished_cb
219 self
._error
_cb
= error_cb
220 self
.error_code
= None
221 self
.error_details
= None
226 self
.object = dbus
.SystemBus().get_object(
227 'org.freedesktop.PackageKit', pk
.GetTid(), False)
228 self
.proxy
= dbus
.Interface(self
.object,
229 'org.freedesktop.PackageKit.Transaction')
231 for signal
, cb
in [('Finished', self
.__finished
_cb
),
232 ('ErrorCode', self
.__error
_code
_cb
),
233 ('StatusChanged', self
.__status
_changed
_cb
),
234 ('Package', self
.__package
_cb
),
235 ('Details', self
.__details
_cb
),
236 ('Files', self
.__files
_cb
)]:
237 self
.proxy
.connect_to_signal(signal
, cb
)
239 self
.proxy
.SetLocale(locale
.getdefaultlocale()[0])
241 def compat_call(self
, method
, arg_sets
):
242 dbus_method
= self
.proxy
.get_dbus_method(method
)
243 for args
in arg_sets
:
245 return dbus_method(*args
)
246 except dbus
.exceptions
.DBusException
, e
:
247 if e
.get_dbus_name() != \
248 'org.freedesktop.DBus.Error.UnknownMethod':
252 def __finished_cb(self
, exit
, runtime
):
253 _logger_pk
.debug(_('Transaction finished: %s'), exit
)
254 if self
.error_code
is not None:
257 self
._finished
_cb
(self
)
259 def __error_code_cb(self
, code
, details
):
260 _logger_pk
.info(_('Transaction failed: %s(%s)'), details
, code
)
261 self
.error_code
= code
262 self
.error_details
= details
264 def __package_cb(self
, status
, id, summary
):
265 from zeroinstall
.injector
import distro
267 package_name
, version
, arch
, repo_
= id.split(';')
268 clean_version
= distro
.try_cleanup_distro_version(version
)
269 if not clean_version
:
270 _logger_pk
.warn(_("Can't parse distribution version '%(version)s' for package '%(package)s'"), {'version': version
, 'package': package
})
271 clean_arch
= distro
.canonical_machine(arch
)
272 package
= {'version': clean_version
,
273 'name': package_name
,
275 'installed': (status
== 'installed')}
276 _logger_pk
.debug(_('Package: %s %r'), id, package
)
277 self
.package
[str(id)] = package
279 def __details_cb(self
, id, licence
, group
, detail
, url
, size
):
280 details
= {'licence': str(licence
),
282 'detail': str(detail
),
285 _logger_pk
.debug(_('Details: %s %r'), id, details
)
286 self
.details
[id] = details
288 def __files_cb(self
, id, files
):
289 self
.files
[id] = files
.split(';')
291 def __status_changed_cb(self
, status
):