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 <sunil@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
Attachment:
signature.asc
Description: OpenPGP digital signature