| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | """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. |
| | """ |
| | from __future__ import print_function |
| |
|
| | import errno |
| | import fcntl |
| | import io |
| | import os |
| | import re |
| | import select |
| | import sys |
| |
|
| | from typing import Optional, Union |
| |
|
| | 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 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.""" |
| | |
| | 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.""" |
| |
|
| | child_pid, percent, select_timeout, status = 0, 0.0, 0.1, "" |
| |
|
| | def __init__(self): |
| | |
| | (self.statusfd, self.writefd) = os.pipe() |
| | |
| | self.write_stream = os.fdopen(self.writefd, "w") |
| | self.status_stream = os.fdopen(self.statusfd, "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 __enter__(self): |
| | |
| | return self |
| |
|
| | def __exit__(self, type, value, traceback): |
| | |
| | self.write_stream.close() |
| | self.status_stream.close() |
| |
|
| | 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: |
| | try: |
| | |
| | |
| | |
| | os.set_inheritable(self.writefd, True) |
| | except AttributeError: |
| | pass |
| | |
| | |
| | |
| | |
| | |
| | try: |
| | os._exit(obj.do_install(self.write_stream.fileno())) |
| | except AttributeError: |
| | os._exit(os.spawnlp(os.P_WAIT, "dpkg", "dpkg", "--status-fd", |
| | str(self.write_stream.fileno()), "-i", |
| | obj)) |
| | except Exception as e: |
| | sys.stderr.write("%s\n" % e) |
| | 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.status_stream.readline() |
| | except IOError as err: |
| | |
| | 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: |
| | |
| | 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()) |
| |
|
| | |
| | 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": |
| | |
| | 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.status_stream], [], [], |
| | self.select_timeout) |
| | except select.error as error: |
| | (errno_, _errstr) = error.args |
| | 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 as 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.""" |
| |
|