From 18df20b610e11869d097674c6d4564deac2cfdae Mon Sep 17 00:00:00 2001 From: Thomas Leonard Date: Sun, 2 May 2010 18:11:35 +0100 Subject: [PATCH] If a distribution package is selected, prompt the user to install it Before, Zero Install would list available (but uninstalled) distribution packages but would never select them. Now they can be selected, and the user is told to install them using their distribution tools. This is based on code in the PackageKit patches from Aleksey Lim. --- zeroinstall/injector/distro.py | 11 +++++++---- zeroinstall/injector/fetch.py | 8 ++++++-- zeroinstall/injector/handler.py | 7 +++++-- zeroinstall/injector/model.py | 29 +++++++++++++++++++++++++---- zeroinstall/injector/solver.py | 4 ++++ 5 files changed, 47 insertions(+), 12 deletions(-) diff --git a/zeroinstall/injector/distro.py b/zeroinstall/injector/distro.py index b0d384a..0b10017 100644 --- a/zeroinstall/injector/distro.py +++ b/zeroinstall/injector/distro.py @@ -260,7 +260,7 @@ class DebianDistribution(Distribution): def __init__(self, dpkg_status, pkgcache): self.dpkg_cache = Cache('dpkg-status.cache', dpkg_status, 2) - self.apt_cache = Cache('apt-cache-cache', pkgcache, 2) + self.apt_cache = Cache('apt-cache-cache', pkgcache, 3) def _query_installed_package(self, package): child = subprocess.Popen(["dpkg-query", "-W", "--showformat=${Version}\t${Architecture}\t${Status}\n", "--", package], @@ -306,7 +306,7 @@ class DebianDistribution(Distribution): child = subprocess.Popen(['apt-cache', 'show', '--no-all-versions', '--', package], stdout = subprocess.PIPE, stderr = null) os.close(null) - arch = version = None + arch = version = size = None for line in child.stdout: line = line.strip() if line.startswith('Version: '): @@ -317,8 +317,10 @@ class DebianDistribution(Distribution): version = try_cleanup_distro_version(version) elif line.startswith('Architecture: '): arch = canonical_machine(line[14:].strip()) + elif line.startswith('Size: '): + size = int(line[6:].strip()) if version and arch: - cached = '%s\t%s' % (version, arch) + cached = '%s\t%s\t%d' % (version, arch, size) else: cached = '-' child.wait() @@ -329,13 +331,14 @@ class DebianDistribution(Distribution): self.apt_cache.put(package, cached) if cached != '-': - candidate_version, candidate_arch = cached.split('\t') + candidate_version, candidate_arch, candidate_size = cached.split('\t') if candidate_version and candidate_version != installed_version: impl = factory('package:deb:%s:%s' % (package, candidate_version)) impl.version = model.parse_version(candidate_version) impl.installed = False if candidate_arch != '*': impl.machine = candidate_arch + impl.download_sources.append(model.DistributionSource(package, candidate_size)) def get_score(self, disto_name): return int(disto_name == 'Debian') diff --git a/zeroinstall/injector/fetch.py b/zeroinstall/injector/fetch.py index dff9b07..6c866cf 100644 --- a/zeroinstall/injector/fetch.py +++ b/zeroinstall/injector/fetch.py @@ -11,7 +11,7 @@ from logging import info, debug, warn from zeroinstall.support import tasks, basedir from zeroinstall.injector.namespaces import XMLNS_IFACE, config_site -from zeroinstall.injector.model import DownloadSource, Recipe, SafeException, escape +from zeroinstall.injector.model import DownloadSource, Recipe, SafeException, escape, DistributionSource from zeroinstall.injector.iface_cache import PendingFeed, ReplayAttack from zeroinstall.injector.handler import NoTrustedKeys from zeroinstall.injector import download @@ -301,6 +301,10 @@ class Fetcher(object): assert impl assert retrieval_method + if isinstance(retrieval_method, DistributionSource): + raise SafeException(_("This program depends on '%s', which is a package that is available through your distribution. " + "Please install it manually using your distributions tools and try again.") % retrieval_method.package_id) + from zeroinstall.zerostore import manifest best = None for digest in impl.digests: @@ -335,7 +339,7 @@ class Fetcher(object): self.handler.impl_added_to_store(impl) return download_impl() - + def _add_to_cache(self, required_digest, stores, retrieval_method, stream): assert isinstance(retrieval_method, DownloadSource) url = retrieval_method.url diff --git a/zeroinstall/injector/handler.py b/zeroinstall/injector/handler.py index 1916074..a991b32 100644 --- a/zeroinstall/injector/handler.py +++ b/zeroinstall/injector/handler.py @@ -102,7 +102,7 @@ class Handler(object): tasks.check(blocker) - def get_download(self, url, force = False, hint = None): + def get_download(self, url, force = False, hint = None, factory = None): """Return the Download object currently downloading 'url'. If no download for this URL has been started, start one now (and start monitoring it). @@ -120,7 +120,10 @@ class Handler(object): dl.abort() raise KeyError except KeyError: - dl = download.Download(url, hint) + if factory is None: + dl = download.Download(url, hint) + else: + dl = factory(url, hint) self.monitor_download(dl) return dl diff --git a/zeroinstall/injector/model.py b/zeroinstall/injector/model.py index d350bcb..0aa8b8f 100644 --- a/zeroinstall/injector/model.py +++ b/zeroinstall/injector/model.py @@ -367,6 +367,20 @@ class Recipe(RetrievalMethod): size = property(lambda self: sum([x.size for x in self.steps])) +class DistributionSource(RetrievalMethod): + """A package that is installed using the distribution's tools (including PackageKit). + @ivar package_id: the package name, in a form recognised by the distribution's tools + @type package_id: str + @ivar size: the download size in bytes + @type size: int""" + + __slots__ = ['package_id', 'size'] + + def __init__(self, package_id, size): + RetrievalMethod.__init__(self) + self.package_id = package_id + self.size = size + class Implementation(object): """An Implementation is a package which implements an Interface. @ivar download_sources: list of methods of getting this implementation @@ -390,6 +404,8 @@ class Implementation(object): @ivar released: release date @ivar local_path: the directory containing this local implementation, or None if it isn't local (id isn't a path) @type local_path: str | None + @ivar requires_root_install: whether the user will need admin rights to use this + @type requires_root_install: bool """ # Note: user_stability shouldn't really be here @@ -444,6 +460,7 @@ class Implementation(object): os = None local_path = None digests = None + requires_root_install = False class DistributionImplementation(Implementation): """An implementation provided by the distribution. Information such as the version @@ -455,7 +472,11 @@ class DistributionImplementation(Implementation): assert id.startswith('package:') Implementation.__init__(self, feed, id) self.installed = True - + + @property + def requires_root_install(self): + return not self.installed + class ZeroInstallImplementation(Implementation): """An implementation where all the information comes from Zero Install. @ivar digests: a list of "algorith=value" strings (since 0.45) @@ -475,15 +496,15 @@ class ZeroInstallImplementation(Implementation): # Deprecated dependencies = property(lambda self: dict([(x.interface, x) for x in self.requires if isinstance(x, InterfaceDependency)])) - + def add_download_source(self, url, size, extract, start_offset = 0, type = None): """Add a download source.""" self.download_sources.append(DownloadSource(self, url, size, extract, start_offset, type)) - + def set_arch(self, arch): self.os, self.machine = _split_arch(arch) arch = property(lambda self: _join_arch(self.os, self.machine), set_arch) - + class Interface(object): """An Interface represents some contract of behaviour. @ivar uri: the URI for this interface. diff --git a/zeroinstall/injector/solver.py b/zeroinstall/injector/solver.py index 9bcf6da..a9c2718 100644 --- a/zeroinstall/injector/solver.py +++ b/zeroinstall/injector/solver.py @@ -138,6 +138,10 @@ class SATSolver(Solver): r = cmp(_get_cached(stores, a), _get_cached(stores, b)) if r: return r + # Packages that require admin access to install come last + r = cmp(b.requires_root_install, a.requires_root_install) + if r: return r + # Stability stab_policy = interface.stability_policy if not stab_policy: -- 2.11.4.GIT