]> git.ipfire.org Git - people/ms/pakfire.git/commitdiff
Major rewrite how transactions are built.
authorMichael Tremer <michael.tremer@ipfire.org>
Sat, 26 Oct 2013 19:47:51 +0000 (21:47 +0200)
committerMichael Tremer <michael.tremer@ipfire.org>
Sat, 26 Oct 2013 19:47:51 +0000 (21:47 +0200)
The result from the solver is now converted into Steps
which are used to present the transaction to the user
and search for packages already in the cache, etc.
After that, the actions are created.
The Action class has been much simplified and made
less complex.

src/_pakfire/solvable.c
src/pakfire/actions.py
src/pakfire/packages/solv.py
src/pakfire/repository/database.py
src/pakfire/repository/system.py
src/pakfire/transaction.py

index d6b826d08194c71138d652edb13027178643905c..04c605d6552df90a3e4830227712b3b51cbe8936 100644 (file)
@@ -87,7 +87,7 @@ PyObject *Solvable_string(SolvableObject *self) {
 PyObject *Solvable_get_name(SolvableObject *self) {
        Solvable *solvable = pool_id2solvable(self->_pool, self->_id);
 
-       const char *name = pool_id2str(solvable->repo->pool, solvable->name);
+       const char *name = pool_id2str(self->_pool, solvable->name);
 
        return Py_BuildValue("s", name);
 }
@@ -95,7 +95,7 @@ PyObject *Solvable_get_name(SolvableObject *self) {
 PyObject *Solvable_get_evr(SolvableObject *self) {
        Solvable *solvable = pool_id2solvable(self->_pool, self->_id);
 
-       const char *evr = pool_id2str(solvable->repo->pool, solvable->evr);
+       const char *evr = pool_id2str(self->_pool, solvable->evr);
 
        return Py_BuildValue("s", evr);
 }
@@ -103,7 +103,7 @@ PyObject *Solvable_get_evr(SolvableObject *self) {
 PyObject *Solvable_get_arch(SolvableObject *self) {
        Solvable *solvable = pool_id2solvable(self->_pool, self->_id);
 
-       const char *arch = pool_id2str(solvable->repo->pool, solvable->arch);
+       const char *arch = pool_id2str(self->_pool, solvable->arch);
 
        return Py_BuildValue("s", arch);
 }
@@ -111,7 +111,7 @@ PyObject *Solvable_get_arch(SolvableObject *self) {
 PyObject *Solvable_get_vendor(SolvableObject *self) {
        Solvable *solvable = pool_id2solvable(self->_pool, self->_id);
 
-       const char *vendor = pool_id2str(solvable->repo->pool, solvable->vendor);
+       const char *vendor = pool_id2str(self->_pool, solvable->vendor);
 
        return Py_BuildValue("s", vendor);
 }
@@ -132,6 +132,7 @@ PyObject *Solvable_set_vendor(SolvableObject *self, PyObject *args) {
 
 PyObject *Solvable_get_repo_name(SolvableObject *self) {
        Solvable *solvable = pool_id2solvable(self->_pool, self->_id);
+       assert(solvable->repo);
 
        return Py_BuildValue("s", solvable->repo->name);
 }
index 691cd73686d8798ab3e17d0327ea7d3b12949bc0..2b044b2f88b8009e9dfea26681dc9102d8e126e8 100644 (file)
@@ -33,15 +33,12 @@ from constants import *
 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()
 
@@ -60,7 +57,7 @@ class Action(object):
                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.
@@ -78,15 +75,31 @@ class Action(object):
                        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
@@ -398,7 +411,16 @@ class ActionInstall(Action):
                # 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.
@@ -423,7 +445,7 @@ class ActionInstall(Action):
                                log.debug("ldconfig is not present or not executable.")
 
 
-class ActionUpdate(Action):
+class ActionUpdate(ActionInstall):
        type = "upgrade"
 
        def check(self, check):
@@ -432,22 +454,10 @@ class ActionUpdate(Action):
                # 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)
 
@@ -455,65 +465,30 @@ class ActionRemove(Action):
                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)
index af2c9474db508aff1e5025b59c37275ec1feafb0..d6a8a407bfb518fc9eb11c72a9e59987c2c9b6e6 100644 (file)
@@ -247,6 +247,9 @@ class SolvPackage(base.Package):
                        path = self.repo.cache.abspath(self.cache_filename)
                        return file.BinaryPackage(self.pakfire, self.repo, path)
 
+       def get_from_db(self):
+               return self.pakfire.repos.local.get_package_by_uuid(self.uuid)
+
        def download(self, text="", logger=None):
                if not self.repo.local:
                        self.repo.download(self, text=text, logger=logger)
index 379ae1c69e580175b31d31a2172c5d151c5135fb..4b3506e69bb76f7a52e03d10b88234e94891e3bb 100644 (file)
@@ -415,27 +415,16 @@ class DatabaseLocal(Database):
                c.close()
 
        def rem_package(self, pkg):
-               log.debug("Removing package from database: %s" % pkg.friendly_name)
-
-               assert pkg.uuid
-
-               # Get the ID of the package in the database.
-               c = self.cursor()
-               c.execute("SELECT id FROM packages WHERE uuid = ? LIMIT 1", (pkg.uuid,))
-               #c.execute("SELECT id FROM packages WHERE name = ? AND epoch = ? AND version = ?"
-               #       " AND release = ? LIMIT 1", (pkg.name, pkg.epoch, pkg.version, pkg.release,))
-
-               row = c.fetchone()
-               if not row:
-                       return
+               assert isinstance(pkg, packages.DatabasePackage), pkg
 
-               id = row["id"]
+               log.debug("Removing package from database: %s" % pkg.friendly_name)
 
                # First, delete all files from the database and then delete the pkg itself.
-               c.execute("DELETE FROM files WHERE pkg = ?", (id,))
-               c.execute("DELETE FROM packages WHERE id = ?", (id,))
-
+               c = self.cursor()
+               c.execute("DELETE FROM files WHERE pkg = ?", (pkg.id,))
+               c.execute("DELETE FROM packages WHERE id = ?", (pkg.id,))
                c.close()
+
                self.commit()
 
        def get_package_by_id(self, id):
@@ -449,6 +438,17 @@ class DatabaseLocal(Database):
                finally:
                        c.close()
 
+       def get_package_by_uuid(self, uuid):
+               c = self.cursor()
+               c.execute("SELECT * FROM packages WHERE uuid = ?", (uuid,))
+
+               try:
+                       for row in c:
+                               return packages.DatabasePackage(self.pakfire, self.repo, self, row)
+
+               finally:
+                       c.close()
+
        @property
        def packages(self):
                c = self.db.execute("SELECT * FROM packages ORDER BY name")
@@ -466,14 +466,12 @@ class DatabaseLocal(Database):
        def get_package_from_solv(self, solv_pkg):
                assert solv_pkg.uuid
 
-               c = self.db.execute("SELECT * FROM packages WHERE uuid = ? LIMIT 1", (solv_pkg.uuid,))
+               c = self.cursor()
+               c.execute("SELECT * FROM packages WHERE uuid = ? LIMIT 1", (solv_pkg.uuid,))
 
                try:
-                       row = c.fetchone()
-                       if row is None:
-                               return
-
-                       return packages.DatabasePackage(self.pakfire, self.repo, self, row)
+                       for row in c:
+                               return packages.DatabasePackage(self.pakfire, self.repo, self, row)
 
                finally:
                        c.close()
index 2d14d3e6d379ae75d7b57a7f2fbca5393e57beb6..6a02fbe27810b19003b5fe3a0b6c92fa2d008ec3 100644 (file)
@@ -108,11 +108,20 @@ class RepositorySystem(base.RepositoryFactory):
                self.index.add_package(pkg)
 
        def rem_package(self, pkg):
-               assert isinstance(pkg, packages.SolvPackage), pkg
+               if isinstance(pkg, packages.SolvPackage):
+                       pkg = pkg.get_from_db()
+
+                       # If the package can not be found in the database,
+                       # we cannot remove it. This does not seem right...
+                       if pkg is None:
+                               return
 
                # Remove package from the database.
                self.db.rem_package(pkg)
 
+       def get_package_by_uuid(self, uuid):
+               return self.db.get_package_by_uuid(uuid)
+
        @property
        def filelist(self):
                return self.db.get_filelist()
index 94fdb594ac336339ede64fe0db20e4208a9c4391..035598b051881a20537ea89d11240f1c6170facc 100644 (file)
@@ -29,16 +29,13 @@ import packages
 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 *
@@ -155,6 +152,60 @@ class TransactionCheck(object):
                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 : [
@@ -199,14 +250,12 @@ class Transaction(object):
 
        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
@@ -217,25 +266,15 @@ class Transaction(object):
                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
 
@@ -244,106 +283,90 @@ class Transaction(object):
                # 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()
@@ -354,89 +377,101 @@ class Transaction(object):
                # 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:
@@ -444,12 +479,12 @@ class Transaction(object):
 
        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")
 
@@ -458,7 +493,7 @@ class Transaction(object):
                # 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:
@@ -488,17 +523,16 @@ class Transaction(object):
                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())
@@ -511,14 +545,14 @@ class Transaction(object):
 
                        # 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)
@@ -544,10 +578,26 @@ class Transaction(object):
                        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")
 
@@ -557,14 +607,17 @@ class Transaction(object):
                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()
 
@@ -579,4 +632,4 @@ class Transaction(object):
                self.local.commit()
 
                # Call sync to make sure all buffers are written to disk.
-               sync()
+               _pakfire.sync()