Skip to content
111 changes: 111 additions & 0 deletions libchannels/apt_cache.py
Original file line number Diff line number Diff line change
@@ -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)
83 changes: 69 additions & 14 deletions libchannels/updates.py → libchannels/apt_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,25 @@

import logging

import libchannels.common

from apt_pkg import size_to_str

logger = logging.getLogger(__name__)

class Updates:
# APT Configuration

# FIXME: should make this interactive
apt_pkg.config.set("DPkg::Options::", "--force-confdef")
apt_pkg.config.set("DPkg::Options::", "--force-confold")

class APT:

"""
Same-release distribution updates management.
"""

MAX_TRIES = 5

def __init__(self):
def __init__(self, cache):
"""
Initializes the class.
"""
Expand All @@ -53,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
Expand Down Expand Up @@ -85,18 +89,15 @@ 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):
"""
Clears the changes made.
"""

if self.cache:
self.cache = None
self.cache.clear()

self.changed = False

Expand All @@ -111,8 +112,55 @@ 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 Exception 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.
Expand Down Expand Up @@ -203,6 +251,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()
Expand All @@ -221,18 +271,23 @@ 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

# Broken packages?
#if self.cache._depcache.broken_count > 0:
if True:
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
Expand Down
11 changes: 11 additions & 0 deletions libchannels/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
):
Expand Down