From d24bc29872209d6c7127a160a8074a69657f5162 Mon Sep 17 00:00:00 2001 From: "Eugenio Paolantonio (g7)" Date: Tue, 12 Jan 2016 13:04:54 +0100 Subject: [PATCH 1/9] [updates] Enforce uninteractive conffile policy dpkg will now keep old configuration files if they have been modified, and will automatically install the new one if they're not. Signed-off-by: Eugenio Paolantonio (g7) --- libchannels/updates.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libchannels/updates.py b/libchannels/updates.py index b94dc7c..4e36af7 100644 --- a/libchannels/updates.py +++ b/libchannels/updates.py @@ -29,6 +29,12 @@ logger = logging.getLogger(__name__) +# APT Configuration + +# FIXME: should make this interactive +apt_pkg.config.set("DPkg::Options::", "--force-confdef") +apt_pkg.config.set("DPkg::Options::", "--force-confold") + class Updates: """ From b8002ed4a58cc339d61218d4d2bdddb9c2baf3f9 Mon Sep 17 00:00:00 2001 From: "Eugenio Paolantonio (g7)" Date: Tue, 12 Jan 2016 13:07:22 +0100 Subject: [PATCH 2/9] [updates] Some more log messages Signed-off-by: Eugenio Paolantonio (g7) --- libchannels/updates.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libchannels/updates.py b/libchannels/updates.py index 4e36af7..b0b31c4 100644 --- a/libchannels/updates.py +++ b/libchannels/updates.py @@ -209,6 +209,8 @@ def install(self): if not self.fetch(package_manager): return + logger.debug("Starting the update run") + # Then run the actual installation process res = self.packages_install_progress.run(package_manager) #res = package_manager.do_install() @@ -227,6 +229,7 @@ def install(self): ) # Dpkg journal dirty? if self.cache.dpkg_journal_dirty: + logger.debug("Dirty journal, fixing...") subprocess.call(["dpkg", "configure", "--all"]) # FIXME: add check for core packages @@ -234,6 +237,7 @@ def install(self): # Broken packages? #if self.cache._depcache.broken_count > 0: if True: + logger.debug("Broken packages, fixing...") fixer = apt_pkg.ProblemResolver(self.cache._depcache) fixer.resolve(True) From 1bd8ea32cfafd5a2ce844eab95f3f9653e8fa889 Mon Sep 17 00:00:00 2001 From: "Eugenio Paolantonio (g7)" Date: Tue, 12 Jan 2016 13:07:40 +0100 Subject: [PATCH 3/9] [updates] Reopen cache when fixing broken packages Signed-off-by: Eugenio Paolantonio (g7) --- libchannels/updates.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libchannels/updates.py b/libchannels/updates.py index b0b31c4..a9267e2 100644 --- a/libchannels/updates.py +++ b/libchannels/updates.py @@ -238,6 +238,8 @@ def install(self): #if self.cache._depcache.broken_count > 0: if True: logger.debug("Broken packages, fixing...") + self.cache.clear() + self.open_cache() fixer = apt_pkg.ProblemResolver(self.cache._depcache) fixer.resolve(True) From 054d614e055a3a8b5143bfbf6a885d9e38f0752e Mon Sep 17 00:00:00 2001 From: "Eugenio Paolantonio (g7)" Date: Tue, 19 Jan 2016 00:00:38 +0100 Subject: [PATCH 4/9] [apt_handler] Renamed updates to apt_handler Signed-off-by: Eugenio Paolantonio (g7) --- libchannels/{updates.py => apt_handler.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename libchannels/{updates.py => apt_handler.py} (99%) diff --git a/libchannels/updates.py b/libchannels/apt_handler.py similarity index 99% rename from libchannels/updates.py rename to libchannels/apt_handler.py index a9267e2..975a4e5 100644 --- a/libchannels/updates.py +++ b/libchannels/apt_handler.py @@ -35,7 +35,7 @@ apt_pkg.config.set("DPkg::Options::", "--force-confdef") apt_pkg.config.set("DPkg::Options::", "--force-confold") -class Updates: +class APT: """ Same-release distribution updates management. From 6f4e6f68663b164e976a201e36e901fb2c79e30c Mon Sep 17 00:00:00 2001 From: "Eugenio Paolantonio (g7)" Date: Fri, 22 Jan 2016 17:32:44 +0100 Subject: [PATCH 5/9] [apt_handler] Handle get_update_infos() failures Signed-off-by: Eugenio Paolantonio (g7) --- libchannels/apt_handler.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libchannels/apt_handler.py b/libchannels/apt_handler.py index 975a4e5..2bc091d 100644 --- a/libchannels/apt_handler.py +++ b/libchannels/apt_handler.py @@ -117,7 +117,11 @@ def get_update_infos(self): if not self.cache: return 0, 0 - return self.cache.required_download, self.cache.required_space + try: + return self.cache.required_download, self.cache.required_space + except SystemError as err: + self.notify_error("An error occurred", err) + return 0, 0 def update(self): """ From dbda6a5516664f3d622ac8f5480278fbd9926edb Mon Sep 17 00:00:00 2001 From: "Eugenio Paolantonio (g7)" Date: Thu, 25 Feb 2016 16:05:39 +0100 Subject: [PATCH 6/9] [apt_handler] Switch to a libchannels-global DynamicCache Signed-off-by: Eugenio Paolantonio (g7) --- libchannels/apt_cache.py | 111 +++++++++++++++++++++++++++++++++++++ libchannels/apt_handler.py | 15 ++--- libchannels/common.py | 11 ++++ 3 files changed, 127 insertions(+), 10 deletions(-) create mode 100644 libchannels/apt_cache.py diff --git a/libchannels/apt_cache.py b/libchannels/apt_cache.py new file mode 100644 index 0000000..853c7b2 --- /dev/null +++ b/libchannels/apt_cache.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- +# +# libchannels - update channels management library +# Copyright (C) 2015 Eugenio "g7" Paolantonio +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# + +import apt +import apt_pkg + +import logging + +logger = logging.getLogger(__name__) + +class DynamicCache: + """ + A proxy object that will load the cache only when required. + """ + + def __init__(self): + """ + Initializes the object. + """ + + self._cache = None + + def open_cache(self, progress=None): + """ + Opens/creates the cache. + """ + + if not self._cache: + self._cache = apt.Cache(progress=progress) + else: + self._cache.open(progress=progress) + + def clear(self): + """ + Clears the changes made. + """ + + if self._cache: + self._cache = None + + def __getattr__(self, name): + """ + Returns the cache attribute, loading the cache first if required. + """ + + if not self._cache: + self.open_cache() + + logger.debug("Cache attribute requested, %s" % name) + return getattr(self._cache, name) + + def __getitem__(self, name): + """ + Offloads the call to the cache object. + """ + + if not self._cache: + self.open_cache() + + return self._cache.__getitem__(name) + + def __contains__(self, name): + """ + Offloads the call to the cache object. + """ + + if not self._cache: + self.open_cache() + + return self._cache.__contains__(name) + + def __iter__(self): + """ + Offloads the iteration to the cache object. + """ + + if not self._cache: + self.open_cache() + + return self._cache.__iter__() + + def __eq__(self, other): + """ + Offloads the comparison to the cache object. + """ + + return self._cache.__eq__(other) + + def __ne__(self, other): + """ + Offloads the comparison to the cache object. + """ + + return self._cache.__ne__(other) diff --git a/libchannels/apt_handler.py b/libchannels/apt_handler.py index 2bc091d..8c1ae27 100644 --- a/libchannels/apt_handler.py +++ b/libchannels/apt_handler.py @@ -23,8 +23,6 @@ import logging -import libchannels.common - from apt_pkg import size_to_str logger = logging.getLogger(__name__) @@ -43,7 +41,7 @@ class APT: MAX_TRIES = 5 - def __init__(self): + def __init__(self, cache): """ Initializes the class. """ @@ -59,8 +57,8 @@ def __init__(self): self.lock_failure_callback = None self.generic_failure_callback = None - - self.cache = None + + self.cache = cache self.cache_progress = None self.cache_acquire_progress = None @@ -91,10 +89,7 @@ def open_cache(self, progress=None): Opens/Creates the cache. """ - if not self.cache: - self.cache = apt.Cache(progress=self.cache_progress) - else: - self.cache.open(progress=self.cache_progress) + return self.cache.open_cache(progress if progress else self.cache_progress) def clear(self): """ @@ -102,7 +97,7 @@ def clear(self): """ if self.cache: - self.cache = None + self.cache.clear() self.changed = False diff --git a/libchannels/common.py b/libchannels/common.py index 2302bcf..2c38269 100644 --- a/libchannels/common.py +++ b/libchannels/common.py @@ -18,16 +18,27 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA # +import os + import logging import apt_pkg import aptsources.sourceslist +import libchannels.apt_handler +import libchannels.apt_cache + logger = logging.getLogger(__name__) # Sourceslist sourceslist = aptsources.sourceslist.SourcesList() +# APT cache +cache_object = libchannels.apt_cache.DynamicCache() + +# APT handler +handler_object = libchannels.apt_handler.APT(cache_object) + def lock( lock_failed_callback=None ): From cb7c34aede1d7392551173bf7f8f9fdbee019c05 Mon Sep 17 00:00:00 2001 From: "Eugenio Paolantonio (g7)" Date: Fri, 26 Feb 2016 13:07:54 +0100 Subject: [PATCH 7/9] [apt_handler] Added new methods check_relationship() and check_relationships() The latter takes directly a relationship list as returned by apt_pkg.parse{,_src}_depends(). Signed-off-by: Eugenio Paolantonio (g7) --- libchannels/apt_handler.py | 45 +++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/libchannels/apt_handler.py b/libchannels/apt_handler.py index 8c1ae27..e76daa0 100644 --- a/libchannels/apt_handler.py +++ b/libchannels/apt_handler.py @@ -117,7 +117,50 @@ def get_update_infos(self): except SystemError as err: self.notify_error("An error occurred", err) return 0, 0 - + + def check_relationship(self, relationship): + """ + Checks a single relationship. + + Returns True if it does, False if not. + + `relationship` is a single relationship. + """ + + for package_name, relationship_version, operator in relationship: + if package_name in self.cache: + package = self.cache[package_name] + if package.is_installed: + version = package.installed + elif package.marked_install or package.marked_upgrade or package.marked_downgrade: + # FIXME: this assumption may not be always correct + version = package.candidate + + if apt_pkg.check_dep(version.version, operator, relationship_version): + return True + + return False + + def check_relationships(self, relationships): + """ + Checks if the current APT state satisfies the given relationships. + + Returns True if it does, False if not. + + `relationships` is a relationship list as returned by `apt_pkg.parse_src_depends()`. + """ + + # Example relationship list: + # [[('abiword', '5.0', '>='), ('meh', '', '')], [('gnumeric', '', ''), ('lol', '', '')]] + # which roughly translates to: + # abiword (>= 5.0) | meh, gnumeric | lol + + for group in relationships: + if not self.check_relationship(group): + return False + + return True + def update(self): """ Updates the package cache. From 34a64d4862f8d785b2c8b112e307e2566a005281 Mon Sep 17 00:00:00 2001 From: "Eugenio Paolantonio (g7)" Date: Wed, 9 Mar 2016 18:24:33 +0100 Subject: [PATCH 8/9] [apt_handler] Recreate the package_manager object after clearing the cache Also, package fetching on broken dependencies has been momentairly disabled pending new tests. Signed-off-by: Eugenio Paolantonio (g7) --- libchannels/apt_handler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libchannels/apt_handler.py b/libchannels/apt_handler.py index e76daa0..83ddeea 100644 --- a/libchannels/apt_handler.py +++ b/libchannels/apt_handler.py @@ -282,11 +282,12 @@ def install(self): logger.debug("Broken packages, fixing...") self.cache.clear() self.open_cache() + package_manager = apt_pkg.PackageManager(self.cache._depcache) fixer = apt_pkg.ProblemResolver(self.cache._depcache) fixer.resolve(True) # FIXME: should notify this? - self.fetch(package_manager) + #self.fetch(package_manager) self.packages_install_progress.run(package_manager) # Restore working state for the next upgrade run From 3558d7773ea20e8dcdb86a4539b87b7e181f9466 Mon Sep 17 00:00:00 2001 From: "Eugenio Paolantonio (g7)" Date: Sat, 16 Apr 2016 18:33:56 +0200 Subject: [PATCH 9/9] [apt_handler] get_update_infos(): catch a broader range of Exceptions Signed-off-by: Eugenio Paolantonio (g7) --- libchannels/apt_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libchannels/apt_handler.py b/libchannels/apt_handler.py index 83ddeea..48e793c 100644 --- a/libchannels/apt_handler.py +++ b/libchannels/apt_handler.py @@ -114,7 +114,7 @@ def get_update_infos(self): try: return self.cache.required_download, self.cache.required_space - except SystemError as err: + except Exception as err: self.notify_error("An error occurred", err) return 0, 0