From: Michael Tremer Date: Tue, 29 Nov 2016 16:13:53 +0000 (+0100) Subject: Create UI submodule X-Git-Tag: 0.9.28~1285^2~1463 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=10458aa01d84d367c4100c35333530b51ae3d090;p=pakfire.git Create UI submodule This patch moves things like the progressbar module and functions that format bytes into a human-readable format into a seperate ui module. Signed-off-by: Michael Tremer --- diff --git a/Makefile.am b/Makefile.am index 4664632ba..07249794a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -153,6 +153,15 @@ pakfire_repositorydir = $(pythondir)/pakfire/repository # ------------------------------------------------------------------------------ +pakfire_ui_PYTHON = \ + src/pakfire/ui/__init__.py \ + src/pakfire/ui/helpers.py \ + src/pakfire/ui/progressbar.py + +pakfire_uidir = $(pythondir)/pakfire/ui + +# ------------------------------------------------------------------------------ + pkgpyexec_LTLIBRARIES += \ _pakfire.la diff --git a/src/pakfire/progressbar.py b/src/pakfire/progressbar.py index b05a53756..b43e2a678 100644 --- a/src/pakfire/progressbar.py +++ b/src/pakfire/progressbar.py @@ -19,312 +19,6 @@ # # ############################################################################### +# XXX kept for compatibility - -import datetime -import fcntl -import math -import signal -import struct -import sys -import termios -import time - -from . import util - -from .i18n import _ - -DEFAULT_VALUE_MAX = 100 -DEFAULT_TERM_WIDTH = 80 - -class ProgressBar(object): - def __init__(self, value_max=None): - self.value_max = value_max or DEFAULT_VALUE_MAX - self.value_cur = 0 - - self.time_start = None - self.finished = False - - # Use the error console as default output. - self.fd = sys.stderr - - # Determine the width of the terminal. - self.term_width = self.get_terminal_width() - self.register_terminal_resize_signal() - - self.widgets = [] - - # Update at max. every poll seconds. - self.poll = 0.5 - - def add(self, widget): - self.widgets.append(widget) - - def start(self): - self.num_intervals = max(self.term_width, 100) - self.next_update = 0 - self.update_interval = self.value_max / self.num_intervals - - # Save the time when we started. - self.time_start = self.time_last_updated = time.time() - - # Initialize the bar. - self.update(0) - - return self - - def finish(self): - if self.finished: - return - - self.finished = True - - # Complete the progress bar. - self.update(self.value_max) - - # End the line. - self.fd.write("\n") - - self.unregister_terminal_resize_signal() - - def update(self, value): - if not self.time_start: - raise RuntimeError("You need to execute start() first") - - self.value_cur = value - - if not self._need_update(): - return - - self.next_update = self.value_cur + self.update_interval - self.last_update_time = time.time() - - self.fd.write(self._format_line()) - self.fd.write("\r") - - def _need_update(self): - if self.value_cur >= self.next_update or self.finished: - return True - - delta = time.time() - self.last_update_time - return delta > self.poll - - def _format_line(self): - result = [] - expandables = [] - - width = self.term_width - (len(self.widgets) - 1) - 4 - - for index, widget in enumerate(self.widgets): - if isinstance(widget, Widget) and widget.expandable: - result.append(widget) - expandables.append(index) - continue - - widget = format_updatable(widget, self) - result.append(widget) - - # Subtract the consumed space by this widget - width -= len(widget) - - while expandables: - portion = int(math.ceil(width / len(expandables))) - index = expandables.pop() - - widget = result[index].update(self, portion) - result[index] = widget - - # Subtract the consumed space by this widget - width -= len(widget) - - return " %s " % " ".join(result) - - def get_terminal_width(self): - cr = util.ioctl_GWINSZ(self.fd) - if cr: - return cr[1] - - # If the ioctl command failed, use the environment data. - columns = os.environ.get("COLUMNS", None) - try: - return int(columns) - 1 - except (TypeError, ValueError): - pass - - return DEFAULT_TERM_WIDTH - - def handle_terminal_resize(self, *args, **kwargs): - """ - Catches terminal resize signals. - """ - self.term_width = self.get_terminal_width() - - def register_terminal_resize_signal(self): - signal.signal(signal.SIGWINCH, self.handle_terminal_resize) - - def unregister_terminal_resize_signal(self): - signal.signal(signal.SIGWINCH, signal.SIG_DFL) - - @property - def percentage(self): - if self.value_cur >= self.value_max: - return 100.0 - - return self.value_cur * 100.0 / self.value_max - - @property - def seconds_elapsed(self): - if self.time_start: - return time.time() - self.time_start - - return 0 - - -def format_updatable(widget, pbar): - if hasattr(widget, "update"): - return widget.update(pbar) - - return widget - - -class Widget(object): - expandable = False - - def update(self, pbar): - pass - -class WidgetFill(Widget): - expandable = True - - def update(self, pbar, width): - return "#" * width - - -class WidgetTimer(Widget): - def __init__(self, format_string=None): - if format_string is None: - format_string = _("Elapsed Time: %s") - - self.format_string = format_string - - @staticmethod - def format_time(seconds): - try: - seconds = int(seconds) - except ValueError: - pass - - return "%s" % datetime.timedelta(seconds=seconds) - - def update(self, pbar): - return self.format_string % self.format_time(pbar.seconds_elapsed) - - -class WidgetETA(WidgetTimer): - def update(self, pbar): - fmt = "%-5s: %s" - - if pbar.value_cur == 0: - return fmt % (_("ETA"), "--:--:--") - - elif pbar.finished: - return fmt % (_("Time"), self.format_time(pbar.seconds_elapsed)) - - else: - eta = pbar.seconds_elapsed * pbar.value_max / pbar.value_cur - pbar.seconds_elapsed - return fmt % (_("ETA"), self.format_time(eta)) - - -class WidgetAnimatedMarker(Widget): - def __init__(self): - self.markers = "|/-\\" - self.marker_cur = -1 - - def update(self, pbar): - if pbar.finished: - return self.markers[0] - - self.marker_cur = (self.marker_cur + 1) % len(self.markers) - return self.markers[self.marker_cur] - - -class WidgetCounter(Widget): - def __init__(self, format_string="%d"): - self.format_string = format_string - - def update(self, pbar): - return self.format_string % pbar.value_cur - - -class WidgetPercentage(Widget): - def update(self, pbar): - return "%3d%%" % pbar.percentage - - -class WidgetBar(WidgetFill): - def __init__(self): - self.marker = "#" - self.marker_inactive = "-" - - self.marker_left = "[" - self.marker_right = "]" - - def update(self, pbar, width): - # Clear the screen if the progress has finished. - if pbar.finished: - return " " * width - - marker_left, marker, marker_inactive, marker_right = (format_updatable(w, pbar) - for w in (self.marker_left, self.marker, self.marker_inactive, self.marker_right)) - - width -= len(marker_left) + len(marker_right) - - if pbar.value_max: - marker *= pbar.value_cur * width // pbar.value_max - else: - marker = "" - - return "".join((marker_left, marker.ljust(width, marker_inactive), marker_right)) - - -class WidgetFileTransferSpeed(Widget): - def update(self, pbar): - speed = 0 - - if pbar.seconds_elapsed >= 1 and pbar.value_cur > 0: - speed = pbar.value_cur / pbar.seconds_elapsed - - return util.format_speed(speed) - - -if __name__ == "__main__": - pbar = ProgressBar(100) - - counter = WidgetCounter() - pbar.add(counter) - - timer = WidgetTimer() - pbar.add(timer) - - bar = WidgetBar() - pbar.add(bar) - - fill = WidgetFill() - pbar.add(fill) - - eta = WidgetETA() - pbar.add(eta) - - percentage = WidgetPercentage() - pbar.add(percentage) - - speed = WidgetFileTransferSpeed() - pbar.add(speed) - - pbar.start() - - for i in range(100): - pbar.update(i) - time.sleep(0.25) - - pbar.finish() +from .ui.progressbar import * diff --git a/src/pakfire/ui/__init__.py b/src/pakfire/ui/__init__.py new file mode 100644 index 000000000..ccd85599a --- /dev/null +++ b/src/pakfire/ui/__init__.py @@ -0,0 +1,51 @@ +#!/usr/bin/python3 +############################################################################### +# # +# Pakfire - The IPFire package management system # +# Copyright (C) 2013 Pakfire development team # +# # +# 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 3 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, see . # +# # +############################################################################### + +import sys + +from . import progressbar + +def make_progress(message, maxval, eta=True, speed=False): + # Return nothing if stdout is not a terminal. + if not sys.stdout.isatty(): + return + + if not maxval: + maxval = 1 + + pb = progressbar.ProgressBar(maxval) + pb.add("%-50s" % message) + + bar = progressbar.WidgetBar() + pb.add(bar) + + if speed: + percentage = progressbar.WidgetPercentage() + pb.add(percentage) + + filetransfer = progressbar.WidgetFileTransferSpeed() + pb.add(filetransfer) + + if eta: + eta = progressbar.WidgetETA() + pb.add(eta) + + return pb.start() diff --git a/src/pakfire/ui/helpers.py b/src/pakfire/ui/helpers.py new file mode 100644 index 000000000..7d96cf1bf --- /dev/null +++ b/src/pakfire/ui/helpers.py @@ -0,0 +1,65 @@ +#!/usr/bin/python3 +############################################################################### +# # +# Pakfire - The IPFire package management system # +# Copyright (C) 2016 Pakfire development team # +# # +# 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 3 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, see . # +# # +############################################################################### + +import fcntl +import struct +import termios + +def ioctl_GWINSZ(fd): + try: + return struct.unpack("hh", fcntl.ioctl(fd, termios.TIOCGWINSZ, "1234")) + except: + pass + +def terminal_size(): + cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2) + + if not cr: + try: + fd = os.open(os.ctermid(), os.O_RDONLY) + cr = ioctl_GWINSZ(fd) + os.close(fd) + except: + pass + + if not cr: + try: + cr = (os.environ["LINES"], os.environ["COLUMNS"]) + except: + cr = (25, 80) + + return int(cr[1]), int(cr[0]) + +def format_size(s): + units = (" ", "k", "M", "G", "T") + unit = 0 + + while abs(s) >= 1024 and unit < len(units): + s /= 1024 + unit += 1 + + return "%d%s" % (round(s), units[unit]) + +def format_time(s): + return "%02d:%02d" % (s // 60, s % 60) + +def format_speed(s): + return "%sB/s" % format_size(s) diff --git a/src/pakfire/ui/progressbar.py b/src/pakfire/ui/progressbar.py new file mode 100644 index 000000000..afba9adbc --- /dev/null +++ b/src/pakfire/ui/progressbar.py @@ -0,0 +1,340 @@ +#!/usr/bin/python3 +############################################################################### +# # +# Pakfire - The IPFire package management system # +# Copyright (C) 2013 Pakfire development team # +# # +# 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 3 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, see . # +# # +############################################################################### + +import datetime +import math +import signal +import struct +import sys +import time + +from ..i18n import _ + +from . import helpers + +DEFAULT_VALUE_MAX = 100 +DEFAULT_TERM_WIDTH = 80 + +class ProgressBar(object): + def __init__(self, value_max=None): + self.value_max = value_max or DEFAULT_VALUE_MAX + self.value_cur = 0 + + self.time_start = None + self.finished = False + + # Use the error console as default output. + self.fd = sys.stderr + + # Determine the width of the terminal. + self.term_width = self.get_terminal_width() + self.register_terminal_resize_signal() + + self.widgets = [] + + # Update at max. every poll seconds. + self.poll = 0.5 + + def add(self, widget): + self.widgets.append(widget) + + def start(self): + self.num_intervals = max(self.term_width, 100) + self.next_update = 0 + self.update_interval = self.value_max / self.num_intervals + + # Save the time when we started. + self.time_start = self.time_last_updated = time.time() + + # Initialize the bar. + self.update(0) + + return self + + def finish(self): + if self.finished: + return + + self.finished = True + + # Complete the progress bar. + self.update(self.value_max) + + # End the line. + self.fd.write("\n") + + self.unregister_terminal_resize_signal() + + def update(self, value): + if not self.time_start: + raise RuntimeError("You need to execute start() first") + + self.value_cur = value + + if not self._need_update(): + return + + self.next_update = self.value_cur + self.update_interval + self.last_update_time = time.time() + + self.fd.write(self._format_line()) + self.fd.write("\r") + + def update_increment(self, value): + return self.update(self.value_cur + value) + + def _need_update(self): + if self.value_cur >= self.next_update or self.finished: + return True + + delta = time.time() - self.last_update_time + return delta > self.poll + + def _format_line(self): + result = [] + expandables = [] + + width = self.term_width - (len(self.widgets) - 1) - 4 + + for index, widget in enumerate(self.widgets): + if isinstance(widget, Widget) and widget.expandable: + result.append(widget) + expandables.append(index) + continue + + widget = format_updatable(widget, self) + result.append(widget) + + # Subtract the consumed space by this widget + width -= len(widget) + + while expandables: + portion = int(math.ceil(width / len(expandables))) + index = expandables.pop() + + widget = result[index].update(self, portion) + result[index] = widget + + # Subtract the consumed space by this widget + width -= len(widget) + + return " %s " % " ".join(result) + + def get_terminal_width(self): + cr = helpers.ioctl_GWINSZ(self.fd) + if cr: + return cr[1] + + # If the ioctl command failed, use the environment data. + columns = os.environ.get("COLUMNS", None) + try: + return int(columns) - 1 + except (TypeError, ValueError): + pass + + return DEFAULT_TERM_WIDTH + + def handle_terminal_resize(self, *args, **kwargs): + """ + Catches terminal resize signals. + """ + self.term_width = self.get_terminal_width() + + def register_terminal_resize_signal(self): + signal.signal(signal.SIGWINCH, self.handle_terminal_resize) + + def unregister_terminal_resize_signal(self): + signal.signal(signal.SIGWINCH, signal.SIG_DFL) + + @property + def percentage(self): + if self.value_cur >= self.value_max: + return 100.0 + + return self.value_cur * 100.0 / self.value_max + + @property + def seconds_elapsed(self): + if self.time_start: + return time.time() - self.time_start + + return 0 + + +def format_updatable(widget, pbar): + if hasattr(widget, "update"): + return widget.update(pbar) + + return widget + + +class Widget(object): + expandable = False + + def update(self, pbar): + pass + +class WidgetFill(Widget): + expandable = True + + def update(self, pbar, width): + return "#" * width + + +class WidgetTimer(Widget): + def __init__(self, format_string=None): + if format_string is None: + format_string = _("Elapsed Time: %s") + + self.format_string = format_string + + @staticmethod + def format_time(seconds): + try: + seconds = int(seconds) + except ValueError: + pass + + return "%s" % datetime.timedelta(seconds=seconds) + + def update(self, pbar): + return self.format_string % self.format_time(pbar.seconds_elapsed) + + +class WidgetETA(WidgetTimer): + def update(self, pbar): + fmt = "%-5s: %s" + + if pbar.value_cur == 0: + return fmt % (_("ETA"), "--:--:--") + + elif pbar.finished: + return fmt % (_("Time"), self.format_time(pbar.seconds_elapsed)) + + else: + eta = pbar.seconds_elapsed * pbar.value_max / pbar.value_cur - pbar.seconds_elapsed + return fmt % (_("ETA"), self.format_time(eta)) + + +class WidgetAnimatedMarker(Widget): + def __init__(self): + self.markers = "|/-\\" + self.marker_cur = -1 + + def update(self, pbar): + if pbar.finished: + return self.markers[0] + + self.marker_cur = (self.marker_cur + 1) % len(self.markers) + return self.markers[self.marker_cur] + + +class WidgetCounter(Widget): + def __init__(self, format_string="%d"): + self.format_string = format_string + + def update(self, pbar): + return self.format_string % pbar.value_cur + + +class WidgetPercentage(Widget): + def __init__(self, clear_when_finished=False): + self.clear_when_finished = clear_when_finished + + def update(self, pbar): + if self.clear_when_finished and pbar.finished: + return "" + + return "%3d%%" % pbar.percentage + + +class WidgetBar(WidgetFill): + def __init__(self): + self.marker = "#" + self.marker_inactive = "-" + + self.marker_left = "[" + self.marker_right = "]" + + def update(self, pbar, width): + # Clear the screen if the progress has finished. + if pbar.finished: + return " " * width + + marker_left, marker, marker_inactive, marker_right = (format_updatable(w, pbar) + for w in (self.marker_left, self.marker, self.marker_inactive, self.marker_right)) + + width -= len(marker_left) + len(marker_right) + + if pbar.value_max: + marker *= pbar.value_cur * width // pbar.value_max + else: + marker = "" + + return "".join((marker_left, marker.ljust(width, marker_inactive), marker_right)) + + +class WidgetFileTransferSpeed(Widget): + def update(self, pbar): + speed = 0 + + if pbar.seconds_elapsed >= 1 and pbar.value_cur > 0: + speed = pbar.value_cur / pbar.seconds_elapsed + + return helpers.format_speed(speed) + + +class WidgetBytesReceived(Widget): + def update(self, pbar): + return helpers.format_size(pbar.value_cur) + + +if __name__ == "__main__": + pbar = ProgressBar(100) + + counter = WidgetCounter() + pbar.add(counter) + + timer = WidgetTimer() + pbar.add(timer) + + bar = WidgetBar() + pbar.add(bar) + + fill = WidgetFill() + pbar.add(fill) + + eta = WidgetETA() + pbar.add(eta) + + percentage = WidgetPercentage() + pbar.add(percentage) + + speed = WidgetFileTransferSpeed() + pbar.add(speed) + + pbar.start() + + for i in range(100): + pbar.update(i) + time.sleep(0.25) + + pbar.finish() diff --git a/src/pakfire/util.py b/src/pakfire/util.py index fcb81220c..3bdab23e7 100644 --- a/src/pakfire/util.py +++ b/src/pakfire/util.py @@ -19,17 +19,13 @@ # # ############################################################################### -import fcntl import hashlib -import math import os import random import shutil import signal import string -import struct import sys -import termios import time import logging @@ -79,7 +75,7 @@ def random_string(length=20): def make_progress(message, maxval, eta=True, speed=False): # XXX delay importing the progressbar module # (because of a circular dependency) - from . import progressbar + from .ui import progressbar # Return nothing if stdout is not a terminal. if not sys.stdout.isatty(): @@ -130,55 +126,6 @@ def rm(path, *args, **kargs): else: raise -def ioctl_GWINSZ(fd): - try: - return struct.unpack("hh", fcntl.ioctl(fd, termios.TIOCGWINSZ, "1234")) - except: - pass - -def terminal_size(): - cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2) - - if not cr: - try: - fd = os.open(os.ctermid(), os.O_RDONLY) - cr = ioctl_GWINSZ(fd) - os.close(fd) - except: - pass - - if not cr: - try: - cr = (os.environ['LINES'], os.environ['COLUMNS']) - except: - cr = (25, 80) - - return int(cr[1]), int(cr[0]) - -def format_size(s): - sign = 1 - - # If s is negative, we save the sign and run the calculation with the - # absolute value of s. - if s < 0: - sign = -1 - s = -1 * s - - units = (" ", "k", "M", "G", "T") - unit = 0 - - while s >= 1024 and unit < len(units): - s /= 1024 - unit += 1 - - return "%d%s" % (round(s) * sign, units[unit]) - -def format_time(s): - return "%02d:%02d" % (s // 60, s % 60) - -def format_speed(s): - return "%sB/s" % format_size(s) - def calc_hash1(filename=None, data=None): h = hashlib.new("sha1")