Hello, Here is an experimental patch to perform package installation using PackageKit. I have tested it for success and error conditions but there are corner cases such as inserting devices during package installation and parallel package installation that I haven't tested. I expect them to work fine by the way.
One thing to consider about PackageKit is that it does not show progress dialogs like aptdaemon. But I have left some code in there to collect progress information that can be used to show progress dialogs if necessary. -- Sunil
From 7ee652f3fc6b09e1fdf9cba706d562128de2c247 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa <su...@medhas.org> Date: Wed, 25 May 2016 11:29:58 +0530 Subject: [PATCH] Implement package installation using PackageKit - Based on code from FreedomBox UI - Plinth. I am the original author of the code and I here by license it under the same license as isenkram package: GNU GPL v2 or later. - When trying to install packages, a dialog is shown to the user asking for administrator password to allow for package installation. - PackageKit does not provide package installation progress bars like aptdaemon. So, use libnotify to show a message at the start and end of installation. --- isenkramd | 181 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 179 insertions(+), 2 deletions(-) diff --git a/isenkramd b/isenkramd index 7eee84d..ac5028d 100755 --- a/isenkramd +++ b/isenkramd @@ -40,6 +40,8 @@ gi.require_version('GUdev', '1.0') from gi.repository import GUdev gi.require_version('Notify', '0.7') from gi.repository import Notify +gi.require_version('PackageKitGlib', '1.0') +from gi.repository import PackageKitGlib as packagekit import isenkram.lookup @@ -49,6 +51,10 @@ from aptdaemon.gtk3widgets import AptErrorDialog, \ AptProgressDialog import aptdaemon.errors + +use_apt_daemon = False + + class AptDaemonGUIClient(object): """Provides a graphical interface to aptdaemon.""" @@ -102,6 +108,173 @@ class AptDaemonGUIClient(object): self.loop.run() +class PackageException(Exception): + """A package operation has failed.""" + + def __init__(self, error_string=None, error_details=None, *args, **kwargs): + """Store packagekit error string and details.""" + super(PackageException, self).__init__(*args, **kwargs) + + self.error_string = error_string + self.error_details = error_details + + def __str__(self): + """Return the strin representation of the exception.""" + return 'PackageException(error_string="{0}", error_details="{1}")' \ + .format(self.error_string, self.error_details) + + +class PackageKitInstaller(object): + """Helper to install packages using PackageKit.""" + + def __init__(self, package_names): + """Initialize transaction object. + + Set most values to None until they are sent as progress update. + """ + self.package_names = package_names + + # Progress + self.allow_cancel = None + self.percentage = None + self.status = None + self.status_string = None + self.flags = None + self.package = None + self.package_id = None + self.item_progress = None + self.role = None + self.caller_active = None + self.download_size_remaining = None + self.speed = None + + def get_id(self): + """Return a identifier to use as a key in a map of transactions.""" + return frozenset(self.package_names) + + def __str__(self): + """Return the string representation of the object""" + return ('Transaction(packages={0}, allow_cancel={1}, status={2}, ' + ' percentage={3}, package={4}, item_progress={5})').format( + self.package_names, self.allow_cancel, self.status_string, + self.percentage, self.package, self.item_progress) + + def notify_and_install(self): + """Notify, start installation and then notify success/error.""" + start_notification = Notify.Notification( + summary='Installing packages', + body='Instaling packages - {packages}'.format( + packages=', '.join(self.package_names))) + start_notification.set_timeout(10000) + start_notification.show() + + error = None + try: + self.install() + except PackageException as exception: + error = exception + + start_notification.close() + if not error: + final_notification = Notify.Notification( + summary='Installation successful', + body='Packages have been successfully installed - {packages}'. + format(packages=', '.join(self.package_names))) + else: + final_notification = Notify.Notification( + summary='Installation failed', + body='Error installing packages: {string}, {details}'.format( + string=error.error_string, details=error.error_details)) + final_notification.set_timeout(10000) + final_notification.show() + + def install(self): + """Run a PackageKit transaction to install given packages.""" + try: + self._do_install() + except GLib.Error as exception: + raise PackageException(exception.message) + + def _do_install(self): + """Run a PackageKit transaction to install given packages. + + Raise exception in case of error. + """ + client = packagekit.Client() + client.set_interactive(False) + + # Refresh package cache from all enabled repositories + results = client.refresh_cache( + False, None, self.progress_callback, self) + self._assert_success(results) + + # Resolve packages again to get the latest versions after refresh + results = client.resolve(packagekit.FilterEnum.INSTALLED, + tuple(self.package_names) + (None, ), + None, self.progress_callback, self) + self._assert_success(results) + + packages_resolved = {} + for package in results.get_package_array(): + packages_resolved[package.get_name()] = package + + package_ids = [] + for package_name in self.package_names: + if package_name not in packages_resolved or \ + not packages_resolved[package_name]: + raise PackageException('packages not found') + + package_ids.append(packages_resolved[package_name].get_id()) + + # Start package installation + results = client.install_packages( + packagekit.TransactionFlagEnum.ONLY_TRUSTED, package_ids + [None], + None, self.progress_callback, self) + self._assert_success(results) + + def _assert_success(self, results): + """Check that the most recent operation was a success.""" + if results and results.get_error_code() is not None: + error = results.get_error_code() + error_code = error.get_code() if error else None + error_string = packagekit.ErrorEnum.to_string(error_code) \ + if error_code else None + error_details = error.get_details() if error else None + raise PackageException(error_string, error_details) + + def progress_callback(self, progress, progress_type, user_data): + """Process progress updates on package resolve operation""" + return + if progress_type == packagekit.ProgressType.PERCENTAGE: + self.percentage = progress.props.percentage + elif progress_type == packagekit.ProgressType.PACKAGE: + self.package = progress.props.package + elif progress_type == packagekit.ProgressType.ALLOW_CANCEL: + self.allow_cancel = progress.props.allow_cancel + elif progress_type == packagekit.ProgressType.PACKAGE_ID: + self.package_id = progress.props.package_id + elif progress_type == packagekit.ProgressType.ITEM_PROGRESS: + self.item_progress = progress.props.item_progress + elif progress_type == packagekit.ProgressType.STATUS: + self.status = progress.props.status + self.status_string = \ + packagekit.StatusEnum.to_string(progress.props.status) + elif progress_type == packagekit.ProgressType.TRANSACTION_FLAGS: + self.flags = progress.props.transaction_flags + elif progress_type == packagekit.ProgressType.ROLE: + self.role = progress.props.role + elif progress_type == packagekit.ProgressType.CALLER_ACTIVE: + self.caller_active = progress.props.caller_active + elif progress_type == packagekit.ProgressType.DOWNLOAD_SIZE_REMAINING: + self.download_size_remaining = \ + progress.props.download_size_remaining + elif progress_type == packagekit.ProgressType.SPEED: + self.speed = progress.props.speed + else: + print('Unhandled packagekit progress callback - %s, %s', + progress, progress_type) + + # Keep refs needed for callback to work n = None npkgs = None @@ -111,8 +284,12 @@ def notify_pleaseinstall(notification=None, action=None, data=None): pkgsstr = string.join(pkgs, " ") # print pkgs print "info: button clicked, installing %s" % pkgsstr - demo = AptDaemonGUIClient(pkgs[0]) - demo.request_installation() + if use_apt_daemon: + installer = AptDaemonGUIClient(pkgs[0]) + installer.request_installation() + else: + installer = PackageKitInstaller(pkgs) + installer.notify_and_install() def notify(bus, vendor, device, pkgs): pkgstr = string.join(pkgs, " ") -- 2.8.1
signature.asc
Description: OpenPGP digital signature