from i18n import _
class Action(object):
- def __init__(self, pakfire, pkg):
+ def __init__(self, pakfire, pkg_solv, pkg_bin=None):
self.pakfire = pakfire
- self.pkg_solv = self.pkg = pkg
- # Try to get the binary version of the package from the cache if
- # any.
- binary_package = self.pkg.get_from_cache()
- if binary_package:
- self.pkg = binary_package
+ self.pkg_solv = pkg_solv
+ if pkg_bin:
+ self.pkg_bin = pkg_bin
self.init()
return filelist
def verify(self):
- assert self.pkg, "No package! %s" % self.pkg_solv
+ assert self.pkg, "No package! %s" % self.pkg
assert self.pkg.repo, "Package has no repository? %s" % self.pkg
# Local packages need no verification.
raise SignatureError, _("%s has got no valid signatures") % self.pkg.friendly_name
@property
- def needs_download(self):
- return self.type in ("install", "reinstall", "upgrade", "downgrade", "change",) \
- and not isinstance(self.pkg, packages.BinaryPackage)
+ def pkg(self):
+ """
+ Return the best version of the package we can use.
+ """
+ return self.pkg_bin or self.pkg_solv
- def download(self, text, logger=None):
- if not self.needs_download:
- return
+ def get_binary_package(self):
+ """
+ Tries to find the binary version of the package in the local cache.
+ """
+ return self.pkg_solv.get_from_cache()
+
+ def _get_pkg_bin(self):
+ if not hasattr(self, "_pkg_bin"):
+ self._pkg_bin = self.get_binary_package()
- self.pkg = self.pkg.download(text, logger=logger)
+ return self._pkg_bin
+
+ def _set_pkg_bin(self, pkg):
+ if pkg and not self.pkg_solv.uuid == pkg.uuid:
+ raise RuntimeError, "Not the same package: %s != %s" % (self.pkg_solv, pkg)
+
+ self._pkg_bin = pkg
+
+ pkg_bin = property(_get_pkg_bin, _set_pkg_bin)
def run(self):
raise NotImplementedError
# Add package to the database.
self.local.add_package(self.pkg)
- self.pkg.extract(_("Installing"), prefix=self.pakfire.path)
+ if isinstance(self, ActionReinstall):
+ msg = _("Reinstalling")
+ elif isinstance(self, ActionUpdate):
+ msg = _("Updating")
+ elif isinstance(self, ActionDowngrade):
+ msg = _("Downgrading")
+ else:
+ msg = _("Installing")
+
+ self.pkg.extract(msg, prefix=self.pakfire.path)
# Check if shared objects were extracted. If this is the case, we need
# to run ldconfig.
log.debug("ldconfig is not present or not executable.")
-class ActionUpdate(Action):
+class ActionUpdate(ActionInstall):
type = "upgrade"
def check(self, check):
# Check if this package can be updated.
check.update(self.pkg)
- def run(self):
- # Add new package to the database.
- self.local.add_package(self.pkg)
-
- self.pkg.extract(_("Updating"), prefix=self.pakfire.path)
-
class ActionRemove(Action):
type = "erase"
- def __init__(self, *args, **kwargs):
- Action.__init__(self, *args, **kwargs)
-
- self.pkg = self.local.db.get_package_from_solv(self.pkg_solv)
- assert self.pkg
-
def check(self, check):
log.debug(_("Running transaction test for %s") % self.pkg.friendly_name)
check.remove(self.pkg)
def run(self):
- self.pkg.cleanup(_("Removing"), prefix=self.pakfire.path)
+ if isinstance(self, ActionCleanup):
+ msg = _("Cleanup")
+ else:
+ msg = _("Removing")
+
+ self.pkg.cleanup(msg, prefix=self.pakfire.path)
# Remove package from the database.
- self.local.rem_package(self.pkg_solv)
+ self.local.rem_package(self.pkg)
-class ActionCleanup(Action):
+class ActionCleanup(ActionRemove):
type = "ignore"
- def __init__(self, *args, **kwargs):
- Action.__init__(self, *args, **kwargs)
-
- self.pkg = self.local.db.get_package_from_solv(self.pkg_solv)
- assert self.pkg
-
def check(self, check):
log.debug(_("Running transaction test for %s") % self.pkg.friendly_name)
# Check if this package can be removed.
check.cleanup(self.pkg)
- def run(self):
- # Cleaning up leftover files and stuff.
- self.pkg.cleanup(_("Cleanup"), prefix=self.pakfire.path)
-
- # Remove package from the database.
- self.local.rem_package(self.pkg_solv)
-
-class ActionReinstall(Action):
+class ActionReinstall(ActionInstall):
type = "reinstall"
- def check(self, check):
- log.debug(_("Running transaction test for %s") % self.pkg.friendly_name)
-
- # Check if this package can be reinstalled.
- check.remove(self.pkg)
- check.install(self.pkg)
-
- def run(self):
- # Remove package from the database and add it afterwards.
- # Sounds weird, but fixes broken entries in the database.
- self.local.rem_package(self.pkg_solv)
- self.local.add_package(self.pkg)
-
- self.pkg.extract(_("Reinstalling"), prefix=self.pakfire.path)
-
-class ActionDowngrade(Action):
+class ActionDowngrade(ActionInstall):
type = "downgrade"
-
- def check(self, check):
- log.debug(_("Running transaction test for %s") % self.pkg.friendly_name)
-
- # Check if this package can be downgraded.
- check.install(self.pkg)
-
- def run(self):
- # Add new package to database.
- self.local.add_package(self.pkg)
-
- self.pkg.extract(_("Downgrading"), prefix=self.pakfire.path)
import satsolver
import system
import util
+import _pakfire
import logging
log = logging.getLogger("pakfire")
from constants import *
from i18n import _
-from _pakfire import Transaction, sync
-_Transaction = Transaction
-
-PKG_DUMP_FORMAT = " %-21s %-8s %-21s %-18s %6s "
# Import all actions directly.
from actions import *
self.remove(pkg)
+class Step(object):
+ def __init__(self, pakfire, type, pkg):
+ self.pakfire = pakfire
+
+ self.type = type
+ self.pkg = pkg
+
+ @classmethod
+ def from_step(cls, pakfire, step):
+ pkg = packages.SolvPackage(pakfire, step.get_solvable())
+
+ return cls(pakfire, step.get_type(), pkg)
+
+ def __repr__(self):
+ return "<%s %s %s>" % (self.__class__.__name__, self.type, self.pkg)
+
+ def get_binary_pkg(self):
+ if not hasattr(self, "__binary_pkg"):
+ if self.type in (ActionCleanup.type, ActionRemove.type):
+ self.__binary_pkg = self.pkg.get_from_db()
+ assert self.__binary_pkg
+ else:
+ self.__binary_pkg = self.pkg.get_from_cache()
+
+ return self.__binary_pkg
+
+ def set_binary_pkg(self, pkg):
+ self.__binary_pkg = pkg
+
+ binary_pkg = property(get_binary_pkg, set_binary_pkg)
+
+ @property
+ def needs_download(self):
+ """
+ Returns True if the package file needs to be downloaded.
+ """
+ if not self.type in (ActionInstall.type, ActionReinstall.type,
+ ActionUpdate.type, ActionDowngrade.type):
+ return False
+
+ # If no binary version of the package has been found,
+ # we don't need to download anything
+ return not self.binary_pkg
+
+ def create_actions(self):
+ actions = []
+
+ for action_cls in Transaction.action_classes.get(self.type, []):
+ action = action_cls(self.pakfire, self.pkg, self.binary_pkg)
+ actions.append(action)
+
+ return actions
+
+
class Transaction(object):
action_classes = {
ActionInstall.type : [
def __init__(self, pakfire):
self.pakfire = pakfire
- self.actions = []
+ self._steps = []
self.installsizechange = 0
- self.__need_sort = False
-
def __nonzero__(self):
- if self.actions:
+ if self.steps:
return True
return False
transaction = cls(pakfire)
# Get transaction data from the solver.
- _transaction = _Transaction(solver.solver)
+ _transaction = _pakfire.Transaction(solver.solver)
# Save installsizechange.
transaction.installsizechange = _transaction.get_installsizechange()
# Get all steps that need to be done from the solver.
- steps = _transaction.steps()
-
- actions = []
- actions_post = []
-
- for step in steps:
- action_name = step.get_type()
- pkg = packages.SolvPackage(pakfire, step.get_solvable())
-
- transaction.add(action_name, pkg)
-
- # Sort all previously added actions.
- transaction.sort()
+ for step in _transaction.steps():
+ step = Step.from_step(pakfire, step)
+ transaction.add_step(step)
return transaction
# Shortcut to local repository.
return self.pakfire.repos.local
- def add(self, action_name, pkg):
- assert isinstance(pkg, packages.SolvPackage), pkg
-
- try:
- classes = self.action_classes[action_name]
- except KeyError:
- raise Exception, "Unknown action requires: %s" % action_name
-
- for cls in classes:
- action = cls(self.pakfire, pkg)
- assert isinstance(action, Action), action
-
- self.actions.append(action)
-
- self.__need_sort = True
+ @property
+ def steps(self):
+ return self._steps
- def sort(self):
+ def add_step(self, step):
"""
- Sort all actions.
+ Adds a new step to this transaction.
"""
- actions = []
- actions_pre = []
- actions_post = []
+ assert isinstance(step, Step), step
- for action in self.actions:
- if isinstance(action, ActionScriptPreTrans):
- actions_pre.append(action)
- elif isinstance(action, ActionScriptPostTrans):
- actions_post.append(action)
- else:
- actions.append(action)
+ self._steps.append(step)
- self.actions = actions_pre + actions + actions_post
- self.__need_sort = False
+ def get_steps_by_type(self, type):
+ return [s for s in self.steps if s.type == type]
@property
def installs(self):
- return [a.pkg for a in self.actions if isinstance(a, ActionInstall)]
+ return self.get_steps_by_type(ActionInstall.type)
@property
def reinstalls(self):
- return [a.pkg for a in self.actions if isinstance(a, ActionReinstall)]
+ return self.get_steps_by_type(ActionReinstall.type)
@property
def removes(self):
- return [a.pkg for a in self.actions if isinstance(a, ActionRemove)]
+ return self.get_steps_by_type(ActionRemove.type)
@property
def updates(self):
- return [a.pkg for a in self.actions if isinstance(a, ActionUpdate)]
+ return self.get_steps_by_type(ActionUpdate.type)
@property
def downgrades(self):
- return [a.pkg for a in self.actions if isinstance(a, ActionDowngrade)]
+ return self.get_steps_by_type(ActionDowngrade.type)
+
+ def get_downloads(self):
+ """
+ Returns a list of all steps that need
+ to a download.
+ """
+ return [s for s in self.steps if s.needs_download]
@property
- def downloads(self):
- return sorted([a.pkg_solv for a in self.actions if a.needs_download])
+ def download_size(self):
+ """
+ Returns the amount of bytes that need to be downloaded.
+ """
+ if not hasattr(self, "__download_size"):
+ self.__download_size = sum((s.pkg.size for s in self.get_downloads()))
+
+ return self.__download_size
def download(self, logger=None):
if logger is None:
logger = logging.getLogger("pakfire")
- # Get all download actions as a list.
- downloads = [d for d in self.downloads]
+ downloads = self.get_downloads()
# If there are no downloads, we can just stop here.
if not downloads:
return
- # Calculate downloadsize.
- download_size = sum([d.size for d in downloads])
-
# Get free space of the download location.
path = os.path.realpath(REPO_CACHE_DIR)
while not os.path.ismount(path):
path = os.path.dirname(path)
path_stat = os.statvfs(path)
- if download_size >= path_stat.f_bavail * path_stat.f_bsize:
+ if self.download_size >= path_stat.f_bavail * path_stat.f_bsize:
raise DownloadError, _("Not enough space to download %s of packages.") \
- % util.format_size(download_size)
+ % util.format_size(self.download_size)
logger.info(_("Downloading packages:"))
time_start = time.time()
- i = 0
- for pkg in downloads:
- i += 1
+ counter = 0
+ counter_downloads = len(downloads)
+ for step in downloads:
+ counter += 1
# Download the package file.
- bin_pkg = pkg.download(text="(%d/%d): " % (i, len(downloads)), logger=logger)
-
- # Search in every action if we need to replace the package.
- for action in self.actions:
- if not action.pkg_solv.uuid == bin_pkg.uuid:
- continue
-
- # Replace the package.
- action.pkg = bin_pkg
+ step.binary_pkg = step.pkg.download(
+ text="(%d/%d): " % (counter, counter_downloads),
+ logger=logger)
# Write an empty line to the console when there have been any downloads.
width, height = util.terminal_size()
# Format and calculate download information.
time_stop = time.time()
download_time = time_stop - time_start
- download_speed = download_size / download_time
+ download_speed = self.download_size / download_time
download_speed = util.format_speed(download_speed)
- download_size = util.format_size(download_size)
+ download_size = util.format_size(self.download_size)
download_time = util.format_time(download_time)
line = "%s | %5sB %s " % \
- (download_speed, download_size, download_time)
+ (download_speed, self.download_size, download_time)
line = " " * (width - len(line)) + line
logger.info(line)
logger.info("")
- def dump_pkg(self, pkg):
- ret = []
-
- name = pkg.name
- if len(name) > 21:
- ret.append(" %s" % name)
- name = ""
-
- ret.append(PKG_DUMP_FORMAT % (name, pkg.arch, pkg.friendly_version,
- pkg.repo.name, util.format_size(pkg.size)))
+ def print_section(self, caption, steps, format_string):
+ section = [caption,]
- return ret
+ pkgs = [s.pkg for s in steps]
+ for pkg in sorted(pkgs):
+ line = format_string % {
+ "arch" : pkg.arch,
+ "name" : pkg.name,
+ "repo" : pkg.repo.name,
+ "size" : util.format_size(pkg.size),
+ "version" : pkg.friendly_version,
+ }
+ section.append(line)
- def dump_pkgs(self, caption, pkgs):
- if not pkgs:
- return []
+ section.append("")
- s = [caption,]
- for pkg in sorted(pkgs):
- s += self.dump_pkg(pkg)
- s.append("")
- return s
+ return section
def dump(self, logger=None):
if logger is None:
logger = logging.getLogger("pakfire")
- if not self.actions:
+ if not self.steps:
logger.info(_("Nothing to do"))
return
- width = 80
- line = "=" * width
-
- s = [""]
- s.append(line)
- s.append(PKG_DUMP_FORMAT % (_("Package"), _("Arch"), _("Version"),
- _("Repository"), _("Size")))
- s.append(line)
-
- actions = (
- (_("Installing:"), self.installs),
- (_("Reinstalling:"), self.reinstalls),
- (_("Updating:"), self.updates),
- (_("Downgrading:"), self.downgrades),
- (_("Removing:"), self.removes),
+ # Prepare some string formatting stuff.
+ # XXX this needs to adapt to the terminal size
+ format_string = " %(name)-21s %(arch)-8s %(version)-21s %(repo)-18s %(size)6s "
+
+ # Prepare the headline.
+ headline = format_string % {
+ "arch" : _("Arch"),
+ "name" : _("Package"),
+ "repo" : _("Repository"),
+ "size" : _("Size"),
+ "version" : _("Version"),
+ }
+
+ # As long, as we can't use the actual terminal width, we use the
+ # length of the headline.
+ terminal_width = len(headline)
+
+ # Create a separator line.
+ sep_line = "=" * terminal_width
+
+ # Create the header.
+ s = [sep_line, headline, sep_line,]
+
+ steps = (
+ (_("Installing:"), self.installs),
+ (_("Reinstalling:"), self.reinstalls),
+ (_("Updating:"), self.updates),
+ (_("Downgrading:"), self.downgrades),
+ (_("Removing:"), self.removes),
)
- for caption, pkgs in actions:
- s += self.dump_pkgs(caption, pkgs)
+ for caption, _steps in steps:
+ if not _steps:
+ continue
+
+ s += self.print_section(caption, _steps, format_string)
+ # Append the transaction summary
s.append(_("Transaction Summary"))
- s.append(line)
+ s.append(sep_line)
- for caption, pkgs in actions:
- if not len(pkgs):
+ for caption, _steps in steps:
+ if not _steps:
continue
- s.append("%-20s %-4d %s" % (caption, len(pkgs),
- _("package", "packages", len(pkgs))))
+
+ s.append("%-20s %-4d %s" % (caption, len(_steps),
+ _("package", "packages", len(_steps))))
# Calculate the size of all files that need to be downloaded this this
# transaction.
- download_size = sum([d.size for d in self.downloads])
- if download_size:
- s.append(_("Total download size: %s") % util.format_size(download_size))
+ if self.download_size:
+ s.append(_("Total download size: %s") % util.format_size(self.download_size))
# Show the size that is consumed by the new packages.
if self.installsizechange > 0:
s.append(_("Installed size: %s") % util.format_size(self.installsizechange))
elif self.installsizechange < 0:
- freed_size = abs(self.installsizechange)
- s.append(_("Freed size: %s") % util.format_size(freed_size))
+ s.append(_("Freed size: %s") % util.format_size(-self.installsizechange))
s.append("")
for line in s:
def cli_yesno(self):
# Empty transactions are always denied.
- if not self.actions:
+ if not self.steps:
return False
return util.ask_user(_("Is this okay?"))
- def check(self, logger=None):
+ def check(self, actions, logger=None):
if logger is None:
logger = logging.getLogger("pakfire")
# Initialize the check object.
check = TransactionCheck(self.pakfire, self)
- for action in self.actions:
+ for action in actions:
try:
action.check(check)
except ActionError, e:
if mode == "disabled":
return
- # Search for actions we need to process.
- actions = []
- for action in self.actions:
- # Skip scripts.
- if isinstance(action, ActionScript):
+ # Search for steps we need to process.
+ steps = []
+ for step in self.steps:
+ if not step.binary_pkg:
continue
- actions.append(action)
+ steps.append(step)
# Make a nice progressbar.
- p = progressbar.ProgressBar(len(actions))
+ p = progressbar.ProgressBar(len(steps))
p.add(_("Verifying signatures..."))
p.add(progressbar.WidgetBar())
p.add(progressbar.WidgetPercentage())
# Do the verification for every action.
i = 0
- for action in actions:
+ for step in steps:
# Update the progressbar.
if p:
i += 1
p.update(i)
try:
- action.verify()
+ step.pkg.verify()
except SignatureError, e:
errors.append("%s" % e)
logger.warning(_("This is dangerous!"))
logger.warning("")
- def run(self, logger=None, signatures_mode=None):
- assert self.actions, "Cannot run an empty transaction."
- assert not self.__need_sort, "Did you forget to sort the transaction?"
+ def create_actions(self):
+ """
+ Create actions from steps.
+ """
+ actions = []
+ actions_pre = []
+ actions_post = []
+ for step in self.steps:
+ for action in step.create_actions():
+ if isinstance(action, ActionScriptPreTrans):
+ actions_pre.append(action)
+ elif isinstance(action, ActionScriptPostTrans):
+ actions_post.append(action)
+ else:
+ actions.append(action)
+
+ return actions_pre + actions + actions_post
+
+ def run(self, logger=None, signatures_mode=None):
if logger is None:
logger = logging.getLogger("pakfire")
self.download()
# Verify signatures.
- self.verify_signatures(mode=signatures_mode, logger=logger)
+ #self.verify_signatures(mode=signatures_mode, logger=logger)
+
+ # Create actions.
+ actions = self.create_actions()
# Run the transaction test
- self.check(logger=logger)
+ self.check(actions, logger=logger)
logger.info(_("Running transaction"))
# Run all actions in order and catch all kinds of ActionError.
- for action in self.actions:
+ for action in actions:
try:
action.run()
self.local.commit()
# Call sync to make sure all buffers are written to disk.
- sync()
+ _pakfire.sync()