From: Michael Tremer Date: Mon, 21 Feb 2011 01:10:23 +0000 (+0100) Subject: Some rework on transactions and initial downloader code. X-Git-Tag: 0.9.3~151 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1de8761d362f8860efe6f0a036b97c7b2c8ad7c9;p=pakfire.git Some rework on transactions and initial downloader code. Move the install/update/remove decision into TransactionSet and makes Transaction more straight-forward. --- diff --git a/pakfire/__init__.py b/pakfire/__init__.py index f8378dcd9..a64f0e6f2 100644 --- a/pakfire/__init__.py +++ b/pakfire/__init__.py @@ -167,14 +167,13 @@ class Pakfire(object): ds.add_requires(req) ds.resolve() - - ts = transaction.TransactionSet(self, ds) - ts.dump() + ds.dump() ret = cli.ask_user(_("Is this okay?")) if not ret: return + ts = transaction.Transaction(self, ds) ts.run() def provides(self, patterns): diff --git a/pakfire/builder.py b/pakfire/builder.py index 8895c874d..712264508 100644 --- a/pakfire/builder.py +++ b/pakfire/builder.py @@ -144,14 +144,14 @@ class Builder(object): ds.add_requires("icecream") ds.resolve() + ds.dump() # Get build dependencies from source package. if isinstance(self.pkg, packages.SourcePackage): for req in self.pkg.requires: ds.add_requires(req) - ts = transaction.TransactionSet(self.pakfire, ds) - ts.dump() + ts = transaction.Transaction(self.pakfire, ds) ts.run() # Copy the makefile and load source tarballs. @@ -169,9 +169,9 @@ class Builder(object): for r in requires: ds.add_requires(r) ds.resolve() + ds.dump() - ts = transaction.TransactionSet(self.pakfire, ds) - ts.dump() + ts = transaction.Transaction(self.pakfire, ds) ts.run() @property diff --git a/pakfire/constants.py b/pakfire/constants.py index 9963c950e..6dfb7cfe8 100644 --- a/pakfire/constants.py +++ b/pakfire/constants.py @@ -11,6 +11,7 @@ CONFIG_FILE = os.path.join(SYSCONFDIR, "pakfire.conf") CACHE_DIR = "/var/cache/pakfire" CCACHE_CACHE_DIR = os.path.join(CACHE_DIR, "ccache") +REPO_CACHE_DIR = os.path.join(CACHE_DIR, "repos") LOCAL_BUILD_REPO_PATH = "/var/lib/pakfire/local" @@ -33,6 +34,8 @@ BUILD_ROOT = "/var/lib/pakfire/build" SOURCE_DOWNLOAD_URL = "http://source.ipfire.org/source-3.x/" SOURCE_CACHE_DIR = os.path.join(CACHE_DIR, "sources") +TIME_24H = 60*60*24 + SOURCE_PACKAGE_META = """\ PKG_NAME="%(PKG_NAME)s" diff --git a/pakfire/depsolve.py b/pakfire/depsolve.py index ae7393447..133b4c8e1 100644 --- a/pakfire/depsolve.py +++ b/pakfire/depsolve.py @@ -5,13 +5,20 @@ import re import packages import repository +import transaction +import util from errors import * +from i18n import _ + +PKG_DUMP_FORMAT = " %-21s %-8s %-21s %-19s %5s " + class Requires(object): - def __init__(self, pkg, requires): + def __init__(self, pkg, requires, dep=False): self.pkg = pkg self.requires = requires + self.dep = dep def __repr__(self): return "<%s %s>" % (self.__class__.__name__, self.requires) @@ -73,18 +80,21 @@ class DependencySet(object): self.__requires = [] self.__obsoletes = [] + # Create a new transaction set. + self.ts = transaction.TransactionSet() + # Read-in all packages from the database that have # been installed previously and need to be taken into # account when resolving dependencies. for pkg in self.repos.local.packages: - self.add_package(pkg) + self.add_package(pkg, transaction=False) - def add_requires(self, requires, pkg=None): + def add_requires(self, requires, pkg=None, dep=False): # XXX for now, we skip the virtual perl requires if requires.startswith("perl(") or requires.startswith("perl>") or requires.startswith("perl="): return - requires = Requires(pkg, requires) + requires = Requires(pkg, requires, dep) if requires in self.__requires: return @@ -102,20 +112,31 @@ class DependencySet(object): self.__obsoletes.append(obsoletes) - def add_package(self, pkg): + def add_package(self, pkg, dep=False, transaction=True): #print pkg, sorted(self.__packages) #assert not pkg in self.__packages if pkg in self.__packages: logging.debug("Trying to add package which is already in the dependency set: %s" % pkg) return - if not isinstance(pkg, packages.DatabasePackage): - logging.info(" --> Adding package to dependency set: %s" % pkg.friendly_name) + if transaction: + transaction_mode = "install" + for p in self.__packages: + if pkg.name == p.name: + transaction_mode = "update" + break + + # Add package to transaction set + func = getattr(self.ts, transaction_mode) + func(pkg, dep=dep) + + #if not isinstance(pkg, packages.DatabasePackage): + # logging.info(" --> Adding package to dependency set: %s" % pkg.friendly_name) self.__packages.append(pkg) # Add the requirements of the newly added package. for req in pkg.requires: - self.add_requires(req, pkg) + self.add_requires(req, pkg, dep=True) # Remove all requires that are fulfilled by this package. # For that we copy the matching requires to _requires and remove them @@ -156,9 +177,68 @@ class DependencySet(object): best = candidates.get_most_recent() if best: - self.add_package(best) + self.add_package(best, dep=requires.dep) if unresolveable_reqs: raise DependencyError, "Cannot resolve %s" % \ " ".join([r.requires for r in unresolveable_reqs]) + def dump_pkg(self, format, pkg): + return format % ( + pkg.name, + pkg.arch, + pkg.friendly_version, + pkg.repo.name, + util.format_size(pkg.size), + ) + + def dump_pkgs(self, caption, pkgs): + if not pkgs: + return [] + + s = [caption,] + for pkg in sorted(pkgs): + s.append(self.dump_pkg(PKG_DUMP_FORMAT, pkg)) + s.append("") + return s + + def dump(self): + width = 80 + line = "=" * width + + s = [] + s.append(line) + s.append(PKG_DUMP_FORMAT % (_("Package"), _("Arch"), _("Version"), _("Repository"), _("Size"))) + s.append(line) + + s += self.dump_pkgs(_("Installing:"), self.ts.installs) + s += self.dump_pkgs(_("Installing for dependencies:"), self.ts.install_deps) + s += self.dump_pkgs(_("Updating:"), self.ts.updates) + s += self.dump_pkgs(_("Updating for dependencies:"), self.ts.update_deps) + s += self.dump_pkgs(_("Removing:"), self.ts.removes) + s += self.dump_pkgs(_("Removing for dependencies:"), self.ts.remove_deps) + + s.append(_("Transaction Summary")) + s.append(line) + + format = "%-20s %-4d %s" + + if self.ts.installs or self.ts.install_deps: + s.append(format % (_("Install"), + len(self.ts.installs + self.ts.install_deps), _("Package(s)"))) + + if self.ts.updates or self.ts.update_deps: + s.append(format % (_("Updates"), + len(self.ts.updates + self.ts.update_deps), _("Package(s)"))) + + if self.ts.removes or self.ts.remove_deps: + s.append(format % (_("Remove"), + len(self.ts.removes + self.ts.remove_deps), _("Package(s)"))) + + download_size = sum([p.size for p in self.ts.installs + \ + self.ts.install_deps + self.ts.updates + self.ts.update_deps]) + s.append(_("Total download size: %s") % util.format_size(download_size)) + s.append("") + + for line in s: + logging.info(line) diff --git a/pakfire/downloader.py b/pakfire/downloader.py new file mode 100644 index 000000000..486b90320 --- /dev/null +++ b/pakfire/downloader.py @@ -0,0 +1,120 @@ +#!/usr/bin/python + +import json +import logging + +from urlgrabber.grabber import URLGrabber, URLGrabError +from urlgrabber.mirror import MGRandomOrder +from urlgrabber.progress import TextMultiFileMeter + +from constants import * + +MIRRORLIST_MAXSIZE = 1024**2 + +class PakfireGrabber(URLGrabber): + """ + Class to make some modifications on the urlgrabber configuration. + """ + pass + + +class Mirror(object): + def __init__(self, mirror, location=None, preferred=False): + # Save URL of the mirror in full format + self.mirror = mirror + + # Save the location (if given) + self.location = location + + # Save preference + self.preferred = False + + +class MirrorList(object): + def __init__(self, pakfire, repo): + self.pakfire = pakfire + self.repo = repo + + self.__mirrors = [] + + # Save URL to more mirrors. + self.mirrorlist = repo.mirrorlist + + self.update(force=False) + + @property + def cache(self): + """ + Shortcut to cache from repository. + """ + return self.repo.cache + + def update(self, force=False): + # XXX should this be allowed? + if not self.mirrorlist: + return + + logging.debug("Updating mirrorlist for repository '%s' (force=%s)" % (self.repo.name, force)) + + cache_filename = "mirrors/mirrorlist" + + # Force the update if no mirrorlist is available. + if not self.cache.exists(cache_filename): + force = True + + if not force and self.cache.exists(cache_filename): + age = self.cache.age(cache_filename) + + # If the age could be determined and is higher than 24h, + # we force an update. + if age and age > TIME_24H: + force = True + + if force: + g = PakfireGrabber() + + try: + mirrordata = g.urlread(self.mirrorlist, limit=MIRRORLIST_MAXSIZE) + except URLGrabError, e: + logging.warning("Could not update the mirrorlist for repo '%s': %s" % (self.repo.name, e)) + return + + # XXX check for empty files or damaged output + + # Save new mirror data to cache. + f = self.cache.open(cache_filename, "w") + f.write(mirrordata) + f.close() + + # Read mirrorlist from cache and parse it. + with self.cache.open(cache_filename) as f: + self.parse_mirrordata(f.read()) + + def parse_mirrordata(self, data): + data = json.loads(data) + + for mirror in data["mirrors"]: + self.add_mirror(**mirror) + + def add_mirror(self, *args, **kwargs): + mirror = Mirror(*args, **kwargs) + + self.__mirrors.append(mirror) + + @property + def preferred(self): + """ + Return a generator for all mirrors that are preferred. + """ + for mirror in self.__mirrors: + if mirror.preferred: + yield mirror + + @property + def all(self): + """ + Return a generator for all mirrors. + """ + for mirror in self.__mirrors: + yield mirror + diff --git a/pakfire/packages/listing.py b/pakfire/packages/listing.py index fbc14b5ba..bdaaeee0b 100644 --- a/pakfire/packages/listing.py +++ b/pakfire/packages/listing.py @@ -22,6 +22,11 @@ class PackageListing(object): def __len__(self): return len(self.__packages) + def get_by_name(self, name): + for pkg in self.__packages: + if pkg.name == name: + yield pkg + def get_most_recent(self): if self.__packages: return self.__packages[-1] diff --git a/pakfire/repository.py b/pakfire/repository.py index fea2a6801..599c3cb27 100644 --- a/pakfire/repository.py +++ b/pakfire/repository.py @@ -3,15 +3,14 @@ import fnmatch import logging import os +import stat +import time from ConfigParser import ConfigParser -from urlgrabber.grabber import URLGrabber -from urlgrabber.mirror import MGRandomOrder -from urlgrabber.progress import TextMultiFileMeter - import base import database +import downloader import index import packages @@ -62,6 +61,7 @@ class Repositories(object): "name" : name, "enabled" : True, "gpgkey" : None, + "mirrorlist" : None, } _args.update(args) @@ -344,17 +344,77 @@ class LocalBuildRepository(LocalRepository): return 20000 +class RepositoryCache(object): + """ + An object that is able to cache all data that is loaded from a + remote repository. + """ + + def __init__(self, pakfire, repo): + self.pakfire = pakfire + self.repo = repo + + self.create() + + @property + def path(self): + return os.path.join(REPO_CACHE_DIR, self.repo.name, self.repo.arch) + + def create(self): + """ + Create all necessary directories. + """ + for d in ("mirrors", "packages", "metadata"): + path = os.path.join(self.path, d) + + if not os.path.exists(path): + os.makedirs(path) + + def exists(self, filename): + """ + Returns True if a file exists and False if it doesn't. + """ + return os.path.exists(os.path.join(self.path, filename)) + + def age(self, filename): + """ + Returns the age of a downloaded file in minutes. + i.e. the time from download until now. + """ + if not self.exists(filename): + return None + + filename = os.path.join(self.path, filename) + + # Creation time of the file + ctime = os.stat(filename)[stat.ST_CTIME] + + return (time.time() - ctime) / 60 + + def open(self, filename, *args, **kwargs): + filename = os.path.join(self.path, filename) + + return open(filename, *args, **kwargs) + + class RemoteRepository(RepositoryFactory): - def __init__(self, pakfire, name, description, url, gpgkey, enabled): + def __init__(self, pakfire, name, description, url, mirrorlist, gpgkey, enabled): RepositoryFactory.__init__(self, pakfire, name, description) self.url, self.gpgkey = url, gpgkey + self.mirrorlist = mirrorlist if enabled in (True, 1, "1", "yes", "y"): self.enabled = True else: self.enabled = False + # Create a cache for the repository where we can keep all temporary data. + self.cache = RepositoryCache(self.pakfire, self) + + # Initialize mirror servers. + self.mirrors = downloader.MirrorList(self.pakfire, self) + if self.local: self.index = index.DirectoryIndex(self.pakfire, self, self.url) else: @@ -376,12 +436,16 @@ class RemoteRepository(RepositoryFactory): # Otherwise not. return False + @property + def arch(self): + return self.pakfire.distro.arch + @property def path(self): if self.local: return self.url[7:] - raise Exception, "XXX find some cache dir" + return self.cache.path @property def priority(self): @@ -399,13 +463,6 @@ class RemoteRepository(RepositoryFactory): return priority - @property - def mirrorlist(self): - # XXX - return [ - "http://mirror0.ipfire.org/", - ] - def fetch_file(self, filename): grabber = URLGrabber( progress_obj = TextMultiFileMeter(), diff --git a/pakfire/transaction.py b/pakfire/transaction.py index a652f22eb..932119ea6 100644 --- a/pakfire/transaction.py +++ b/pakfire/transaction.py @@ -3,6 +3,7 @@ import logging import depsolve +import packages import util from i18n import _ @@ -90,24 +91,54 @@ class ActionRemove(Action): class TransactionSet(object): - def __init__(self, pakfire, ds): - self.pakfire = pakfire - self.ds = ds + def __init__(self): + self.installs = [] + self.install_deps = [] - self._actions = [] + self.updates = [] + self.update_deps = [] - self._installs = [] - self._removes = [] - self._updates = [] + self.removes = [] + self.remove_deps = [] - # Reference to local repository - self.local = pakfire.repos.local + def install(self, pkg, dep=False): + logging.info(" --> Marking package for install: %s" % pkg.friendly_name) - self._packages = self.local.get_all() + if dep: + self.install_deps.append(pkg) + else: + self.installs.append(pkg) - self.populate() + def remove(self, pkg, dep=False): + logging.info(" --> Marking package for remove: %s" % pkg.friendly_name) + + if dep: + self.remove_deps.append(pkg) + else: + self.removes.append(pkg) + + def update(self, pkg, dep=False): + logging.info(" --> Marking package for update: %s" % pkg.friendly_name) + + if dep: + self.update_deps.append(pkg) + else: + self.updates.append(pkg) + + def download(self): + pass + + +class Transaction(object): + def __init__(self, pakfire, ds): + self.pakfire = pakfire + self.ds = ds + + self._actions = [] def _install_pkg(self, pkg): + assert isinstance(pkg, packages.BinaryPackage) + # XXX add dependencies for running the script here action_prein = ActionScriptPreIn(self.pakfire, pkg) @@ -119,41 +150,32 @@ class TransactionSet(object): for action in (action_prein, action_extract, action_postin): self.add_action(action) - self._installs.append(pkg) - def _update_pkg(self, pkg): + assert isinstance(pkg, packages.BinaryPackage) + action_extract = ActionExtract(self.pakfire, pkg) self.add_action(action_extract) - self._updates.append(pkg) def _remove_pkg(self, pkg): # XXX TBD - self._removes.append(pkg) + pass def populate(self): - # XXX need to check later, if this really works - # Determine which packages we have to add # and which we have to remove. - for pkg in self.ds.packages: - pkgs = self.local.get_by_name(pkg.name) - pkgs = [p for p in pkgs] - if not pkgs: - # Got a new package to install - self._install_pkg(pkg) + # Add all packages that need to be installed. + for pkg in self.ds.ts.installs + self.ds.ts.install_deps: + self._install_pkg(pkg) - else: - # Check for updates - for _pkg in pkgs: - if pkg > _pkg: - self._update_pkg(pkg) - break + # Add all packages that need to be updated. + for pkg in self.ds.ts.updates + self.ds.ts.update_deps: + self._update_pkg(pkg) - for pkg in self._packages: - if not pkg in self.ds.packages: - self._remove_pkg(pkg) + # Add all packages that need to be removed. + for pkg in self.ds.ts.removes + self.ds.ts.remove_deps: + self._remove_pkg(pkg) def add_action(self, action): logging.debug("New action added: %s" % action) @@ -167,18 +189,6 @@ class TransactionSet(object): for _action in self.actions: _action.remove_dep(action) - @property - def installs(self): - return sorted(self._installs) - - @property - def updates(self): - return sorted(self._updates) - - @property - def removes(self): - return sorted(self._removes) - @property def actions(self): for action in self._actions: @@ -196,6 +206,12 @@ class TransactionSet(object): logging.error("Action finished with an error: %s - %s" % (action, e)) def run(self): + # Download all packages. + self.ds.ts.download() + + # Create all the actions that need to be done. + self.populate() + while True: if not [a for a in self.actions]: break @@ -208,60 +224,3 @@ class TransactionSet(object): self.run_action(action) self.remove_action(action) - def dump_pkg(self, format, pkg): - return format % ( - pkg.name, - pkg.arch, - pkg.friendly_version, - pkg.repo.name, - util.format_size(pkg.size), - ) - - def dump(self): - width = 80 - line = "=" * width - format = " %-21s %-8s %-21s %-19s %5s " - - s = [] - s.append(line) - s.append(format % (_("Package"), _("Arch"), _("Version"), _("Repository"), _("Size"))) - s.append(line) - - if self.installs: - s.append(_("Installing:")) - for pkg in self.installs: - s.append(self.dump_pkg(format, pkg)) - s.append("") - - if self.updates: - s.append(_("Updating:")) - for pkg in self.updates: - s.append(self.dump_pkg(format, pkg)) - s.append("") - - if self.removes: - s.append(_("Removing:")) - for pkg in self.removes: - s.append(self.dump_pkg(format, pkg)) - s.append("") - - s.append(_("Transaction Summary")) - s.append(line) - - format = "%-20s %-4d %s" - - if self.installs: - s.append(format % (_("Install"), len(self.installs), _("Package(s)"))) - - if self.updates: - s.append(format % (_("Updates"), len(self.updates), _("Package(s)"))) - - if self.removes: - s.append(format % (_("Remove"), len(self.removes), _("Package(s)"))) - - download_size = sum([p.size for p in self.installs + self.updates]) - s.append(_("Total download size: %s") % util.format_size(download_size)) - s.append("") - - print "\n".join(s) - diff --git a/po/POTFILES.in b/po/POTFILES.in index c554e6be5..a74b5f526 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -6,6 +6,7 @@ pakfire/constants.py pakfire/database.py pakfire/depsolve.py pakfire/distro.py +pakfire/downloader.py pakfire/errors.py pakfire/i18n.py pakfire/index.py diff --git a/po/pakfire.pot b/po/pakfire.pot index 48e6a9250..5c286ab14 100644 --- a/po/pakfire.pot +++ b/po/pakfire.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-02-20 11:01+0100\n" +"POT-Creation-Date: 2011-02-21 02:10+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -150,112 +150,124 @@ msgstr "" msgid "Path to input packages." msgstr "" -#: ../pakfire/__init__.py:174 -msgid "Is this okay?" -msgstr "" - -#: ../pakfire/packages/base.py:51 -msgid "Name" +#: ../pakfire/depsolve.py:211 +msgid "Package" msgstr "" -#: ../pakfire/packages/base.py:52 ../pakfire/transaction.py:227 +#: ../pakfire/depsolve.py:211 ../pakfire/packages/base.py:51 msgid "Arch" msgstr "" -#: ../pakfire/packages/base.py:53 ../pakfire/transaction.py:227 +#: ../pakfire/depsolve.py:211 ../pakfire/packages/base.py:52 msgid "Version" msgstr "" -#: ../pakfire/packages/base.py:54 -msgid "Release" +#: ../pakfire/depsolve.py:211 +msgid "Repository" msgstr "" -#: ../pakfire/packages/base.py:55 ../pakfire/transaction.py:227 +#: ../pakfire/depsolve.py:211 ../pakfire/packages/base.py:54 msgid "Size" msgstr "" -#: ../pakfire/packages/base.py:56 -msgid "Repo" +#: ../pakfire/depsolve.py:214 +msgid "Installing:" msgstr "" -#: ../pakfire/packages/base.py:57 -msgid "Summary" +#: ../pakfire/depsolve.py:215 +msgid "Installing for dependencies:" msgstr "" -#: ../pakfire/packages/base.py:58 -msgid "URL" +#: ../pakfire/depsolve.py:216 +msgid "Updating:" msgstr "" -#: ../pakfire/packages/base.py:59 -msgid "License" +#: ../pakfire/depsolve.py:217 +msgid "Updating for dependencies:" msgstr "" -#: ../pakfire/packages/base.py:62 -msgid "Description" +#: ../pakfire/depsolve.py:218 +msgid "Removing:" msgstr "" -#: ../pakfire/packages/base.py:68 -msgid "Build ID" +#: ../pakfire/depsolve.py:219 +msgid "Removing for dependencies:" msgstr "" -#: ../pakfire/packages/base.py:69 -msgid "Build date" +#: ../pakfire/depsolve.py:221 +msgid "Transaction Summary" msgstr "" -#: ../pakfire/packages/base.py:70 -msgid "Build host" +#: ../pakfire/depsolve.py:227 +msgid "Install" msgstr "" -#: ../pakfire/packages/packager.py:70 -msgid "Extracting" +#: ../pakfire/depsolve.py:228 ../pakfire/depsolve.py:232 +#: ../pakfire/depsolve.py:236 +msgid "Package(s)" msgstr "" -#: ../pakfire/packages/packager.py:125 -msgid "Extracting:" +#: ../pakfire/depsolve.py:231 +msgid "Updates" msgstr "" -#: ../pakfire/transaction.py:227 -msgid "Package" +#: ../pakfire/depsolve.py:235 +msgid "Remove" msgstr "" -#: ../pakfire/transaction.py:227 -msgid "Repository" +#: ../pakfire/depsolve.py:240 +#, python-format +msgid "Total download size: %s" msgstr "" -#: ../pakfire/transaction.py:231 -msgid "Installing:" +#: ../pakfire/__init__.py:172 +msgid "Is this okay?" msgstr "" -#: ../pakfire/transaction.py:237 -msgid "Updating:" +#: ../pakfire/packages/base.py:50 +msgid "Name" msgstr "" -#: ../pakfire/transaction.py:243 -msgid "Removing:" +#: ../pakfire/packages/base.py:53 +msgid "Release" msgstr "" -#: ../pakfire/transaction.py:248 -msgid "Transaction Summary" +#: ../pakfire/packages/base.py:55 +msgid "Repo" msgstr "" -#: ../pakfire/transaction.py:254 -msgid "Install" +#: ../pakfire/packages/base.py:56 +msgid "Summary" msgstr "" -#: ../pakfire/transaction.py:254 ../pakfire/transaction.py:257 -#: ../pakfire/transaction.py:260 -msgid "Package(s)" +#: ../pakfire/packages/base.py:57 +msgid "URL" msgstr "" -#: ../pakfire/transaction.py:257 -msgid "Updates" +#: ../pakfire/packages/base.py:58 +msgid "License" msgstr "" -#: ../pakfire/transaction.py:260 -msgid "Remove" +#: ../pakfire/packages/base.py:61 +msgid "Description" msgstr "" -#: ../pakfire/transaction.py:263 -#, python-format -msgid "Total download size: %s" +#: ../pakfire/packages/base.py:67 +msgid "Build ID" +msgstr "" + +#: ../pakfire/packages/base.py:68 +msgid "Build date" +msgstr "" + +#: ../pakfire/packages/base.py:69 +msgid "Build host" +msgstr "" + +#: ../pakfire/packages/packager.py:70 +msgid "Extracting" +msgstr "" + +#: ../pakfire/packages/packager.py:125 +msgid "Extracting:" msgstr ""