]> git.ipfire.org Git - people/stevee/pakfire.git/blobdiff - src/libpakfire/transaction.c
digests: Split off into a new set of files
[people/stevee/pakfire.git] / src / libpakfire / transaction.c
index d5de4ee09c3214821221571954be333c2811afe8..f2f1fbce0f1c87ba289eb590df5dced959eb103f 100644 (file)
 #                                                                             #
 #############################################################################*/
 
-#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include <openssl/crypto.h>
+#include <openssl/evp.h>
+
+#include <solv/pool_fileconflicts.h>
 #include <solv/transaction.h>
 
+#include <pakfire/archive.h>
+#include <pakfire/db.h>
+#include <pakfire/digest.h>
+#include <pakfire/downloader.h>
+#include <pakfire/file.h>
+#include <pakfire/filelist.h>
 #include <pakfire/i18n.h>
+#include <pakfire/jail.h>
 #include <pakfire/logging.h>
 #include <pakfire/package.h>
-#include <pakfire/packagelist.h>
 #include <pakfire/pakfire.h>
-#include <pakfire/pool.h>
 #include <pakfire/private.h>
 #include <pakfire/repo.h>
-#include <pakfire/step.h>
+#include <pakfire/string.h>
 #include <pakfire/transaction.h>
-#include <pakfire/types.h>
+#include <pakfire/ui.h>
 #include <pakfire/util.h>
 
