diff options
Diffstat (limited to 'apt/progress')
| -rw-r--r-- | apt/progress/__init__.py | 420 | ||||
| -rw-r--r-- | apt/progress/base.py | 302 | ||||
| -rw-r--r-- | apt/progress/gtk2.py | 239 | ||||
| -rw-r--r-- | apt/progress/old.py | 205 | ||||
| -rw-r--r-- | apt/progress/text.py | 261 |
5 files changed, 951 insertions, 476 deletions
diff --git a/apt/progress/__init__.py b/apt/progress/__init__.py index 8694de77..10c11021 100644 --- a/apt/progress/__init__.py +++ b/apt/progress/__init__.py @@ -1,405 +1,35 @@ -# progress.py - progress reporting classes +# apt/progress/__init__.py - Initialization file for apt.progress. # -# Copyright (c) 2005-2009 Canonical +# Copyright (c) 2009 Julian Andres Klode <jak@debian.org> # -# Author: Michael Vogt <michael.vogt@ubuntu.com> +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. # -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 2 of the -# License, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA -"""progress reporting classes. - -This module provides classes for progress reporting. They can be used with -e.g., for reporting progress on the cache opening process, the cache update -progress, or the package install progress. +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA +"""Progress reporting. + +This package provides progress reporting for the python-apt package. The module +'base' provides classes with no output, the module 'gtk2' provides classes for +GTK+ applications, and the module 'text' provides classes for terminals, etc. """ - -import errno -import fcntl -import os -import re -import select -import sys - import apt_pkg -__all__ = ('CdromProgress', 'DpkgInstallProgress', 'DumbInstallProgress', - 'FetchProgress', 'InstallProgress', 'OpProgress', 'OpTextProgress', - 'TextFetchProgress') - - -class OpProgress(object): - """Abstract class to implement reporting on cache opening. - - Subclass this class to implement simple Operation progress reporting. - """ - - def __init__(self): - self.op = None - self.subOp = None - - def update(self, percent): - """Called periodically to update the user interface.""" - - def done(self): - """Called once an operation has been completed.""" - - -class OpTextProgress(OpProgress): - """A simple text based cache open reporting class.""" - - def __init__(self): - OpProgress.__init__(self) - - def update(self, percent): - """Called periodically to update the user interface.""" - sys.stdout.write("\r%s: %.2i " % (self.subOp, percent)) - sys.stdout.flush() - - def done(self): - """Called once an operation has been completed.""" - sys.stdout.write("\r%s: Done\n" % self.op) - - -class FetchProgress(object): - """Report the download/fetching progress. - - Subclass this class to implement fetch progress reporting - """ - - # download status constants - dlDone = 0 - dlQueued = 1 - dlFailed = 2 - dlHit = 3 - dlIgnored = 4 - dlStatusStr = {dlDone: "Done", - dlQueued: "Queued", - dlFailed: "Failed", - dlHit: "Hit", - dlIgnored: "Ignored"} - - def __init__(self): - self.eta = 0.0 - self.percent = 0.0 - # Make checking easier - self.currentBytes = 0 - self.currentItems = 0 - self.totalBytes = 0 - self.totalItems = 0 - self.currentCPS = 0 - - def start(self): - """Called when the fetching starts.""" - - def stop(self): - """Called when all files have been fetched.""" - - def updateStatus(self, uri, descr, shortDescr, status): - """Called when the status of an item changes. - - This happens eg. when the downloads fails or is completed. - """ - def update_status_full(self, uri, descr, short_descr, status, file_size, - partial_size): - """Called when the status of an item changes. - - This happens eg. when the downloads fails or is completed. This - version include information on current filesize and partial size - """ - - def pulse(self): - """Called periodically to update the user interface. - - Return True to continue or False to cancel. - """ - self.percent = (((self.currentBytes + self.currentItems) * 100.0) / - float(self.totalBytes + self.totalItems)) - if self.currentCPS > 0: - self.eta = ((self.totalBytes - self.currentBytes) / - float(self.currentCPS)) - return True - - def pulse_items(self, items): - """Called periodically to update the user interface. - This function includes details about the items being fetched - Return True to continue or False to cancel. - - """ - self.percent = (((self.currentBytes + self.currentItems) * 100.0) / - float(self.totalBytes + self.totalItems)) - if self.currentCPS > 0: - self.eta = ((self.totalBytes - self.currentBytes) / - float(self.currentCPS)) - return True - - def mediaChange(self, medium, drive): - """react to media change events.""" - - -class TextFetchProgress(FetchProgress): - """ Ready to use progress object for terminal windows """ - - def __init__(self): - FetchProgress.__init__(self) - self.items = {} - - def updateStatus(self, uri, descr, shortDescr, status): - """Called when the status of an item changes. - - This happens eg. when the downloads fails or is completed. - """ - if status != self.dlQueued: - print "\r%s %s" % (self.dlStatusStr[status], descr) - self.items[uri] = status - - def pulse(self): - """Called periodically to update the user interface. - - Return True to continue or False to cancel. - """ - FetchProgress.pulse(self) - if self.currentCPS > 0: - s = "[%2.f%%] %sB/s %s" % (self.percent, - apt_pkg.SizeToStr(int(self.currentCPS)), - apt_pkg.TimeToStr(int(self.eta))) - else: - s = "%2.f%% [Working]" % (self.percent) - print "\r%s" % (s), - sys.stdout.flush() - return True - - def stop(self): - """Called when all files have been fetched.""" - print "\rDone downloading " - - def mediaChange(self, medium, drive): - """react to media change events.""" - print ("Media change: please insert the disc labeled " - "'%s' in the drive '%s' and press enter") % (medium, drive) - - return raw_input() not in ('c', 'C') - - -class DumbInstallProgress(object): - """Report the install progress. - - Subclass this class to implement install progress reporting. - """ - - def startUpdate(self): - """Start update.""" - - def run(self, pm): - """Start installation.""" - return pm.DoInstall() - - def finishUpdate(self): - """Called when update has finished.""" - - def updateInterface(self): - """Called periodically to update the user interface""" - - -class InstallProgress(DumbInstallProgress): - """An InstallProgress that is pretty useful. - - It supports the attributes 'percent' 'status' and callbacks for the dpkg - errors and conffiles and status changes. - """ - - def __init__(self): - DumbInstallProgress.__init__(self) - self.selectTimeout = 0.1 - (read, write) = os.pipe() - self.writefd = write - self.statusfd = os.fdopen(read, "r") - fcntl.fcntl(self.statusfd.fileno(), fcntl.F_SETFL, os.O_NONBLOCK) - self.read = "" - self.percent = 0.0 - self.status = "" - - def error(self, pkg, errormsg): - """Called when a error is detected during the install.""" - - def conffile(self, current, new): - """Called when a conffile question from dpkg is detected.""" - - def statusChange(self, pkg, percent, status): - """Called when the status changed.""" - - def updateInterface(self): - """Called periodically to update the interface.""" - if self.statusfd is None: - return - try: - while not self.read.endswith("\n"): - r = os.read(self.statusfd.fileno(), 1) - if not r: - return - self.read += r - except OSError, (errno_, errstr): - # resource temporarly unavailable is ignored - if errno_ != errno.EAGAIN and errno_ != errno.EWOULDBLOCK: - print errstr - if not self.read.endswith("\n"): - return - - s = self.read - #print s - try: - (status, pkg, percent, status_str) = s.split(":", 3) - except ValueError: - # silently ignore lines that can't be parsed - self.read = "" - return - #print "percent: %s %s" % (pkg, float(percent)/100.0) - if status == "pmerror": - self.error(pkg, status_str) - elif status == "pmconffile": - # we get a string like this: - # 'current-conffile' 'new-conffile' useredited distedited - match = re.match("\s*\'(.*)\'\s*\'(.*)\'.*", status_str) - if match: - self.conffile(match.group(1), match.group(2)) - elif status == "pmstatus": - if float(percent) != self.percent or status_str != self.status: - self.statusChange(pkg, float(percent), - status_str.strip()) - self.percent = float(percent) - self.status = status_str.strip() - self.read = "" - - def fork(self): - """Fork.""" - return os.fork() - - def waitChild(self): - """Wait for child progress to exit. - - The return values is the full status returned from os.waitpid() - (not only the return code). - """ - while True: - try: - select.select([self.statusfd], [], [], self.selectTimeout) - except select.error, (errno_, errstr): - if errno_ != errno.EINTR: - raise - self.updateInterface() - try: - (pid, res) = os.waitpid(self.child_pid, os.WNOHANG) - if pid == self.child_pid: - break - except OSError, (errno_, errstr): - if errno_ == errno.ECHILD: - break - if errno_ != errno.EINTR: - raise - return res - - def run(self, pm): - """Start installing. - - Returns the PackageManager status: - (pm.ResultCompleted, pm.ResultFailed, pm.ResultIncomplete) - """ - pid = self.fork() - if pid == 0: - # pm.DoInstall might raise a exception, - # when this happens, we need to catch - # it, otherwise os._exit() is not run - # and the execution continues in the - # parent code leading to very confusing bugs - try: - res = pm.DoInstall(self.writefd) - except Exception, e: - os._exit(pm.ResultFailed) - os._exit(res) - self.child_pid = pid - res = self.waitChild() - return os.WEXITSTATUS(res) - - -class CdromProgress(object): - """Report the cdrom add progress. - - Subclass this class to implement cdrom add progress reporting. - """ - - def __init__(self): - pass - - def update(self, text, step): - """Called periodically to update the user interface.""" - - def askCdromName(self): - """Called to ask for the name of the cdrom.""" - - def changeCdrom(self): - """Called to ask for the cdrom to be changed.""" - - -class DpkgInstallProgress(InstallProgress): - """Progress handler for a local Debian package installation.""" - - def run(self, debfile): - """Start installing the given Debian package.""" - self.debfile = debfile - self.debname = os.path.basename(debfile).split("_")[0] - pid = self.fork() - if pid == 0: - # child - res = os.system("/usr/bin/dpkg --status-fd %s -i %s" % \ - (self.writefd, debfile)) - os._exit(os.WEXITSTATUS(res)) - self.child_pid = pid - res = self.waitChild() - return res +__all__ = [] - def updateInterface(self): - """Process status messages from dpkg.""" - if self.statusfd is None: - return - while True: - try: - self.read += os.read(self.statusfd.fileno(), 1) - except OSError, (errno_, errstr): - # resource temporarly unavailable is ignored - if errno_ != 11: - print errstr - break - if not self.read.endswith("\n"): - continue - statusl = self.read.split(":") - if len(statusl) < 3: - print "got garbage from dpkg: '%s'" % self.read - self.read = "" - break - pkg_name = statusl[1].strip() - status = statusl[2].strip() - #print status - if status == "error": - self.error(pkg_name, status) - elif status == "conffile-prompt": - # we get a string like this: - # 'current-conffile' 'new-conffile' useredited distedited - match = re.match("\s*\'(.*)\'\s*\'(.*)\'.*", statusl[3]) - if match: - self.conffile(match.group(1), match.group(2)) - else: - self.status = status - self.read = "" +if apt_pkg._COMPAT_0_7: + from apt.progress.old import (CdromProgress, DpkgInstallProgress, + DumbInstallProgress, FetchProgress, + InstallProgress, OpProgress, + OpTextProgress, TextFetchProgress) diff --git a/apt/progress/base.py b/apt/progress/base.py new file mode 100644 index 00000000..6636cccc --- /dev/null +++ b/apt/progress/base.py @@ -0,0 +1,302 @@ +# apt/progress/base.py - Base classes for progress reporting. +# +# Copyright (C) 2009 Julian Andres Klode <jak@debian.org> +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA +"""Base classes for progress reporting. + +Custom progress classes should inherit from these classes. They can also be +used as dummy progress classes which simply do nothing. +""" +import errno +import fcntl +import os +import re +import select + +import apt_pkg + +__all__ = ['AcquireProgress', 'CdromProgress', 'InstallProgress', 'OpProgress'] + + +class AcquireProgress(object): + """Monitor object for downloads controlled by the Acquire class. + + This is an mostly abstract class. You should subclass it and implement the + methods to get something useful. + """ + + current_bytes = current_cps = fetched_bytes = last_bytes = total_bytes \ + = 0.0 + current_items = elapsed_time = total_items = 0 + + def done(self, item): + """Invoked when an item is successfully and completely fetched.""" + + def fail(self, item): + """Invoked when an item could not be fetched.""" + + def fetch(self, item): + """Invoked when some of the item's data is fetched.""" + + def ims_hit(self, item): + """Invoked when an item is confirmed to be up-to-date. + + Invoked when an item is confirmed to be up-to-date. For instance, + when an HTTP download is informed that the file on the server was + not modified. + """ + + def media_change(self, media, drive): + """Prompt the user to change the inserted removable media. + + The parameter 'media' decribes the name of the the media type that + should be changed, whereas the parameter 'drive' should be the + identifying name of the drive whose media should be changed. + + This method should not return until the user has confirmed to the user + interface that the media change is complete. It must return True if + the user confirms the media change, or False to cancel it. + """ + return False + + def pulse(self, owner): + """Periodically invoked while the Acquire process is underway. + + This method gets invoked while the Acquire progress given by the + parameter 'owner' is underway. It should display information about + the current state. + + This function returns a boolean value indicating whether the + acquisition should be continued (True) or cancelled (False). + """ + return True + + def start(self): + """Invoked when the Acquire process starts running.""" + # Reset all our values. + self.current_bytes = 0.0 + self.current_cps = 0.0 + self.current_items = 0 + self.elapsed_time = 0 + self.fetched_bytes = 0.0 + self.last_bytes = 0.0 + self.total_bytes = 0.0 + self.total_items = 0 + + def stop(self): + """Invoked when the Acquire process stops running.""" + + +class CdromProgress(object): + """Base class for reporting the progress of adding a cdrom. + + Can be used with apt_pkg.Cdrom to produce an utility like apt-cdrom. The + attribute 'total_steps' defines the total number of steps and can be used + in update() to display the current progress. + """ + + total_steps = 0 + + def ask_cdrom_name(self): + """Ask for the name of the cdrom. + + If a name has been provided, return it. Otherwise, return None to + cancel the operation. + """ + + def change_cdrom(self): + """Ask for the CD-ROM to be changed. + + Return True once the cdrom has been changed or False to cancel the + operation. + """ + + def update(self, text, current): + """Periodically invoked to update the interface. + + The string 'text' defines the text which should be displayed. The + integer 'current' defines the number of completed steps. + """ + + +class InstallProgress(object): + """Class to report the progress of installing packages.""" + + percent, select_timeout, status = 0.0, 0.1, "" + + def __init__(self): + (read, write) = os.pipe() + self.writefd = os.fdopen(write, "w") + self.statusfd = os.fdopen(read, "r") + fcntl.fcntl(self.statusfd, fcntl.F_SETFL, os.O_NONBLOCK) + + def start_update(self): + """(Abstract) Start update.""" + + def finish_update(self): + """(Abstract) Called when update has finished.""" + + def error(self, pkg, errormsg): + """(Abstract) Called when a error is detected during the install.""" + + def conffile(self, current, new): + """(Abstract) Called when a conffile question from dpkg is detected.""" + + def status_change(self, pkg, percent, status): + """(Abstract) Called when the APT status changed.""" + + def dpkg_status_change(self, pkg, status): + """(Abstract) Called when the dpkg status changed.""" + + def processing(self, pkg, stage): + """(Abstract) Sent just before a processing stage starts. + + The parameter 'stage' is one of "upgrade", "install" + (both sent before unpacking), "configure", "trigproc", "remove", + "purge". This method is used for dpkg only. + """ + + def run(self, obj): + """Install using the object 'obj'. + + This functions runs install actions. The parameter 'obj' may either + be a PackageManager object in which case its do_install() method is + called or the path to a deb file. + + If the object is a PackageManager, the functions returns the result + of calling its do_install() method. Otherwise, the function returns + the exit status of dpkg. In both cases, 0 means that there were no + problems. + """ + pid = self.fork() + if pid == 0: + # pm.do_install might raise a exception, + # when this happens, we need to catch + # it, otherwise os._exit() is not run + # and the execution continues in the + # parent code leading to very confusing bugs + try: + os._exit(obj.do_install(self.writefd.fileno())) + except AttributeError: + os._exit(os.spawnlp(os.P_WAIT, "dpkg", "dpkg", "--status-fd", + str(self.writefd.fileno()), "-i", obj)) + except Exception: + os._exit(apt_pkg.PackageManager.RESULT_FAILED) + + self.child_pid = pid + res = self.wait_child() + return os.WEXITSTATUS(res) + + def fork(self): + """Fork.""" + return os.fork() + + def update_interface(self): + """Update the interface.""" + try: + line = self.statusfd.readline() + except IOError, err: + # resource temporarly unavailable is ignored + if err.errno != errno.EAGAIN and err.errno != errno.EWOULDBLOCK: + print err.strerror + return + + pkgname = status = status_str = percent = base = "" + + if line.startswith('pm'): + try: + (status, pkgname, percent, status_str) = line.split(":", 3) + except ValueError: + # silently ignore lines that can't be parsed + self.read = "" + return + elif line.startswith('status'): + try: + (base, pkgname, status, status_str) = line.split(":", 3) + except ValueError: + (base, pkgname, status) = line.split(":", 2) + elif line.startswith('processing'): + (status, status_str, pkgname) = line.split(":", 2) + self.processing(pkgname.strip(), status_str.strip()) + + # Always strip the status message + pkgname = pkgname.strip() + status_str = status_str.strip() + status = status.strip() + + if status == 'pmerror' or status == 'error': + self.error(pkgname, status_str) + elif status == 'conffile-prompt' or status == 'pmconffile': + match = re.match("\s*\'(.*)\'\s*\'(.*)\'.*", status_str) + if match: + self.conffile(match.group(1), match.group(2)) + elif status == "pmstatus": + # FIXME: Float comparison + if float(percent) != self.percent or status_str != self.status: + self.status_change(pkgname, float(percent), status_str.strip()) + self.percent = float(percent) + self.status = status_str.strip() + elif base == "status": + self.dpkg_status_change(pkgname, status) + + def wait_child(self): + """Wait for child progress to exit. + + This method is responsible for calling update_interface() from time to + time. It exits once the child has exited. The return values is the + full status returned from os.waitpid() (not only the return code). + """ + (pid, res) = (0, 0) + while True: + try: + select.select([self.statusfd], [], [], self.select_timeout) + except select.error, (errno_, errstr): + if errno_ != errno.EINTR: + raise + + self.update_interface() + try: + (pid, res) = os.waitpid(self.child_pid, os.WNOHANG) + if pid == self.child_pid: + break + except OSError, err: + if err.errno == errno.ECHILD: + break + if err.errno != errno.EINTR: + raise + + return res + + +class OpProgress(object): + """Monitor objects for operations. + + Display the progress of operations such as opening the cache.""" + + major_change, op, percent, subop = False, "", 0.0, "" + + def update(self, percent=None): + """Called periodically to update the user interface. + + You may use the optional argument 'percent' to set the attribute + 'percent' in this call. + """ + if percent is not None: + self.percent = percent + + def done(self): + """Called once an operation has been completed.""" diff --git a/apt/progress/gtk2.py b/apt/progress/gtk2.py index f872e34f..29e730a3 100644 --- a/apt/progress/gtk2.py +++ b/apt/progress/gtk2.py @@ -11,7 +11,7 @@ # published by the Free Software Foundation; either version 2 of the # License, or (at your option) any later version. # -# his program is distributed in the hope that it will be useful, +# This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. @@ -22,7 +22,6 @@ # USA """GObject-powered progress classes and a GTK+ status widget.""" -from gettext import gettext as _ import os import time @@ -37,8 +36,17 @@ import gobject import pango import vte -import apt import apt_pkg +from apt_pkg import gettext as _ +from apt.deprecation import function_deprecated_by, AttributeDeprecatedBy +from apt.progress import base + +if apt_pkg._COMPAT_0_7: + from apt.progress import old + + +__all__ = ['GAcquireProgress', 'GInstallProgress', 'GOpProgress', + 'GtkAptProgress'] def mksig(params=(), run=gobject.SIGNAL_RUN_FIRST, rettype=gobject.TYPE_NONE): @@ -52,7 +60,7 @@ def mksig(params=(), run=gobject.SIGNAL_RUN_FIRST, rettype=gobject.TYPE_NONE): return (run, rettype, params) -class GOpProgress(gobject.GObject, apt.progress.OpProgress): +class GOpProgress(gobject.GObject, base.OpProgress): """Operation progress with GObject signals. Signals: @@ -68,22 +76,28 @@ class GOpProgress(gobject.GObject, apt.progress.OpProgress): "status-finished": mksig()} def __init__(self): - apt.progress.OpProgress.__init__(self) + base.OpProgress.__init__(self) gobject.GObject.__init__(self) self._context = glib.main_context_default() - def update(self, percent): + def update(self, percent=None): """Called to update the percentage done""" - self.emit("status-changed", self.op, percent) + base.OpProgress.update(self, percent) + self.emit("status-changed", self.op, self.percent) while self._context.pending(): self._context.iteration() def done(self): """Called when all operation have finished.""" + base.OpProgress.done(self) self.emit("status-finished") + if apt_pkg._COMPAT_0_7: + subOp = AttributeDeprecatedBy('subop') + Op = AttributeDeprecatedBy('op') -class GInstallProgress(gobject.GObject, apt.progress.InstallProgress): + +class GInstallProgress(gobject.GObject, base.InstallProgress): """Installation progress with GObject signals. Signals: @@ -107,19 +121,19 @@ class GInstallProgress(gobject.GObject, apt.progress.InstallProgress): "status-finished": mksig()} def __init__(self, term): - apt.progress.InstallProgress.__init__(self) + base.InstallProgress.__init__(self) gobject.GObject.__init__(self) self.finished = False self.time_last_update = time.time() self.term = term reaper = vte.reaper_get() - reaper.connect("child-exited", self.childExited) + reaper.connect("child-exited", self.child_exited) self.env = ["VTE_PTY_KEEP_FD=%s" % self.writefd, "DEBIAN_FRONTEND=gnome", "APT_LISTCHANGES_FRONTEND=gtk"] self._context = glib.main_context_default() - def childExited(self, term, pid, status): + def child_exited(self, term, pid, status): """Called when a child process exits""" self.apt_status = os.WEXITSTATUS(status) self.finished = True @@ -138,21 +152,31 @@ class GInstallProgress(gobject.GObject, apt.progress.InstallProgress): """ self.emit("status-conffile") - def startUpdate(self): + def start_update(self): """Called when the update starts. Emits: status-started() """ self.emit("status-started") - def finishUpdate(self): + def run(self, obj): + """Run.""" + self.finished = False + return base.InstallProgress.run(self, obj) + + def finish_update(self): """Called when the update finished. Emits: status-finished() """ self.emit("status-finished") - def statusChange(self, pkg, percent, status): + def processing(self, pkg, stage): + """Called when entering a new stage in dpkg.""" + # We have no percentage or alike, send -1 to let the bar pulse. + self.emit("status-changed", ("Installing %s...") % pkg, -1) + + def status_change(self, pkg, percent, status): """Called when the status changed. Emits: status-changed(status, percent) @@ -160,12 +184,12 @@ class GInstallProgress(gobject.GObject, apt.progress.InstallProgress): self.time_last_update = time.time() self.emit("status-changed", status, percent) - def updateInterface(self): + def update_interface(self): """Called periodically to update the interface. Emits: status-timeout() [When a timeout happens] """ - apt.progress.InstallProgress.updateInterface(self) + base.InstallProgress.update_interface(self) while self._context.pending(): self._context.iteration() if self.time_last_update + self.INSTALL_TIMEOUT < time.time(): @@ -175,40 +199,25 @@ class GInstallProgress(gobject.GObject, apt.progress.InstallProgress): """Fork the process.""" return self.term.forkpty(envv=self.env) - def waitChild(self): + def wait_child(self): """Wait for the child process to exit.""" while not self.finished: - self.updateInterface() + self.update_interface() return self.apt_status + if apt_pkg._COMPAT_0_7: + updateInterface = function_deprecated_by(update_interface) + startUpdate = function_deprecated_by(start_update) + finishUpdate = function_deprecated_by(finish_update) + statusChange = function_deprecated_by(status_change) + waitChild = function_deprecated_by(wait_child) + childExited = function_deprecated_by(child_exited) -class GDpkgInstallProgress(apt.progress.DpkgInstallProgress, GInstallProgress): - """An InstallProgress for local installations. - - Signals: - - * status-changed(str: status, int: percent) - * status-started() - Not Implemented yet - * status-finished() - * status-timeout() - When the maintainer script hangs - * status-error() - When an error happens - * status-conffile() - On Conffile - """ - - def run(self, debfile): - """Install the given package.""" - apt.progress.DpkgInstallProgress.run(self, debfile) - - def updateInterface(self): - """Called periodically to update the interface. - Emits: status-timeout() [When a timeout happens]""" - apt.progress.DpkgInstallProgress.updateInterface(self) - if self.time_last_update + self.INSTALL_TIMEOUT < time.time(): - self.emit("status-timeout") +GDpkgInstallProgress = GInstallProgress -class GFetchProgress(gobject.GObject, apt.progress.FetchProgress): +class GAcquireProgress(gobject.GObject, base.AcquireProgress): """A Fetch Progress with GObject signals. Signals: @@ -216,6 +225,8 @@ class GFetchProgress(gobject.GObject, apt.progress.FetchProgress): * status-changed(str: description, int: percent) * status-started() * status-finished() + + DEPRECATED. """ __gsignals__ = {"status-changed": mksig((str, int)), @@ -223,40 +234,98 @@ class GFetchProgress(gobject.GObject, apt.progress.FetchProgress): "status-finished": mksig()} def __init__(self): - apt.progress.FetchProgress.__init__(self) + base.AcquireProgress.__init__(self) gobject.GObject.__init__(self) self._continue = True self._context = glib.main_context_default() def start(self): + base.AcquireProgress.start(self) self.emit("status-started") def stop(self): + base.AcquireProgress.stop(self) self.emit("status-finished") def cancel(self): self._continue = False - def pulse(self): - apt.progress.FetchProgress.pulse(self) - currentItem = self.currentItems + 1 - if currentItem > self.totalItems: - currentItem = self.totalItems - if self.currentCPS > 0: + def pulse(self, owner): + base.AcquireProgress.pulse(self, owner) + current_item = self.current_items + 1 + if current_item > self.total_items: + current_item = self.total_items + if self.current_cps > 0: text = (_("Downloading file %(current)li of %(total)li with " "%(speed)s/s") % \ - {"current": currentItem, - "total": self.totalItems, - "speed": apt_pkg.SizeToStr(self.currentCPS)}) + {"current": current_item, + "total": self.total_items, + "speed": apt_pkg.size_to_str(self.current_cps)}) else: text = (_("Downloading file %(current)li of %(total)li") % \ - {"current": currentItem, - "total": self.totalItems}) - self.emit("status-changed", text, self.percent) + {"current": current_item, + "total": self.total_items}) + + percent = (((self.current_bytes + self.current_items) * 100.0) / + float(self.total_bytes + self.total_items)) + self.emit("status-changed", text, percent) while self._context.pending(): self._context.iteration() return self._continue +if apt_pkg._COMPAT_0_7: + + class GFetchProgress(gobject.GObject, old.FetchProgress): + """A Fetch Progress with GObject signals. + + Signals: + + * status-changed(str: description, int: percent) + * status-started() + * status-finished() + + DEPRECATED. + """ + + __gsignals__ = {"status-changed": mksig((str, int)), + "status-started": mksig(), + "status-finished": mksig()} + + def __init__(self): + old.FetchProgress.__init__(self) + gobject.GObject.__init__(self) + self._continue = True + self._context = glib.main_context_default() + + def start(self): + self.emit("status-started") + + def stop(self): + self.emit("status-finished") + + def cancel(self): + self._continue = False + + def pulse(self): + old.FetchProgress.pulse(self) + current_item = self.currentItems + 1 + if current_item > self.totalItems: + current_item = self.totalItems + if self.current_cps > 0: + text = (_("Downloading file %(current)li of %(total)li with " + "%(speed)s/s") % \ + {"current": current_item, + "total": self.totalItems, + "speed": apt_pkg.size_to_str(self.currentCPS)}) + else: + text = (_("Downloading file %(current)li of %(total)li") % \ + {"current": current_item, + "total": self.totalItems}) + self.emit("status-changed", text, self.percent) + while self._context.pending(): + self._context.iteration() + return self._continue + class GtkAptProgress(gtk.VBox): """Graphical progress for installation/fetch/operations. @@ -291,11 +360,15 @@ class GtkAptProgress(gtk.VBox): self._progress_open.connect("status-started", self._on_status_started) self._progress_open.connect("status-finished", self._on_status_finished) - self._progress_fetch = GFetchProgress() - self._progress_fetch.connect("status-changed", self._on_status_changed) - self._progress_fetch.connect("status-started", self._on_status_started) - self._progress_fetch.connect("status-finished", + self._progress_acquire = GAcquireProgress() + self._progress_acquire.connect("status-changed", + self._on_status_changed) + self._progress_acquire.connect("status-started", + self._on_status_started) + self._progress_acquire.connect("status-finished", self._on_status_finished) + + self._progress_fetch = None self._progress_install = GInstallProgress(self._terminal) self._progress_install.connect("status-changed", self._on_status_changed) @@ -309,19 +382,6 @@ class GtkAptProgress(gtk.VBox): self._on_status_timeout) self._progress_install.connect("status-conffile", self._on_status_timeout) - self._progress_dpkg_install = GDpkgInstallProgress(self._terminal) - self._progress_dpkg_install.connect("status-changed", - self._on_status_changed) - self._progress_dpkg_install.connect("status-started", - self._on_status_started) - self._progress_dpkg_install.connect("status-finished", - self._on_status_finished) - self._progress_dpkg_install.connect("status-timeout", - self._on_status_timeout) - self._progress_dpkg_install.connect("status-error", - self._on_status_timeout) - self._progress_dpkg_install.connect("status-conffile", - self._on_status_timeout) def clear(self): """Reset all status information.""" @@ -342,12 +402,27 @@ class GtkAptProgress(gtk.VBox): @property def dpkg_install(self): """Return the install progress handler for dpkg.""" - return self._dpkg_progress_install + return self._progress_install + + if apt_pkg._COMPAT_0_7: + + @property + def fetch(self): + """Return the fetch progress handler.""" + if self._progress_fetch is None: + self._progress_fetch = GFetchProgress() + self._progress_fetch.connect("status-changed", + self._on_status_changed) + self._progress_fetch.connect("status-started", + self._on_status_started) + self._progress_fetch.connect("status-finished", + self._on_status_finished) + return self._progress_fetch @property - def fetch(self): - """Return the fetch progress handler.""" - return self._progress_fetch + def acquire(self): + """Return the acquire progress handler.""" + return self._progress_acquire def _on_status_started(self, progress): """Called when something starts.""" @@ -364,7 +439,7 @@ class GtkAptProgress(gtk.VBox): def _on_status_changed(self, progress, status, percent): """Called when the status changed.""" self._label.set_text(status) - if percent is None: + if percent is None or percent == -1: self._progressbar.pulse() else: self._progressbar.set_fraction(percent/100.0) @@ -412,6 +487,7 @@ def _test(): """Test function""" import sys + import apt from apt.debfile import DebPackage win = gtk.Window() @@ -422,18 +498,19 @@ def _test(): win.show() cache = apt.cache.Cache(apt_progress.open) pkg = cache["xterm"] - if pkg.isInstalled: - pkg.markDelete() + if pkg.is_installed: + pkg.mark_delete() else: - pkg.markInstall() + pkg.mark_install() apt_progress.show_terminal(True) try: - cache.commit(apt_progress.fetch, apt_progress.install) + cache.commit(apt_progress.acquire, apt_progress.install) except Exception, exc: print >> sys.stderr, "Exception happened:", exc if len(sys.argv) > 1: deb = DebPackage(sys.argv[1], cache) deb.install(apt_progress.dpkg_install) + win.connect("destroy", gtk.main_quit) gtk.main() diff --git a/apt/progress/old.py b/apt/progress/old.py new file mode 100644 index 00000000..c2d95b85 --- /dev/null +++ b/apt/progress/old.py @@ -0,0 +1,205 @@ +# progress.py - progress reporting classes +# +# Copyright (c) 2005-2009 Canonical +# +# Author: Michael Vogt <michael.vogt@ubuntu.com> +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA +"""Deprecated progress reporting classes. + +This module provides classes for compatibility with python-apt 0.7. They are +completely deprecated and should not be used anymore. +""" + + +import os +import sys + +import apt_pkg +from apt.deprecation import AttributeDeprecatedBy, function_deprecated_by +import warnings +from apt.progress import base, text + +__all__ = [] + + +class OpProgress(base.OpProgress): + """Abstract class to implement reporting on cache opening.""" + + subOp = AttributeDeprecatedBy('subop') + Op = AttributeDeprecatedBy('op') + + +class OpTextProgress(OpProgress, text.OpProgress): + """A simple text based cache open reporting class.""" + + +class FetchProgress(object): + """Report the download/fetching progress.""" + + # download status constants + (dlDone, dlQueued, dlFailed, dlHit, dlIgnored) = range(5) + dlStatusStr = {dlDone: "Done", dlQueued: "Queued", dlFailed: "Failed", + dlHit: "Hit", dlIgnored: "Ignored"} + + def __init__(self): + self.eta = 0.0 + self.percent = 0.0 + # Make checking easier + self.currentBytes = 0 + self.currentItems = 0 + self.totalBytes = 0 + self.totalItems = 0 + self.currentCPS = 0 + warnings.warn("FetchProgress() is deprecated.", DeprecationWarning) + + def start(self): + """Called when the fetching starts.""" + + def stop(self): + """Called when all files have been fetched.""" + + def updateStatus(self, uri, descr, short_descr, status): + """Called when the status of an item changes. + + This happens eg. when the downloads fails or is completed. + """ + + def update_status_full(self, uri, descr, short_descr, status, file_size, + partial_size): + """Called when the status of an item changes. + + This happens eg. when the downloads fails or is completed. This + version include information on current filesize and partial size + """ + + def pulse(self): + """Called periodically to update the user interface. + + Return True to continue or False to cancel. + """ + self.percent = (((self.currentBytes + self.currentItems) * 100.0) / + float(self.totalBytes + self.totalItems)) + if self.currentCPS > 0: + self.eta = ((self.totalBytes - self.currentBytes) / + float(self.currentCPS)) + return True + + def pulse_items(self, items): + """Called periodically to update the user interface. + This function includes details about the items being fetched + Return True to continue or False to cancel. + + """ + self.percent = (((self.currentBytes + self.currentItems) * 100.0) / + float(self.totalBytes + self.totalItems)) + if self.currentCPS > 0: + self.eta = ((self.totalBytes - self.currentBytes) / + float(self.currentCPS)) + return True + + def mediaChange(self, medium, drive): + """react to media change events.""" + + +class TextFetchProgress(FetchProgress): + """ Ready to use progress object for terminal windows """ + + def __init__(self): + FetchProgress.__init__(self) + self.items = {} + + def updateStatus(self, uri, descr, short_descr, status): + """Called when the status of an item changes. + + This happens eg. when the downloads fails or is completed. + """ + if status != self.dlQueued: + print "\r%s %s" % (self.dlStatusStr[status], descr) + self.items[uri] = status + + def pulse(self): + """Called periodically to update the user interface. + + Return True to continue or False to cancel. + """ + FetchProgress.pulse(self) + if self.currentCPS > 0: + s = "[%2.f%%] %sB/s %s" % (self.percent, + apt_pkg.size_to_str(int(self.currentCPS)), + apt_pkg.time_to_str(int(self.eta))) + else: + s = "%2.f%% [Working]" % (self.percent) + print "\r%s" % (s), + sys.stdout.flush() + return True + + def stop(self): + """Called when all files have been fetched.""" + print "\rDone downloading " + + def mediaChange(self, medium, drive): + """react to media change events.""" + print ("Media change: please insert the disc labeled " + "'%s' in the drive '%s' and press enter") % (medium, drive) + + return raw_input() not in ('c', 'C') + + +class CdromProgress(base.CdromProgress): + """Report the cdrom add progress. + + This class has been replaced by apt_pkg.CdromProgress. + """ + _basetype = base.CdromProgress + askCdromName = function_deprecated_by(_basetype.ask_cdrom_name) + changeCdrom = function_deprecated_by(_basetype.change_cdrom) + del _basetype + + +class DumbInstallProgress(base.InstallProgress): + """Report the install progress. + + Subclass this class to implement install progress reporting. + """ + + startUpdate = function_deprecated_by(base.InstallProgress.start_update) + finishUpdate = function_deprecated_by(base.InstallProgress.finish_update) + updateInterface = function_deprecated_by( + base.InstallProgress.update_interface) + + +class InstallProgress(DumbInstallProgress, base.InstallProgress): + """An InstallProgress that is pretty useful. + + It supports the attributes 'percent' 'status' and callbacks for the dpkg + errors and conffiles and status changes. + """ + + selectTimeout = AttributeDeprecatedBy('select_timeout') + statusChange = function_deprecated_by(base.InstallProgress.status_change) + waitChild = function_deprecated_by(base.InstallProgress.wait_child) + + +class DpkgInstallProgress(InstallProgress): + """Progress handler for a local Debian package installation.""" + + def run(self, debfile): + """Start installing the given Debian package.""" + # Deprecated stuff + self.debfile = debfile + self.debname = os.path.basename(debfile).split("_")[0] + return base.InstallProgress(self, debfile) diff --git a/apt/progress/text.py b/apt/progress/text.py new file mode 100644 index 00000000..5e45c1db --- /dev/null +++ b/apt/progress/text.py @@ -0,0 +1,261 @@ +# Copyright (c) 2009 Julian Andres Klode <jak@debian.org> +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA +"""Progress reporting for text interfaces.""" +import sys + +import apt_pkg +from apt.progress import base + +__all__ = ['AcquireProgress', 'CdromProgress', 'OpProgress'] + + +def _(msg): + """Translate the message, also try apt if translation is missing.""" + res = apt_pkg.gettext(msg) + if res == msg: + res = apt_pkg.gettext(msg, "apt") + return res + + +class TextProgress(object): + """Internal Base class for text progress classes.""" + + def __init__(self, outfile=None): + self._file = outfile or sys.stdout + self._width = 0 + + def _write(self, msg, newline=True, maximize=False): + """Write the message on the terminal, fill remaining space.""" + self._file.write("\r") + self._file.write(msg) + + # Fill remaining stuff with whitespace + if self._width > len(msg): + self._file.write((self._width - len(msg)) * ' ') + elif maximize: # Needed for OpProgress. + self._width = max(self._width, len(msg)) + if newline: + self._file.write("\n") + else: + #self._file.write("\r") + self._file.flush() + + +class OpProgress(base.OpProgress, TextProgress): + """Operation progress reporting. + + This closely resembles OpTextProgress in libapt-pkg. + """ + + def __init__(self, outfile=None): + TextProgress.__init__(self, outfile) + base.OpProgress.__init__(self) + self.old_op = "" + + def update(self, percent=None): + """Called periodically to update the user interface.""" + base.OpProgress.update(self, percent) + if self.major_change and self.old_op: + self._write(self.old_op) + self._write("%s... %i%%\r" % (self.op, self.percent), False, True) + self.old_op = self.op + + def done(self): + """Called once an operation has been completed.""" + base.OpProgress.done(self) + if self.old_op: + self._write(_("%c%s... Done") % ('\r', self.old_op), True, True) + self.old_op = "" + + +class AcquireProgress(base.AcquireProgress, TextProgress): + """AcquireProgress for the text interface.""" + + def __init__(self, outfile=None): + TextProgress.__init__(self, outfile) + base.AcquireProgress.__init__(self) + self._signal = None + self._width = 80 + self._id = 1 + + def start(self): + """Start an Acquire progress. + + In this case, the function sets up a signal handler for SIGWINCH, i.e. + window resize signals. And it also sets id to 1. + """ + base.AcquireProgress.start(self) + import signal + self._signal = signal.signal(signal.SIGWINCH, self._winch) + # Get the window size. + self._winch() + self._id = 1L + + def _winch(self, *dummy): + """Signal handler for window resize signals.""" + import fcntl + import termios + import struct + buf = fcntl.ioctl(self._file, termios.TIOCGWINSZ, 8 * ' ') + dummy, col, dummy, dummy = struct.unpack('hhhh', buf) + self._width = col - 1 # 1 for the cursor + + def ims_hit(self, item): + """Called when an item is update (e.g. not modified on the server).""" + base.AcquireProgress.ims_hit(self, item) + line = _('Hit ') + item.description + if item.owner.filesize: + line += ' [%sB]' % apt_pkg.size_to_str(item.owner.filesize) + self._write(line) + + def fail(self, item): + """Called when an item is failed.""" + base.AcquireProgress.fail(self, item) + if item.owner.status == item.owner.STAT_DONE: + self._write(_("Ign ") + item.description) + else: + self._write(_("Err ") + item.description) + self._write(" %s" % item.owner.error_text) + + def fetch(self, item): + """Called when some of the item's data is fetched.""" + base.AcquireProgress.fetch(self, item) + # It's complete already (e.g. Hit) + if item.owner.complete: + return + item.owner.id = self._id + self._id += 1 + line = _("Get:") + "%s %s" % (item.owner.id, item.description) + if item.owner.filesize: + line += (" [%sB]" % apt_pkg.size_to_str(item.owner.filesize)) + + self._write(line) + + def pulse(self, owner): + """Periodically invoked while the Acquire process is underway. + + Return False if the user asked to cancel the whole Acquire process.""" + base.AcquireProgress.pulse(self, owner) + percent = (((self.current_bytes + self.current_items) * 100.0) / + float(self.total_bytes + self.total_items)) + + shown = False + tval = '%i%%' % percent + + end = "" + if self.current_cps: + eta = int(float(self.total_bytes - self.current_bytes) / + self.current_cps) + end = " %sB/s %s" % (apt_pkg.size_to_str(self.current_cps), + apt_pkg.time_to_str(eta)) + + for worker in owner.workers: + val = '' + if not worker.current_item: + if worker.status: + val = ' [%s]' % worker.status + if len(tval) + len(val) + len(end) >= self._width: + break + tval += val + shown = True + continue + shown = True + + if worker.current_item.owner.id: + val += " [%i %s" % (worker.current_item.owner.id, + worker.current_item.shortdesc) + else: + val += ' [%s' % worker.current_item.description + if worker.current_item.owner.mode: + val += ' %s' % worker.current_item.owner.mode + + val += ' %sB' % apt_pkg.size_to_str(worker.current_size) + + # Add the total size and percent + if worker.total_size and not worker.current_item.owner.complete: + val += "/%sB %i%%" % (apt_pkg.size_to_str(worker.total_size), + worker.current_size*100.0/worker.total_size) + + val += ']' + + if len(tval) + len(val) + len(end) >= self._width: + # Display as many items as screen width + break + else: + tval += val + + if not shown: + tval += _(" [Working]") + + if self.current_cps: + tval += (self._width - len(end) - len(tval)) * ' ' + end + + self._write(tval, False) + return True + + def media_change(self, medium, drive): + """Prompt the user to change the inserted removable media.""" + base.AcquireProgress.media_change(self, medium, drive) + self._write(_("Media change: please insert the disc labeled\n" + " '%s'\n" + "in the drive '%s' and press enter\n") % (medium, drive)) + return raw_input() not in ('c', 'C') + + def stop(self): + """Invoked when the Acquire process stops running.""" + base.AcquireProgress.stop(self) + # Trick for getting a translation from apt + self._write((_("Fetched %sB in %s (%sB/s)\n") % ( + apt_pkg.size_to_str(self.fetched_bytes), + apt_pkg.time_to_str(self.elapsed_time), + apt_pkg.size_to_str(self.current_cps))).rstrip("\n")) + + # Delete the signal again. + import signal + signal.signal(signal.SIGWINCH, self._signal) + + +class CdromProgress(base.CdromProgress, TextProgress): + """Text CD-ROM progress.""" + + def ask_cdrom_name(self): + """Ask the user to provide a name for the disc.""" + base.CdromProgress.ask_cdrom_name(self) + self._write(_("Please provide a name for this Disc, such as " + "'Debian 2.1r1 Disk 1'"), False) + try: + return raw_input(":") + except KeyboardInterrupt: + return + + def update(self, text, current): + """Set the current progress.""" + base.CdromProgress.update(self, text, current) + if text: + self._write(text, False) + + def change_cdrom(self): + """Ask the user to change the CD-ROM.""" + base.CdromProgress.change_cdrom(self) + self._write(_("Please insert a Disc in the drive and press enter"), + False) + try: + return (raw_input() == '') + except KeyboardInterrupt: + return False + +InstallProgress = base.InstallProgress |
