From 9e4b0be832659f4469154ea74de60e1fd8c94517 Mon Sep 17 00:00:00 2001 From: Michal Domonkos Date: Jan 19 2021 13:36:21 +0000 Subject: spectool: use our own progressbar implementation Add a basic progressbar with the same interface as that of progressbar2, so that we can drop yet another dependency. This is a very simple bar showing the downloaded and target size, without any speed or ETA calculations, but should be good enough. Note that progressbar2 will still be used in case it's installed, as it has a few additional bells and whistles. --- diff --git a/rpmdev-spectool b/rpmdev-spectool index 56a2d43..5bc10ba 100755 --- a/rpmdev-spectool +++ b/rpmdev-spectool @@ -22,13 +22,14 @@ import argparse import os +import shutil +import sys import tempfile import time from collections import OrderedDict from typing import Optional from urllib.parse import urlparse -import progressbar import requests import rpm @@ -206,9 +207,98 @@ def split_numbers(args: str) -> list: return args.split(",") +def format_size(size, SI=False): + symbols = ' KMGTPEZY' + depth = 0 + max_depth = len(symbols) - 1 + unit = 1000.0 if SI else 1024.0 + + # 1023.95 should be formatted as 1.0 (not 1024.0) + # More info: https://stackoverflow.com/a/63839503 + thres = unit - 0.05 + + while size >= thres and depth < max_depth: + depth += 1 + size /= unit + symbol = ' %siB' % symbols[depth] if depth > 0 else '' + + return '%.1f%s' % (size, symbol) + + +class SimpleProgressBar(object): + FORMAT = '{value:>10} / {max_value:<10} [{bars}]' + BARS = '= ' + SPINLEN = 5 + + def __init__(self, stream=sys.stderr, max_width=80, fps=10): + self._stream = stream + self._max_width = max_width + self._min_delay = 1 / fps + + def start(self, max_value): + self._value = 0 + self._max_value = max_value or 0 + self._status = dict() + self._spinner = 0 + self._timestamp = 0 + self.update(0) + + def update(self, value): + self._value = value + if value > self._max_value: + self._max_value = 0 + + ts = time.time() + if (ts - self._timestamp) < self._min_delay: + return + self._timestamp = ts + + status = {'value': format_size(value), + 'max_value': format_size(self._max_value) \ + if self._max_value else '???', + 'bars': ''} + + termw = min(shutil.get_terminal_size()[0], self._max_width) + nbars = max(termw - len(self.FORMAT.format(**status)), 0) + nfill = nskip = 0 + + if self._max_value: + nfill = round(nbars * value / self._max_value) + elif nbars > self.SPINLEN: + nfill = self.SPINLEN + nskip = self._spinner % (nbars - self.SPINLEN) + self._spinner = nskip + 1 + + status['bars'] = self.BARS[1] * nskip + \ + self.BARS[0] * nfill + \ + self.BARS[1] * (nbars - nfill - nskip) + + if status == self._status: + return + self._status = status + + self._stream.write('\r') + self._stream.write(self.FORMAT.format(**self._status)) + self._stream.flush() + + def finish(self): + self._max_value = self._value + self._timestamp = 0 # Force an update + self.update(self._value) + + self._stream.write('\n') + self._stream.flush() + + +try: + from progressbar import DataTransferBar as ProgressBar +except ImportError: + ProgressBar = SimpleProgressBar + + # simple streamed file download progress tracker inspired by requests_download class ProgressTracker: - def __init__(self, progress_bar: progressbar.ProgressBar): + def __init__(self, progress_bar: ProgressBar): self.progress_bar = progress_bar self.received = 0 @@ -233,6 +323,27 @@ class ProgressTracker: self.progress_bar.finish() +# For easy progressbar debugging +class DummyResponse(object): + class RawData(object): + def __init__(self, size): + self.size = size + + def stream(self, chunk_size, decode_content=False): + for i in range(round(self.size / chunk_size)): + time.sleep(0.001) + yield b'0' * chunk_size + + def __init__(self, headers=None, size=1024*1024*50): + self.raw = self.RawData(size) + self.headers = headers + if headers is None: + self.headers = {'content-length': size} + + def raise_for_status(self): + pass + + # simple streamed file download implementation inspired by requests_download def download(url, target, headers=None, tracker: Optional[ProgressTracker] = None): if headers is None: @@ -268,7 +379,7 @@ def get_file(url: str, path: str, force: bool) -> bool: print("File '{}' already present.".format(path)) return False - progress = ProgressTracker(progressbar.DataTransferBar()) + progress = ProgressTracker(ProgressBar()) download(url, path, tracker=progress) return True