-struct _PakfireTransaction {
-       Pakfire pakfire;
-       Transaction* transaction;
-       PakfireStep* steps;
-       size_t num_steps;
+struct pakfire_transaction {
+       struct pakfire* pakfire;
        int nrefs;
+
+       Transaction* transaction;
+       char** userinstalled;
+
+       struct pakfire_archive** archives;
+       struct pakfire_package** packages;
+       size_t num;
+       size_t progress;
+
+       // Callbacks
+       struct pakfire_transaction_callbacks {
+               // Status
+               pakfire_status_callback status;
+               void* status_data;
+       } callbacks;
 };
 
-PAKFIRE_EXPORT PakfireTransaction pakfire_transaction_create(Pakfire pakfire, Transaction* trans) {
-       PakfireTransaction transaction = pakfire_calloc(1, sizeof(*transaction));
-       if (transaction) {
-               DEBUG("Allocated Transaction at %p\n", transaction);
-               transaction->nrefs = 1;
+enum pakfire_actions {
+       PAKFIRE_ACTION_NOOP = 0,
+       PAKFIRE_ACTION_VERIFY,
+       PAKFIRE_ACTION_EXECUTE,
+       PAKFIRE_ACTION_PRETRANS,
+       PAKFIRE_ACTION_POSTTRANS,
+};
 
-               transaction->pakfire = pakfire_ref(pakfire);
+enum pakfire_steps {
+       PAKFIRE_STEP_IGNORE = 0,
+       PAKFIRE_STEP_INSTALL,
+       PAKFIRE_STEP_REINSTALL,
+       PAKFIRE_STEP_ERASE,
+       PAKFIRE_STEP_UPGRADE,
+       PAKFIRE_STEP_DOWNGRADE,
+       PAKFIRE_STEP_OBSOLETE,
+};
 
-               // Clone the transaction, so we get independent from what ever called this.
-               if (trans) {
-                       transaction->transaction = transaction_create_clone(trans);
-                       transaction_order(transaction->transaction, 0);
-               } else {
-                       transaction->transaction = transaction_create(trans->pool);
-               }
+static enum pakfire_steps pakfire_transaction_get_step_type(
+               struct pakfire_transaction* transaction, struct pakfire_package* pkg) {
+       int type = transaction_type(transaction->transaction, pakfire_package_id(pkg),
+               SOLVER_TRANSACTION_SHOW_ACTIVE|SOLVER_TRANSACTION_CHANGE_IS_REINSTALL);
 
-               // Save total number of steps
-               transaction->num_steps = transaction->transaction->steps.count;
+       // Translate solver types into our own types
+       switch (type) {
+               case SOLVER_TRANSACTION_INSTALL:
+               case SOLVER_TRANSACTION_MULTIINSTALL:
+                       return PAKFIRE_STEP_INSTALL;
 
-               // Import all steps
-               PakfireStep* steps = transaction->steps = pakfire_calloc(transaction->num_steps + 1, sizeof(*steps));
-               for (unsigned int i = 0; i < transaction->num_steps; i++)
-                       *steps++ = pakfire_step_create(transaction, transaction->transaction->steps.elements[i]);
-       }
+               case SOLVER_TRANSACTION_REINSTALL:
+               case SOLVER_TRANSACTION_MULTIREINSTALL:
+                       return PAKFIRE_STEP_REINSTALL;
 
-       return transaction;
+               case SOLVER_TRANSACTION_ERASE:
+                       return PAKFIRE_STEP_ERASE;
+
+               case SOLVER_TRANSACTION_DOWNGRADE:
+                       return PAKFIRE_STEP_DOWNGRADE;
+
+               case SOLVER_TRANSACTION_UPGRADE:
+                       return PAKFIRE_STEP_UPGRADE;
+
+               case SOLVER_TRANSACTION_OBSOLETES:
+                       return PAKFIRE_STEP_OBSOLETE;
+
+               // Anything we don't care about
+               case SOLVER_TRANSACTION_IGNORE:
+               case SOLVER_TRANSACTION_REINSTALLED:
+               case SOLVER_TRANSACTION_DOWNGRADED:
+               default:
+                               return PAKFIRE_STEP_IGNORE;
+       }
 }
 
-PAKFIRE_EXPORT PakfireTransaction pakfire_transaction_ref(PakfireTransaction transaction) {
-       if (!transaction)
-               return NULL;
+static void pakfire_transaction_free_archives_and_packages(
+               struct pakfire_transaction* transaction) {
+       if (transaction->archives) {
+               for (unsigned int i = 0; i < transaction->num; i++)
+                       if (transaction->archives[i])
+                               pakfire_archive_unref(transaction->archives[i]);
+               free(transaction->archives);
 
-       transaction->nrefs++;
-       return transaction;
+               transaction->archives = NULL;
+       }
+
+       if (transaction->packages) {
+               for (unsigned int i = 0; i < transaction->num; i++)
+                       if (transaction->packages[i])
+                               pakfire_package_unref(transaction->packages[i]);
+               free(transaction->packages);
+
+               transaction->packages = NULL;
+       }
 }
 
-void pakfire_transaction_free(PakfireTransaction transaction) {
+static void pakfire_transaction_free(struct pakfire_transaction* transaction) {
+       pakfire_transaction_free_archives_and_packages(transaction);
+
+       if (transaction->userinstalled) {
+               for (char** userinstalled = transaction->userinstalled; *userinstalled; userinstalled++)
+                       free(*userinstalled);
+               free(transaction->userinstalled);
+       }
+       transaction_free(transaction->transaction);
+
        pakfire_unref(transaction->pakfire);
+       free(transaction);
+}
 
-       // Release all steps
-       while (*transaction->steps)
-               pakfire_step_unref(*transaction->steps++);
+static int pakfire_transaction_import_transaction(
+               struct pakfire_transaction* transaction, Solver* solver) {
+       // Clone the transaction to keep a copy of it
+       Transaction* t = solver_create_transaction(solver);
+       if (!t)
+               return 1;
 
-       transaction_free(transaction->transaction);
-       pakfire_free(transaction);
+       transaction->transaction = t;
+
+       // Order the transaction
+       transaction_order(t, 0);
+
+       // Free any previous content
+       pakfire_transaction_free_archives_and_packages(transaction);
 
-       DEBUG("Released Transaction at %p\n", transaction);
+       // How many steps?
+       transaction->num = t->steps.count;
+
+       // Allocate space for packages
+       transaction->packages = calloc(transaction->num, sizeof(*transaction->packages));
+       if (!transaction->packages)
+               return 1;
+
+       // Allocate space for archives
+       transaction->archives = calloc(transaction->num, sizeof(*transaction->archives));
+       if (!transaction->archives)
+               return 1;
+
+       // Create all packages
+       for (unsigned int i = 0; i < transaction->num; i++) {
+               transaction->packages[i] = pakfire_package_create_from_solvable(
+                       transaction->pakfire, t->steps.elements[i]);
+       }
+
+       return 0;
 }
 
-PAKFIRE_EXPORT PakfireTransaction pakfire_transaction_unref(PakfireTransaction transaction) {
-       if (!transaction)
-               return NULL;
+static int pakfire_transaction_import_userinstalled(
+               struct pakfire_transaction* t, Solver* solver) {
+       if (t->userinstalled) {
+               free(t->userinstalled);
+               t->userinstalled = NULL;
+       }
+
+       Queue userinstalled;
+       queue_init(&userinstalled);
 
+       // Fetch a list of all packages that are installed by the user
+       solver_get_userinstalled(solver, &userinstalled, GET_USERINSTALLED_NAMES);
+
+       // Skip everything if the queue is empty
+       if (!userinstalled.count)
+               return 0;
+
+       t->userinstalled = calloc(userinstalled.count + 1, sizeof(*t->userinstalled));
+       if (!t->userinstalled) {
+               ERROR(t->pakfire, "Could not allocate userinstalled\n");
+               return 1;
+       }
+
+       Pool* pool = pakfire_get_solv_pool(t->pakfire);
+
+       // Store the names of all userinstalled packages
+       for (int i = 0; i < userinstalled.count; i++) {
+               const char* package = pool_id2str(pool, userinstalled.elements[i]);
+               t->userinstalled[i] = strdup(package);
+       }
+
+       return 0;
+}
+
+int pakfire_transaction_create(struct pakfire_transaction** transaction,
+               struct pakfire* pakfire, Solver* solver) {
+       struct pakfire_transaction* t = calloc(1, sizeof(*t));
+       if (!t)
+               return ENOMEM;
+
+       // Store reference to Pakfire
+       t->pakfire = pakfire_ref(pakfire);
+
+       // Initialize the reference counter
+       t->nrefs = 1;
+
+       // Import transaction
+       int r = pakfire_transaction_import_transaction(t, solver);
+       if (r)
+               goto ERROR;
+
+       // Import userinstalled
+       r = pakfire_transaction_import_userinstalled(t, solver);
+       if (r)
+               goto ERROR;
+
+       *transaction = t;
+       return 0;
+
+ERROR:
+       pakfire_transaction_free(t);
+       return 1;
+}
+
+PAKFIRE_EXPORT struct pakfire_transaction* pakfire_transaction_ref(
+               struct pakfire_transaction* transaction) {
+       transaction->nrefs++;
+
+       return transaction;
+}
+
+PAKFIRE_EXPORT struct pakfire_transaction* pakfire_transaction_unref(
+               struct pakfire_transaction* transaction) {
        if (--transaction->nrefs > 0)
                return transaction;
 
@@ -102,260 +271,1036 @@ PAKFIRE_EXPORT PakfireTransaction pakfire_transaction_unref(PakfireTransaction t
        return NULL;
 }
 
-PAKFIRE_EXPORT PakfirePool pakfire_transaction_get_pool(PakfireTransaction transaction) {
-       return pakfire_get_pool(transaction->pakfire);
+void pakfire_transaction_set_status_callback(struct pakfire_transaction* transaction,
+               pakfire_status_callback callback, void* data) {
+       transaction->callbacks.status = callback;
+       transaction->callbacks.status_data = data;
 }
 
-Transaction* pakfire_transaction_get_transaction(PakfireTransaction transaction) {
-       return transaction->transaction;
+static int pakfire_transaction_get_progress(struct pakfire_transaction* transaction) {
+       return transaction->progress * 100 / transaction->num;
 }
 
-PAKFIRE_EXPORT size_t pakfire_transaction_count(PakfireTransaction transaction) {
-       return transaction->num_steps;
+static void pakfire_transaction_status(struct pakfire_transaction* transaction,
+               const char* message, ...) {
+       char* buffer = NULL;
+       va_list args;
+       int r;
+
+       // Do nothing if callback isn't set
+       if (!transaction->callbacks.status)
+               return;
+
+       // Format the message
+       if (message) {
+               va_start(args, message);
+               r = vasprintf(&buffer, message, args);
+               va_end(args);
+
+               if (r < 0)
+                       return;
+       }
+
+       // Fetch progress
+       const int progress = pakfire_transaction_get_progress(transaction);
+
+       // Call the callback
+       transaction->callbacks.status(transaction->pakfire,
+               transaction->callbacks.status_data, progress, buffer);
+
+       // Cleanup
+       if (buffer)
+               free(buffer);
+}
+
+PAKFIRE_EXPORT size_t pakfire_transaction_count(struct pakfire_transaction* transaction) {
+       return transaction->num;
 }
 
-PAKFIRE_EXPORT ssize_t pakfire_transaction_installsizechange(PakfireTransaction transaction) {
+static ssize_t pakfire_transaction_installsizechange(struct pakfire_transaction* transaction) {
        ssize_t sizechange = transaction_calc_installsizechange(transaction->transaction);
 
        // Convert from kbytes to bytes
        return sizechange * 1024;
 }
 
-PAKFIRE_EXPORT ssize_t pakfire_transaction_downloadsize(PakfireTransaction transaction) {
+static ssize_t pakfire_transaction_downloadsize(struct pakfire_transaction* transaction) {
        ssize_t size = 0;
 
-       PakfireStep* steps = transaction->steps;
-       while (*steps) {
-               PakfireStep step = *steps++;
-
-               size += pakfire_step_get_downloadsize(step);
-       }
+       for (unsigned int i = 0; i < transaction->num; i++)
+               size += pakfire_package_get_downloadsize(transaction->packages[i]);
 
        return size;
 }
 
-PAKFIRE_EXPORT PakfireStep pakfire_transaction_get_step(PakfireTransaction transaction, unsigned int index) {
-       PakfireStep* steps = transaction->steps;
-
-       while (index-- && *steps)
-               steps++;
-
-       if (*steps)
-               return pakfire_step_ref(*steps);
+static void pakfire_transaction_append_line(char*** lines, const char* format, ...) {
+       if (!lines)
+               return;
 
-       return NULL;
-}
-
-PAKFIRE_EXPORT PakfirePackageList pakfire_transaction_get_packages(PakfireTransaction transaction, pakfire_step_type_t type) {
-       PakfirePackageList packagelist = pakfire_packagelist_create();
+       char* buffer = NULL;
+       va_list args;
+       int r;
 
-       PakfireStep* steps = transaction->steps;
-       while (*steps) {
-               PakfireStep step = *steps++;
+       va_start(args, format);
+       r = vasprintf(&buffer, format, args);
+       va_end(args);
 
-               if (pakfire_step_get_type(step) == type) {
-                       PakfirePackage package = pakfire_step_get_package(step);
-                       pakfire_packagelist_push(packagelist, package);
+       if (r < 0)
+               return;
 
-                       pakfire_package_unref(package);
-               }
+       // Count lines
+       unsigned int count = 0;
+       if (*lines) {
+               for (char** l = *lines; *l; l++)
+                       count++;
        }
 
-       // Sort list in place
-       pakfire_packagelist_sort(packagelist);
+       // Increase size of array
+       *lines = reallocarray(*lines, count + 2, sizeof(**lines));
+       if (!*lines)
+               return;
 
-       return packagelist;
+       // Append line and terminate lines
+       (*lines)[count] = buffer;
+       (*lines)[count + 1] = NULL;
 }
 
-static void pakfire_transaction_add_headline(char** str, size_t width, const char* headline) {
-       assert(headline);
-
-       asprintf(str, "%s%s\n", *str, headline);
+static void pakfire_transaction_add_headline(char*** lines, size_t width, const char* headline) {
+       pakfire_transaction_append_line(lines, "%s\n", headline);
 }
 
-static void pakfire_transaction_add_newline(char** str, size_t width) {
-       asprintf(str, "%s\n", *str);
+static void pakfire_transaction_add_newline(char*** lines, size_t width) {
+       pakfire_transaction_append_line(lines, "\n");
 }
 
-static void pakfire_transaction_add_line(char** str, size_t width, const char* name,
+static void pakfire_transaction_add_line(char*** lines, size_t width, const char* name,
                const char* arch, const char* version, const char* repo, const char* size) {
-       // XXX need to adapt to size
-       asprintf(str, "%s %-21s %-8s %-21s %-18s %6s \n", *str, name, arch, version, repo, size);
+       pakfire_transaction_append_line(lines, " %-21s %-8s %-21s %-18s %6s \n",
+               name, arch, version, repo, size);
 }
 
-static void pakfire_transaction_add_package(char** str, size_t width, PakfirePackage pkg) {
-       PakfireRepo repo = pakfire_package_get_repo(pkg);
+static void pakfire_transaction_add_package(char*** lines, size_t width, struct pakfire_package* pkg) {
+       char size[128];
 
-       unsigned long long size = pakfire_package_get_size(pkg);
-       char* size_str = pakfire_format_size(size);
+       struct pakfire_repo* repo = pakfire_package_get_repo(pkg);
 
-       pakfire_transaction_add_line(str, width,
+       // Format size
+       int r = pakfire_format_size(size, pakfire_package_get_size(pkg));
+       if (r < 0)
+               return;
+
+       pakfire_transaction_add_line(lines, width,
                pakfire_package_get_name(pkg),
                pakfire_package_get_arch(pkg),
                pakfire_package_get_evr(pkg),
                pakfire_repo_get_name(repo),
-               size_str
+               size
        );
 
        pakfire_repo_unref(repo);
-       pakfire_free(size_str);
 }
 
-static void pakfire_transaction_add_separator(char** str, size_t width) {
-       while (width-- > 0)
-               asprintf(str, "%s=", *str);
+static void pakfire_transaction_add_package_change(char*** lines, size_t width,
+               struct pakfire_package* old_pkg, struct pakfire_package* new_pkg) {
+       // Print the new package first
+       pakfire_transaction_add_package(lines, width, new_pkg);
 
-       // newline
-       asprintf(str, "%s\n", *str);
+       pakfire_transaction_append_line(lines,
+               "   --> %s\n", pakfire_package_get_nevra(old_pkg));
 }
 
-static size_t pakfire_transaction_add_section(char** str, size_t width, PakfireTransaction transaction,
-               const char* headline, pakfire_step_type_t type) {
-       PakfirePackageList list = pakfire_transaction_get_packages(transaction, type);
-
-       // Nothing to do if there are no packages in this stage
-       size_t c = pakfire_packagelist_count(list);
-       if (c == 0)
-               goto END;
+static void pakfire_transaction_add_separator(char*** lines, size_t width) {
+       char* separator = alloca(width + 1);
 
-       // Headline
-       pakfire_transaction_add_headline(str, width, headline);
+       for (unsigned int i = 0; i < width; i++)
+               separator[i] = '=';
+       separator[width] = '\0';
 
-       // List each package
-       for (unsigned int i = 0; i < c; i++) {
-               PakfirePackage pkg = pakfire_packagelist_get(list, i);
-               pakfire_transaction_add_package(str, width, pkg);
-               pakfire_package_unref(pkg);
-       }
+       pakfire_transaction_append_line(lines, "%s\n", separator);
+}
 
-       // newline
-       pakfire_transaction_add_newline(str, width);
+static void pakfire_transaction_add_usage_line(char*** lines, size_t width,
+               const char* headline, ssize_t size) {
+       char buffer[128];
 
-END:
-       pakfire_packagelist_unref(list);
+       int r = pakfire_format_size(buffer, size);
+       if (r < 0)
+               return;
 
-       return c;
+       pakfire_transaction_append_line(lines,  "%-21s: %s\n", headline, buffer);
 }
 
-static void pakfire_transaction_add_summary_line(char** str, size_t width, const char* headline, size_t pkgs) {
-       if (pkgs > 0)
-               asprintf(str, "%s%-20s %-4zu %s\n", *str, headline, pkgs, _("package(s)"));
-}
+static char* pakfire_transaction_join_lines(char** lines) {
+       if (!lines)
+               return NULL;
+
+       size_t size = 0;
+
+       // Determine total length
+       for (char** line = lines; *line; line++)
+               size += strlen(*line);
 
-static void pakfire_transaction_add_usage_line(char** str, size_t width, const char* headline, ssize_t size) {
-       char* s = pakfire_format_size(size);
+       // Allocate memory large enough to hold the result
+       char* s = calloc(1, size + 1);
+       if (!s)
+               return s;
 
-       asprintf(str, "%s%-21s: %s\n", *str, headline, s);
+       char* p = s;
+
+       for (char** line = lines; *line; line++) {
+               p += snprintf(p, s - p - 1, "%s", *line);
+       }
 
-       pakfire_free(s);
+       return s;
 }
 
-PAKFIRE_EXPORT char* pakfire_transaction_dump(PakfireTransaction transaction, size_t width) {
-       char* string = "";
+PAKFIRE_EXPORT char* pakfire_transaction_dump(struct pakfire_transaction* transaction, size_t width) {
+       char headline[1024];
+
+       Pool* pool = transaction->transaction->pool;
+       const int mode =
+               SOLVER_TRANSACTION_SHOW_OBSOLETES |
+               SOLVER_TRANSACTION_OBSOLETE_IS_UPGRADE;
+
+       char** lines = NULL;
 
        // Header
-       pakfire_transaction_add_separator(&string, width);
-       pakfire_transaction_add_line(&string, width,
+       pakfire_transaction_add_separator(&lines, width);
+       pakfire_transaction_add_line(&lines, width,
                _("Package"),
                _("Arch"),
                _("Version"),
                _("Repository"),
                _("Size")
        );
-       pakfire_transaction_add_separator(&string, width);
-
-       // Show what we are doing
-       size_t installing = pakfire_transaction_add_section(&string, width, transaction,
-               _("Installing:"), PAKFIRE_STEP_INSTALL);
-       size_t reinstalling = pakfire_transaction_add_section(&string, width, transaction,
-               _("Reinstalling:"), PAKFIRE_STEP_REINSTALL);
-       size_t updating = pakfire_transaction_add_section(&string, width, transaction,
-               _("Updating:"), PAKFIRE_STEP_UPGRADE);
-       size_t downgrading = pakfire_transaction_add_section(&string, width, transaction,
-               _("Downgrading:"), PAKFIRE_STEP_DOWNGRADE);
-       size_t removing = pakfire_transaction_add_section(&string, width, transaction,
-               _("Removing:"), PAKFIRE_STEP_ERASE);
-       size_t obsoleting = pakfire_transaction_add_section(&string, width, transaction,
-               _("Obsoleting:"), PAKFIRE_STEP_OBSOLETE);
+       pakfire_transaction_add_separator(&lines, width);
+
+       Queue classes;
+       queue_init(&classes);
+
+       // Get all classes
+       transaction_classify(transaction->transaction, mode, &classes);
+
+       Queue pkgs;
+       queue_init(&pkgs);
+
+       /*
+               The classes queue now contains a list of all classes as a tuple of:
+                       * The class type
+                       * The number of packages in this class
+                       * The from ID (for arch/vendor change)
+                       * The to ID (for arch/vendor change)
+       */
+       for (int i = 0; i < classes.count; i += 4) {
+               Id class = classes.elements[i];
+               unsigned int count = classes.elements[i+1];
+
+               const char* from = pool_id2str(pool, classes.elements[i+2]);
+               const char* to   = pool_id2str(pool, classes.elements[i+3]);
+
+               switch (class) {
+                       case SOLVER_TRANSACTION_INSTALL:
+                               if (count)
+                                       pakfire_string_format(headline, _("Installing %u packages:"), count);
+                               else
+                                       pakfire_string_set(headline, _("Installing one package:"));
+                               break;
+
+                       case SOLVER_TRANSACTION_REINSTALLED:
+                               if (count)
+                                       pakfire_string_format(headline, _("Reinstalling %u packages:"), count);
+                               else
+                                       pakfire_string_set(headline, _("Reinstalling one package:"));
+                               break;
+
+                       case SOLVER_TRANSACTION_ERASE:
+                               if (count)
+                                       pakfire_string_format(headline, _("Removing %u packages:"), count);
+                               else
+                                       pakfire_string_set(headline, _("Removing one package:"));
+                               break;
+
+                       case SOLVER_TRANSACTION_UPGRADED:
+                               if (count)
+                                       pakfire_string_format(headline, _("Updating %u packages:"), count);
+                               else
+                                       pakfire_string_set(headline, _("Updating one package:"));
+                               break;
+
+                       case SOLVER_TRANSACTION_DOWNGRADED:
+                               if (count)
+                                       pakfire_string_format(headline, _("Downgrading %u packages:"), count);
+                               else
+                                       pakfire_string_set(headline, _("Downgrading one package:"));
+                               break;
+
+                       case SOLVER_TRANSACTION_CHANGED:
+                               if (count)
+                                       pakfire_string_format(headline, _("Changing %u packages:"), count);
+                               else
+                                       pakfire_string_set(headline, _("Changing one package:"));
+                               break;
+
+                       case SOLVER_TRANSACTION_ARCHCHANGE:
+                               if (count)
+                                       pakfire_string_format(headline,
+                                               _("%u architecture changes from '%s' to '%s':"), count, from, to);
+                               else
+                                       pakfire_string_format(headline,
+                                               _("One architecture change from '%s' to '%s':"), from, to);
+                               break;
+
+                       case SOLVER_TRANSACTION_VENDORCHANGE:
+                               if (count)
+                                       pakfire_string_format(headline,
+                                               _("%u vendor changes from '%s' to '%s':"), count, from, to);
+                               else
+                                       pakfire_string_format(headline,
+                                               _("One vendor change from '%s' to '%s':"), from, to);
+                               break;
+
+                       case SOLVER_TRANSACTION_IGNORE:
+                               continue;
+               }
 
-       // Summary
-       pakfire_transaction_add_headline(&string, width, _("Transaction Summary"));
-       pakfire_transaction_add_separator(&string, width);
+               // Show what we are doing
+               pakfire_transaction_add_headline(&lines, width, headline);
+
+               // Fetch packages in this class
+               transaction_classify_pkgs(transaction->transaction, mode, class,
+                       classes.elements[i+2], classes.elements[i+3], &pkgs);
 
-       pakfire_transaction_add_summary_line(&string, width, _("Installing:"), installing);
-       pakfire_transaction_add_summary_line(&string, width, _("Reinstalling:"), reinstalling);
-       pakfire_transaction_add_summary_line(&string, width, _("Updating:"), updating);
-       pakfire_transaction_add_summary_line(&string, width, _("Downgrading:"), downgrading);
-       pakfire_transaction_add_summary_line(&string, width, _("Removing:"), removing);
-       pakfire_transaction_add_summary_line(&string, width, _("Obsoleting:"), obsoleting);
+               // List all packages
+               for (int j = 0; j < pkgs.count; j++) {
+                       struct pakfire_package* old_pkg = pakfire_package_create_from_solvable(
+                               transaction->pakfire, pkgs.elements[j]);
+                       struct pakfire_package* new_pkg = NULL;
+
+                       switch (class) {
+                               case SOLVER_TRANSACTION_UPGRADED:
+                               case SOLVER_TRANSACTION_DOWNGRADED:
+                                       new_pkg = pakfire_package_create_from_solvable(transaction->pakfire,
+                                               transaction_obs_pkg(transaction->transaction, pkgs.elements[j]));
+
+                                       pakfire_transaction_add_package_change(&lines, width, old_pkg, new_pkg);
+                                       break;
+
+                               default:
+                                       pakfire_transaction_add_package(&lines, width, old_pkg);
+                                       break;
+                       }
+
+                       pakfire_package_unref(old_pkg);
+                       if (new_pkg)
+                               pakfire_package_unref(new_pkg);
+               }
+
+               // Newline
+               pakfire_transaction_add_newline(&lines, width);
+       }
+
+       queue_free(&classes);
+       queue_free(&pkgs);
+
+       // Summary
+       pakfire_transaction_add_headline(&lines, width, _("Transaction Summary"));
+       pakfire_transaction_add_separator(&lines, width);
 
        // How much do we need to download?
        size_t downloadsize = pakfire_transaction_downloadsize(transaction);
+
        if (downloadsize > 0)
-               pakfire_transaction_add_usage_line(&string, width,
+               pakfire_transaction_add_usage_line(&lines, width,
                        _("Total Download Size"), downloadsize);
 
        // How much more space do we need?
        ssize_t sizechange = pakfire_transaction_installsizechange(transaction);
-
-       pakfire_transaction_add_usage_line(&string, width,
+       pakfire_transaction_add_usage_line(&lines, width,
                (sizechange >= 0) ? _("Installed Size") : _("Freed Size"), sizechange);
 
-       // Remove trailing newline
-       size_t l = strlen(string) - 1;
+       // Join all lines together
+       char* string = pakfire_transaction_join_lines(lines);
 
-       if (l > 0 && string[l] == '\n')
-               string[l] = '\0';
+       DEBUG(transaction->pakfire, "Transaction: %s\n", string);
 
-       DEBUG("Transaction: %s\n", string);
+       // Free lines
+       if (lines) {
+               for (char** line = lines; *line; line++)
+                       free(*line);
+               free(lines);
+       }
 
        return string;
 }
 
-static int pakfire_transaction_run_steps(PakfireTransaction transaction, const pakfire_action_type_t action) {
+static void* pakfire_transaction_fileconflict_callback(
+               Pool* pool, Id p, void* data) {
+       return NULL;
+}
+
+static int pakfire_transaction_check_fileconflicts(
+               struct pakfire_transaction* transaction) {
+       Pool* pool = pakfire_get_solv_pool(transaction->pakfire);
+
+       const int flags =
+               FINDFILECONFLICTS_USE_SOLVABLEFILELIST |
+               FINDFILECONFLICTS_CHECK_DIRALIASING |
+               FINDFILECONFLICTS_USE_ROOTDIR;
+
+       Queue pkgs;
+       queue_init(&pkgs);
+
+       Queue conflicts;
+       queue_init(&conflicts);
+
+       // Fetch all installed packages
+       int newpkgs = transaction_installedresult(transaction->transaction, &pkgs);
+
+       // Perform check
+       int count = pool_findfileconflicts(pool, &pkgs, newpkgs, &conflicts, flags,
+               pakfire_transaction_fileconflict_callback, NULL);
+
+       DEBUG(transaction->pakfire, "Found %d file conflict(s)\n", count);
+
+       // Free everything
+       queue_free(&pkgs);
+       queue_free(&conflicts);
+
+       return (count > 0);
+}
+
+static int pakfire_transaction_check(struct pakfire_transaction* transaction) {
+       int r;
+
+       // Check for any file conflicts
+       r = pakfire_transaction_check_fileconflicts(transaction);
+       if (r)
+               return r;
+
+       return 0;
+}
+
+static int pakfire_transaction_verify(struct pakfire_transaction* transaction,
+               struct pakfire_package* pkg, struct pakfire_archive* archive) {
+       int r;
+
+       const char* nevra = pakfire_package_get_nevra(pkg);
+
+       // Nothing to do if this step does not have an archive
+       if (!archive) {
+               DEBUG(transaction->pakfire, "Package %s requires no archive\n", nevra);
+               return 0;
+       }
+
+       enum pakfire_digest_types digest_type = 0;
+
+       // Fetch digest from package
+       const unsigned char* expected_digest = pakfire_package_get_digest(pkg, &digest_type);
+       if (!expected_digest) {
+               DEBUG(transaction->pakfire, "Package %s has no digest\n", nevra);
+               return 0;
+       }
+
+       unsigned char computed_digest[EVP_MAX_MD_SIZE];
+       size_t digest_length = 0;
+
+       // Compute digest of the archive
+       r = pakfire_archive_digest(archive, digest_type, computed_digest, &digest_length);
+       if (r) {
+               ERROR(transaction->pakfire, "Could not compute digest for %s: %m\n", nevra);
+               return r;
+       }
+
+       // Compare digests
+       r = CRYPTO_memcmp(computed_digest, expected_digest, digest_length);
+       if (r) {
+               ERROR(transaction->pakfire, "Digests of %s do not match\n", nevra);
+               return 1;
+       }
+
+       return 0;
+}
+
+static int pakfire_transaction_run_script(struct pakfire_transaction* transaction,
+               struct pakfire_db* db, const char* type, struct pakfire_package* pkg, struct pakfire_archive* archive) {
+       struct pakfire_scriptlet* scriptlet = NULL;
+
+       // Fetch scriptlet from archive if possible
+       if (archive)
+               scriptlet = pakfire_archive_get_scriptlet(archive, type);
+       else
+               scriptlet = pakfire_db_get_scriptlet(db, pkg, type);
+
+       // Nothing to do if there are no scriptlets
+       if (!scriptlet)
+               return 0;
+
+       // Execute the scriptlet
+       pakfire_scriptlet_execute(scriptlet);
+
+       pakfire_scriptlet_unref(scriptlet);
+
+       return 0;
+}
+
+static int pakfire_transaction_extract(struct pakfire_transaction* transaction,
+               struct pakfire_package* pkg, struct pakfire_archive* archive) {
+       const char* nevra = pakfire_package_get_nevra(pkg);
+
+       // Update status
+       pakfire_transaction_status(transaction, _("Installing %s..."), nevra);
+
+       // Extract payload
+       int r = pakfire_archive_extract(archive);
+       if (r) {
+               ERROR(transaction->pakfire, "Could not extract package %s: %m\n",
+                       nevra);
+               return r;
+       }
+
+       // Is it necessary to call ldconfig?
+       struct pakfire_filelist* filelist = pakfire_archive_get_filelist(archive);
+       if (filelist) {
+               int need_ldconfig = pakfire_filelist_contains(filelist, "*/lib*.so.?");
+
+               // Update the runtime linker cache
+               if (need_ldconfig)
+                       pakfire_jail_ldconfig(transaction->pakfire);
+
+               pakfire_filelist_unref(filelist);
+       }
+
+       return r;
+}
+
+static int pakfire_transaction_erase(struct pakfire_transaction* transaction,
+               struct pakfire_db* db, struct pakfire_package* pkg) {
+       struct pakfire_filelist* filelist = NULL;
+       int r;
+
+       // Fetch filelist
+       r = pakfire_db_package_filelist(db, &filelist, pkg);
+       if (r)
+               goto ERROR;
+
+       const size_t length = pakfire_filelist_size(filelist);
+
+       // Delete all files
+       for (unsigned int i = 0; i < length; i++) {
+               struct pakfire_file* file = pakfire_filelist_get(filelist, i);
+
+               // Remove the file
+               r = pakfire_file_remove(file);
+               pakfire_file_unref(file);
+
+               // Break on any errors
+               if (r)
+                       goto ERROR;
+       }
+
+       // Update the runtime linker cache after all files have been removed
+       pakfire_jail_ldconfig(transaction->pakfire);
+
+ERROR:
+       if (filelist)
+               pakfire_filelist_unref(filelist);
+
+       return r;
+}
+
+static const char* pakfire_action_type_string(enum pakfire_actions type) {
+       switch (type) {
+               case PAKFIRE_ACTION_NOOP:
+                       return "NOOP";
+
+               case PAKFIRE_ACTION_VERIFY:
+                       return "VERIFY";
+
+               case PAKFIRE_ACTION_EXECUTE:
+                       return "EXECUTE";
+
+               case PAKFIRE_ACTION_PRETRANS:
+                       return "PRETRANS";
+
+               case PAKFIRE_ACTION_POSTTRANS:
+                       return "POSTTRANS";
+       }
+
+       return NULL;
+}
+
+static int pakfire_transaction_package_is_userinstalled(
+               struct pakfire_transaction* transaction, struct pakfire_package* pkg) {
+       // No packages on the list
+       if (!transaction->userinstalled)
+               return 0;
+
+       const char* name = pakfire_package_get_name(pkg);
+
+       // Check if the package is on the list
+       for (char** elem = transaction->userinstalled; *elem; elem++) {
+               if (strcmp(name, *elem) == 0)
+                       return 1;
+       }
+
+       // Not found
+       return 0;
+}
+
+static int pakfire_transaction_run_step(struct pakfire_transaction* transaction,
+               struct pakfire_db* db, const enum pakfire_actions action, struct pakfire_package* pkg, struct pakfire_archive* archive) {
+       if (!pkg) {
+               errno = EINVAL;
+               return 1;
+       }
+
+       DEBUG(transaction->pakfire, "Running %s for %s\n",
+               pakfire_action_type_string(action), pakfire_package_get_nevra(pkg));
+
+       enum pakfire_steps type = pakfire_transaction_get_step_type(transaction, pkg);
+
        int r = 0;
+       switch (action) {
+               // Verify this step
+               case PAKFIRE_ACTION_VERIFY:
+                       r = pakfire_transaction_verify(transaction, pkg, archive);
+                       break;
 
-       // Walk through all steps
-       PakfireStep* steps = transaction->steps;
-       while (*steps) {
-               PakfireStep step = *steps++;
+               // Run the pre-transaction scripts
+               case PAKFIRE_ACTION_PRETRANS:
+                       switch (type) {
+                               case PAKFIRE_STEP_INSTALL:
+                               case PAKFIRE_STEP_REINSTALL:
+                                       r = pakfire_transaction_run_script(transaction, db,
+                                               "pretransin", pkg, archive);
+                                       break;
+
+                               case PAKFIRE_STEP_UPGRADE:
+                               case PAKFIRE_STEP_DOWNGRADE:
+                                       r = pakfire_transaction_run_script(transaction, db,
+                                               "pretransup", pkg, archive);
+                                       break;
+
+                               case PAKFIRE_STEP_ERASE:
+                               case PAKFIRE_STEP_OBSOLETE:
+                                       r = pakfire_transaction_run_script(transaction, db,
+                                               "pretransun", pkg, archive);
+                                       break;
+
+                               case PAKFIRE_STEP_IGNORE:
+                                       break;
+                       }
+                       break;
+
+               // Run the post-transaction scripts
+               case PAKFIRE_ACTION_POSTTRANS:
+                       switch (type) {
+                               case PAKFIRE_STEP_INSTALL:
+                               case PAKFIRE_STEP_REINSTALL:
+                                       r = pakfire_transaction_run_script(transaction, db,
+                                               "posttransin", pkg, archive);
+                                       break;
+
+                               case PAKFIRE_STEP_UPGRADE:
+                               case PAKFIRE_STEP_DOWNGRADE:
+                                       r = pakfire_transaction_run_script(transaction, db,
+                                               "posttransup", pkg, archive);
+                                       break;
+
+                               case PAKFIRE_STEP_ERASE:
+                               case PAKFIRE_STEP_OBSOLETE:
+                                       r = pakfire_transaction_run_script(transaction, db,
+                                               "posttransun", pkg, archive);
+                                       break;
+
+                               case PAKFIRE_STEP_IGNORE:
+                                       break;
+                       }
+                       break;
+
+               // Execute the action of this script
+               case PAKFIRE_ACTION_EXECUTE:
+                       // Increment progress
+                       transaction->progress++;
+
+                       // Update progress callback
+                       pakfire_transaction_status(transaction, NULL);
+
+                       switch (type) {
+                               case PAKFIRE_STEP_INSTALL:
+                               case PAKFIRE_STEP_REINSTALL:
+                                       r = pakfire_transaction_run_script(transaction, db,
+                                               "prein", pkg, archive);
+                                       if (r)
+                                               break;
+
+                                       r = pakfire_transaction_extract(transaction, pkg, archive);
+                                       if (r)
+                                               break;
+
+                                       // Remove package metadata first when reinstalling
+                                       if (type == PAKFIRE_STEP_REINSTALL) {
+                                               r = pakfire_db_remove_package(db, pkg);
+                                               if (r)
+                                                       break;
+                                       }
+
+                                       r = pakfire_db_add_package(db, pkg, archive,
+                                                       pakfire_transaction_package_is_userinstalled(transaction, pkg));
+                                       if (r)
+                                               break;
+
+                                       r = pakfire_transaction_run_script(transaction, db,
+                                               "postin", pkg, archive);
+                                       break;
+
+                               case PAKFIRE_STEP_UPGRADE:
+                               case PAKFIRE_STEP_DOWNGRADE:
+                                       r = pakfire_transaction_run_script(transaction, db,
+                                               "preup", pkg, archive);
+                                       if (r)
+                                               break;
+
+                                       r = pakfire_transaction_extract(transaction, pkg, archive);
+                                       if (r)
+                                               break;
+
+                                       r = pakfire_db_add_package(db, pkg, archive,
+                                                       pakfire_transaction_package_is_userinstalled(transaction, pkg));
+                                       if (r)
+                                               break;
+
+                                       r = pakfire_transaction_run_script(transaction, db,
+                                               "postup", pkg, archive);
+                                       break;
+
+                               case PAKFIRE_STEP_ERASE:
+                               case PAKFIRE_STEP_OBSOLETE:
+                                       r = pakfire_transaction_run_script(transaction, db,
+                                               "preun", pkg, archive);
+                                       if (r)
+                                               break;
+
+                                       r = pakfire_transaction_erase(transaction, db, pkg);
+                                       if (r)
+                                               break;
+
+                                       r = pakfire_db_remove_package(db, pkg);
+                                       if (r)
+                                               break;
+
+                                       r = pakfire_transaction_run_script(transaction, db,
+                                               "postun", pkg, archive);
+                                       break;
+
+                               case PAKFIRE_STEP_IGNORE:
+                                       break;
+                       }
+                       break;
+
+               // Do nothing
+               case PAKFIRE_ACTION_NOOP:
+                       break;
+       }
+
+       if (r)
+               ERROR(transaction->pakfire, "Step has failed: %s\n", strerror(r));
 
-               // Verify the step
-               r = pakfire_step_run(step, action);
+       return r;
+}
+
+static int pakfire_transaction_run_steps(struct pakfire_transaction* transaction,
+               struct pakfire_db* db, enum pakfire_actions action) {
+       int r = 0;
+
+       // Update status
+       switch (action) {
+               case PAKFIRE_ACTION_VERIFY:
+                       pakfire_transaction_status(transaction, _("Verifying packages..."));
+                       break;
+
+               case PAKFIRE_ACTION_PRETRANS:
+                       pakfire_transaction_status(transaction, _("Preparing installation..."));
+                       break;
+
+               case PAKFIRE_ACTION_POSTTRANS:
+                       pakfire_transaction_status(transaction, _("Finishing up..."));
+                       break;
+
+               default:
+                       break;
+       }
+
+       // Walk through all steps
+       for (unsigned int i = 0; i < transaction->num; i++) {
+               r = pakfire_transaction_run_step(transaction, db, action,
+                       transaction->packages[i], transaction->archives[i]);
 
                // End loop if action was unsuccessful
-               if (r)
+               if (r) {
+                       DEBUG(transaction->pakfire, "Step %d failed: %m\n", i);
                        break;
+               }
        }
 
        return r;
 }
 
-PAKFIRE_EXPORT int pakfire_transaction_run(PakfireTransaction transaction) {
-       DEBUG("Running Transaction %p\n", transaction);
+static int pakfire_transaction_open_archives(struct pakfire_transaction* transaction) {
+       for (unsigned int i = 0; i < transaction->num; i++) {
+               struct pakfire_package* pkg = transaction->packages[i];
+
+               // Fetch the type
+               enum pakfire_steps type = pakfire_transaction_get_step_type(transaction, pkg);
+
+               // Do we need the archive?
+               switch (type) {
+                       case PAKFIRE_STEP_INSTALL:
+                       case PAKFIRE_STEP_REINSTALL:
+                       case PAKFIRE_STEP_UPGRADE:
+                       case PAKFIRE_STEP_DOWNGRADE:
+                       case PAKFIRE_STEP_OBSOLETE:
+                               break;
+
+                       case PAKFIRE_STEP_ERASE:
+                       case PAKFIRE_STEP_IGNORE:
+                               continue;
+               }
 
-       int r = 0;
+               transaction->archives[i] = pakfire_package_get_archive(pkg);
+               if (!transaction->archives[i]) {
+                       ERROR(transaction->pakfire, "Could not open archive for %s: %m\n",
+                               pakfire_package_get_nevra(pkg));
+                       return 1;
+               }
+       }
 
-       // Verify steps
-       r = pakfire_transaction_run_steps(transaction, PAKFIRE_ACTION_VERIFY);
+       return 0;
+}
+
+static int pakfire_transaction_perform(struct pakfire_transaction* transaction) {
+       struct pakfire_repo* repo = NULL;
+       struct pakfire_db* db;
+       int r;
+
+       DEBUG(transaction->pakfire, "Running Transaction %p\n", transaction);
+
+       // Open all archives
+       r = pakfire_transaction_open_archives(transaction);
        if (r)
                return r;
 
+       // Open the database
+       r = pakfire_db_open(&db, transaction->pakfire, PAKFIRE_DB_READWRITE);
+       if (r) {
+               ERROR(transaction->pakfire, "Could not open the database\n");
+               return r;
+       }
+
+       // Verify steps
+       r = pakfire_transaction_run_steps(transaction, db, PAKFIRE_ACTION_VERIFY);
+       if (r)
+               goto ERROR;
+
        // Execute all pre transaction actions
-       r = pakfire_transaction_run_steps(transaction, PAKFIRE_ACTION_PRETRANS);
+       r = pakfire_transaction_run_steps(transaction, db, PAKFIRE_ACTION_PRETRANS);
        if (r)
-               return r;
+               goto ERROR;
 
-       r = pakfire_transaction_run_steps(transaction, PAKFIRE_ACTION_EXECUTE);
+       r = pakfire_transaction_run_steps(transaction, db, PAKFIRE_ACTION_EXECUTE);
        if (r)
-               return r;
+               goto ERROR;
 
        // Execute all post transaction actions
-       r = pakfire_transaction_run_steps(transaction, PAKFIRE_ACTION_POSTTRANS);
+       r = pakfire_transaction_run_steps(transaction, db, PAKFIRE_ACTION_POSTTRANS);
        if (r)
-               return r;
+               goto ERROR;
 
-       DEBUG("Transaction %p has finished successfully\n", transaction);
+       DEBUG(transaction->pakfire, "The transaction has finished successfully\n");
 
-       return 0;
+       // Reload database for next transaction
+
+       repo = pakfire_get_installed_repo(transaction->pakfire);
+       if (!repo)
+               goto ERROR;
+
+       // Reload the database
+       r = pakfire_db_load(db, repo);
+
+ERROR:
+       if (repo)
+               pakfire_repo_unref(repo);
+       pakfire_db_unref(db);
+
+       return r;
+}
+
+static int pakfire_transaction_download_package(struct pakfire_transaction* transaction,
+               struct pakfire_downloader* downloader, struct pakfire_package* pkg) {
+       int r = 1;
+       struct pakfire_repo* repo = NULL;
+       struct pakfire_mirrorlist* mirrorlist = NULL;
+
+       // Fetch the repository to download from
+       repo = pakfire_package_get_repo(pkg);
+       if (!repo)
+               goto ERROR;
+
+       // Fetch baseurl
+       const char* baseurl = pakfire_repo_get_baseurl(repo);
+
+       // Fetch mirrorlist
+       mirrorlist = pakfire_repo_get_mirrorlist(repo);
+
+       const char* nevra = pakfire_package_get_nevra(pkg);
+       if (!nevra)
+               goto ERROR;
+
+       // Where to store the package?
+       const char* path = pakfire_package_get_path(pkg);
+       if (!path) {
+               ERROR(transaction->pakfire, "Could not retrieve package path for %s: %m\n", nevra);
+               goto ERROR;
+       }
+
+       // What file to download?
+       const char* filename = pakfire_package_get_filename(pkg);
+       if (!filename) {
+               ERROR(transaction->pakfire, "Could not retrieve filename for package %s: %m\n", nevra);
+               goto ERROR;
+       }
+
+       enum pakfire_digest_types digest_type = 0;
+
+       // Retrieve package digest
+       const unsigned char* digest = pakfire_package_get_digest(pkg, &digest_type);
+       const size_t digest_length = pakfire_digest_length(digest_type);
+
+       // Add transfer to downloader
+       r = pakfire_downloader_add_transfer(downloader, baseurl, mirrorlist,
+               nevra, filename, path, digest_type, digest, digest_length, 0);
+
+ERROR:
+       if (mirrorlist)
+               pakfire_mirrorlist_unref(mirrorlist);
+       if (repo)
+               pakfire_repo_unref(repo);
+
+       return r;
+}
+
+static int pakfire_transaction_package_needs_download(
+               struct pakfire_transaction* transaction, struct pakfire_package* pkg) {
+       enum pakfire_steps type = pakfire_transaction_get_step_type(transaction, pkg);
+       switch (type) {
+               case PAKFIRE_STEP_INSTALL:
+               case PAKFIRE_STEP_REINSTALL:
+               case PAKFIRE_STEP_DOWNGRADE:
+               case PAKFIRE_STEP_UPGRADE:
+                       break;
+
+               // No need to download for these steps
+               default:
+                       return 0;
+       }
+
+       // No download required if this package is already installed
+       if (pakfire_package_is_installed(pkg))
+               return 0;
+
+       const char* path = pakfire_package_get_path(pkg);
+
+       // Does the file exist?
+       int r = access(path, R_OK);
+       if (r == 0)
+               return 0;
+
+       // This package needs to be downloaded
+       return 1;
+}
+
+PAKFIRE_EXPORT int pakfire_transaction_download(struct pakfire_transaction* transaction) {
+       struct pakfire_downloader* downloader;
+       int r;
+
+       // Initialize the downloader
+       r = pakfire_downloader_create(&downloader, transaction->pakfire);
+       if (r) {
+               ERROR(transaction->pakfire, "Could not initialize downloader: %m\n");
+               return 1;
+       }
+
+       // Add all packages that need to be downloaded
+       for (unsigned int i = 0; i < transaction->num; i++) {
+               struct pakfire_package* pkg = transaction->packages[i];
+
+               if (!pakfire_transaction_package_needs_download(transaction, pkg))
+                       continue;
+
+               // Enqueue download
+               r = pakfire_transaction_download_package(transaction, downloader, pkg);
+               if (r) {
+                       const char* nevra = pakfire_package_get_nevra(pkg);
+
+                       ERROR(transaction->pakfire, "Could not add download to queue: %s: %m\n", nevra);
+                       goto ERROR;
+               }
+       }
+
+       // Run the downloader
+       r = pakfire_downloader_run(downloader);
+
+ERROR:
+       pakfire_downloader_unref(downloader);
+
+       return r;
+}
+
+PAKFIRE_EXPORT int pakfire_transaction_run(
+               struct pakfire_transaction* transaction, int flags) {
+       int r;
+
+       // Skip running an empty transaction
+       if (!transaction->num) {
+               DEBUG(transaction->pakfire, "Empty transaction. Skipping...\n");
+               return 0;
+       }
+
+       // Show what would be done
+       char* dump = pakfire_transaction_dump(transaction, 80);
+
+       // Check if we should continue
+       r = pakfire_confirm(transaction->pakfire, dump, _("Is this okay? [y/N]"));
+       if (r) {
+               ERROR(transaction->pakfire, "Transaction aborted upon user request\n");
+               goto ERROR;
+       }
+
+       // Write transaction dump to log
+       INFO(transaction->pakfire, "%s\n", dump);
+
+       // Perform a check if this can actually be run
+       r = pakfire_transaction_check(transaction);
+       if (r)
+               goto ERROR;
+
+       // End here for a dry run
+       if (flags & PAKFIRE_TRANSACTION_DRY_RUN)
+               goto ERROR;
+
+       // Download what we need
+       r = pakfire_transaction_download(transaction);
+       if (r)
+               goto ERROR;
+
+       // Perform all steps
+       r = pakfire_transaction_perform(transaction);
+       if (r)
+               goto ERROR;
+
+ERROR:
+       // Cleanup
+       free(dump);
+
+       return r;
 }