mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-09-19 04:59:01 +00:00
packages: refresh package list before install and handle errors
- Refresh package list before trying to install packages. Also lookup the ID of the package, including the version, using the newly fetched package list. - Call on_install() callback requested by modules only if the package installation was successful. Handle any exception raised in the callback itself. - Handle exceptions raised during packagekit operations. Also check the returned results for error code. - Capture error/success status during a transaction. Don't destroy the transaction object until the error/success status has been collected. When a view is refreshed after completion of a transaction collect the result of the transaction and show it to the user. - Handle cases where package searches in the package list fails. Simply show the package name without its description.
This commit is contained in:
parent
c7a8bcbd17
commit
3a22e1717e
@ -19,7 +19,10 @@
|
|||||||
Framework for installing and updating distribution packages
|
Framework for installing and updating distribution packages
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from django.contrib import messages
|
||||||
import functools
|
import functools
|
||||||
|
from gettext import gettext as _
|
||||||
|
from gi.repository import GLib as glib
|
||||||
from gi.repository import PackageKitGlib as packagekit
|
from gi.repository import PackageKitGlib as packagekit
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
@ -32,6 +35,17 @@ transactions = {}
|
|||||||
packages_resolved = {}
|
packages_resolved = {}
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
class Transaction(object):
|
class Transaction(object):
|
||||||
"""Information about an ongoing transaction."""
|
"""Information about an ongoing transaction."""
|
||||||
|
|
||||||
@ -57,6 +71,10 @@ class Transaction(object):
|
|||||||
self.caller_active = None
|
self.caller_active = None
|
||||||
self.download_size_remaining = None
|
self.download_size_remaining = None
|
||||||
|
|
||||||
|
# Completion
|
||||||
|
self.is_finished = False
|
||||||
|
self.exception = None
|
||||||
|
|
||||||
def get_id(self):
|
def get_id(self):
|
||||||
"""Return a identifier to use as a key in a map of transactions."""
|
"""Return a identifier to use as a key in a map of transactions."""
|
||||||
return frozenset(self.package_names)
|
return frozenset(self.package_names)
|
||||||
@ -78,13 +96,70 @@ class Transaction(object):
|
|||||||
|
|
||||||
def _install(self):
|
def _install(self):
|
||||||
"""Run a PackageKit transaction to install given packages."""
|
"""Run a PackageKit transaction to install given packages."""
|
||||||
package_ids = [packages_resolved[package_name].get_id()
|
try:
|
||||||
for package_name in self.package_names]
|
self._do_install()
|
||||||
|
except PackageException as exception:
|
||||||
|
self.finish(exception)
|
||||||
|
return
|
||||||
|
except glib.Error as exception:
|
||||||
|
self.finish(PackageException(exception.message))
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
if self.on_install:
|
||||||
|
self.on_install()
|
||||||
|
except Exception as exception:
|
||||||
|
logger.exception('Error during setup - %s', exception)
|
||||||
|
self.finish(exception)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.finish()
|
||||||
|
|
||||||
|
def _do_install(self):
|
||||||
|
"""Run a PackageKit transaction to install given packages.
|
||||||
|
|
||||||
|
Raise exception in case of error.
|
||||||
|
"""
|
||||||
client = packagekit.Client()
|
client = packagekit.Client()
|
||||||
client.set_interactive(False)
|
client.set_interactive(False)
|
||||||
client.install_packages(packagekit.TransactionFlagEnum.ONLY_TRUSTED,
|
|
||||||
package_ids + [None], None,
|
# Refresh package cache from all enabled repositories
|
||||||
self.progress_callback, self)
|
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)
|
||||||
|
|
||||||
|
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."""
|
||||||
|
# XXX: Untested code
|
||||||
|
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 = error_code.to_string() 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):
|
def progress_callback(self, progress, progress_type, user_data):
|
||||||
"""Process progress updates on package resolve operation"""
|
"""Process progress updates on package resolve operation"""
|
||||||
@ -102,8 +177,6 @@ class Transaction(object):
|
|||||||
self.status = progress.props.status
|
self.status = progress.props.status
|
||||||
self.status_string = \
|
self.status_string = \
|
||||||
packagekit.StatusEnum.to_string(progress.props.status)
|
packagekit.StatusEnum.to_string(progress.props.status)
|
||||||
if self.status == packagekit.StatusEnum.FINISHED:
|
|
||||||
self.finish()
|
|
||||||
elif progress_type == packagekit.ProgressType.TRANSACTION_FLAGS:
|
elif progress_type == packagekit.ProgressType.TRANSACTION_FLAGS:
|
||||||
self.flags = progress.props.transaction_flags
|
self.flags = progress.props.transaction_flags
|
||||||
elif progress_type == packagekit.ProgressType.ROLE:
|
elif progress_type == packagekit.ProgressType.ROLE:
|
||||||
@ -117,15 +190,20 @@ class Transaction(object):
|
|||||||
logger.info('Unhandle packagekit progress callback - %s, %s',
|
logger.info('Unhandle packagekit progress callback - %s, %s',
|
||||||
progress, progress_type)
|
progress, progress_type)
|
||||||
|
|
||||||
def finish(self):
|
def finish(self, exception=None):
|
||||||
"""Perform clean up operations on the transaction.
|
"""Mark transaction as complected and store exception if any."""
|
||||||
|
self.is_finished = True
|
||||||
|
self.exception = exception
|
||||||
|
|
||||||
Remove self from global transactions list.
|
def collect_result(self):
|
||||||
|
"""Retrieve the result of this transaction.
|
||||||
|
|
||||||
|
Also remove self from global transactions list.
|
||||||
"""
|
"""
|
||||||
if self.status == packagekit.StatusEnum.FINISHED:
|
assert self.is_finished
|
||||||
self.on_install()
|
|
||||||
|
|
||||||
del transactions[self.get_id()]
|
del transactions[self.get_id()]
|
||||||
|
return self.exception
|
||||||
|
|
||||||
|
|
||||||
def required(package_names, on_install=None):
|
def required(package_names, on_install=None):
|
||||||
@ -137,8 +215,7 @@ def required(package_names, on_install=None):
|
|||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
def wrapper(request, *args, **kwargs):
|
def wrapper(request, *args, **kwargs):
|
||||||
"""Check and install packages required by a view."""
|
"""Check and install packages required by a view."""
|
||||||
if not is_installing(package_names) and \
|
if not _should_show_install_view(request, package_names):
|
||||||
check_installed(package_names):
|
|
||||||
return func(request, *args, **kwargs)
|
return func(request, *args, **kwargs)
|
||||||
|
|
||||||
view = plinth.views.PackageInstallView.as_view()
|
view = plinth.views.PackageInstallView.as_view()
|
||||||
@ -150,6 +227,32 @@ def required(package_names, on_install=None):
|
|||||||
return wrapper2
|
return wrapper2
|
||||||
|
|
||||||
|
|
||||||
|
def _should_show_install_view(request, package_names):
|
||||||
|
"""Return whether the installation view should be shown."""
|
||||||
|
transaction_id = frozenset(package_names)
|
||||||
|
|
||||||
|
# No transaction in progress
|
||||||
|
if transaction_id not in transactions:
|
||||||
|
is_installed = check_installed(package_names)
|
||||||
|
return not is_installed
|
||||||
|
|
||||||
|
# Installing
|
||||||
|
transaction = transactions[transaction_id]
|
||||||
|
if not transaction.is_finished:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Transaction finished, waiting to show the result
|
||||||
|
exception = transaction.collect_result()
|
||||||
|
if not exception:
|
||||||
|
messages.success(request,
|
||||||
|
_('Installed and configured packages successfully'))
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
messages.error(request, _('Error installing packages: {details}')
|
||||||
|
.format(details=exception.error_string))
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def check_installed(package_names):
|
def check_installed(package_names):
|
||||||
"""Return a boolean installed status of package.
|
"""Return a boolean installed status of package.
|
||||||
|
|
||||||
@ -171,6 +274,11 @@ def check_installed(package_names):
|
|||||||
|
|
||||||
packages_resolved[package.get_name()] = package
|
packages_resolved[package.get_name()] = package
|
||||||
|
|
||||||
|
# When package names could not be resolved
|
||||||
|
for package_name in package_names:
|
||||||
|
if package_name not in packages_resolved:
|
||||||
|
packages_resolved[package_name] = None
|
||||||
|
|
||||||
return set(installed_package_names) == set(package_names)
|
return set(installed_package_names) == set(package_names)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -44,9 +44,9 @@
|
|||||||
<tr><th>Package</th><th>Summary</th></tr>
|
<tr><th>Package</th><th>Summary</th></tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for package in packages %}
|
{% for package_name, package in packages.items %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ package.get_name }}</td>
|
<td>{{ package_name }}</td>
|
||||||
<td>{{ package.get_summary }}</td>
|
<td>{{ package.get_summary }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@ -44,8 +44,9 @@ class PackageInstallView(TemplateView):
|
|||||||
|
|
||||||
if 'packages_names' not in context:
|
if 'packages_names' not in context:
|
||||||
context['package_names'] = self.kwargs.get('package_names', [])
|
context['package_names'] = self.kwargs.get('package_names', [])
|
||||||
context['packages'] = [package_module.packages_resolved[package_name]
|
context['packages'] = {
|
||||||
for package_name in context['package_names']]
|
package_name: package_module.packages_resolved[package_name]
|
||||||
|
for package_name in context['package_names']}
|
||||||
context['is_installing'] = \
|
context['is_installing'] = \
|
||||||
package_module.is_installing(context['package_names'])
|
package_module.is_installing(context['package_names'])
|
||||||
context['transactions'] = package_module.transactions
|
context['transactions'] = package_module.transactions
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user