From 8c673ee66c69a62679fc6311547b803cec6a755a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 12 Jun 2014 23:29:34 +0200 Subject: [PATCH] Add basic download info to view --- qutebrowser/browser/downloads.py | 54 +++++++++++++++++++---------- qutebrowser/models/downloadmodel.py | 6 +--- qutebrowser/test/utils/test_misc.py | 28 +++++++++++++++ qutebrowser/utils/misc.py | 16 +++++++++ 4 files changed, 80 insertions(+), 24 deletions(-) diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index 8cbec2035..fe2825bba 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -26,7 +26,7 @@ import qutebrowser.config.config as config import qutebrowser.utils.message as message from qutebrowser.utils.log import downloads as logger from qutebrowser.utils.usertypes import PromptMode -from qutebrowser.utils.misc import interpolate_color +from qutebrowser.utils.misc import interpolate_color, format_seconds class DownloadItem(QObject): @@ -45,21 +45,17 @@ class DownloadItem(QObject): fileobj: The file object to download the file to. _last_done: The count of bytes which where downloaded when calculating the speed the last time. - _last_percentage: The remembered percentage for percentage_changed. + _last_percentage: The remembered percentage for data_changed. Signals: - speed_changed: The download speed changed. - arg: The speed in bytes/s - percentage_changed: The download percentage changed. - arg: The new percentage, -1 if unknown. + data_changed: The downloads metadata changed. finished: The download was finished. error: An error with the download occured. arg: The error message as string. """ REFRESH_INTERVAL = 200 - speed_changed = pyqtSignal(float) - percentage_changed = pyqtSignal(int) + data_changed = pyqtSignal() finished = pyqtSignal() error = pyqtSignal(str) @@ -74,6 +70,7 @@ class DownloadItem(QObject): self.bytes_done = None self.bytes_total = None self.speed = None + self.basename = '???' self.fileobj = None self._do_delayed_write = False self._last_done = None @@ -88,6 +85,19 @@ class DownloadItem(QObject): self.timer.setInterval(self.REFRESH_INTERVAL) self.timer.start() + def __str__(self): + """Get the download as a string. + + Example: foo.pdf [699.2K/s|0.34|16%|4.253/25.124] + """ + perc = 0 if self.percentage is None else round(self.percentage) + remaining = (format_seconds(self.remaining_time) + if self.remaining_time is not None else + '?') + return '{name} [{speed}|{remaining}|{perc: 2}%|{down}/{total}]'.format( + name=self.basename, speed=self.speed, remaining=remaining, + perc=perc, down=self.bytes_done, total=self.bytes_total) + def _die(self, msg): """Abort the download and emit an error.""" self.error.emit(msg) @@ -112,6 +122,15 @@ class DownloadItem(QObject): else: return 100 * self.bytes_done / self.bytes_total + @property + def remaining_time(self): + """Property to get the remaining download time in seconds.""" + if (self.bytes_total is None or self.bytes_total <= 0 or + self.speed is None or self.speed == 0): + return None + return (self.bytes_total - self.bytes_done) / self.speed + + def bg_color(self): """Background color to be shown.""" start = config.get('colors', 'download.bg.start') @@ -139,6 +158,7 @@ class DownloadItem(QObject): if self.fileobj is not None: raise ValueError("Filename was already set! filename: {}, " "existing: {}".format(filename, self.fileobj)) + self.basename = os.path.basename(filename) try: self.fileobj = open(filename, 'wb') if self._do_delayed_write: @@ -174,11 +194,7 @@ class DownloadItem(QObject): """ self.bytes_done = bytes_done self.bytes_total = bytes_total - perc = round(self.percentage) - if perc != self._last_percentage: - logger.debug("{}% downloaded".format(perc)) - self.percentage_changed.emit(perc) - self._last_percentage = perc + self.data_changed.emit() @pyqtSlot() def on_reply_finished(self): @@ -233,7 +249,7 @@ class DownloadItem(QObject): self.speed = delta * 1000 / self.REFRESH_INTERVAL logger.debug("Download speed: {} bytes/sec".format(self.speed)) self._last_done = self.bytes_done - self.speed_changed.emit(self.speed) + self.data_changed.emit() class DownloadManager(QObject): @@ -296,21 +312,21 @@ class DownloadManager(QObject): """ suggested_filename = self._get_filename(reply) download_location = config.get('storage', 'download-directory') - suggested_filename = os.path.join(download_location, + suggested_filepath = os.path.join(download_location, suggested_filename) - logger.debug("fetch: {} -> {}".format(reply.url(), suggested_filename)) + logger.debug("fetch: {} -> {}".format(reply.url(), suggested_filepath)) download = DownloadItem(reply) download.finished.connect(self.on_finished) - download.percentage_changed.connect(self.on_data_changed) - download.speed_changed.connect(self.on_data_changed) + download.data_changed.connect(self.on_data_changed) download.error.connect(self.on_error) + download.basename = suggested_filename self.download_about_to_be_added.emit(len(self.downloads) + 1) self.downloads.append(download) self.download_added.emit() message.question("Save file to:", mode=PromptMode.text, handler=download.set_filename, cancelled_handler=download.cancel, - default=suggested_filename) + default=suggested_filepath) @pyqtSlot() def on_finished(self): diff --git a/qutebrowser/models/downloadmodel.py b/qutebrowser/models/downloadmodel.py index 1c28f6148..773880ee2 100644 --- a/qutebrowser/models/downloadmodel.py +++ b/qutebrowser/models/downloadmodel.py @@ -69,11 +69,7 @@ class DownloadModel(QAbstractListModel): except IndexError: return QVariant() if role == Qt.DisplayRole: - perc = item.percentage - if perc is None: - data = QVariant() - else: - data = str(round(perc)) # FIXME + data = str(item) elif role == Qt.ForegroundRole: data = config.get('colors', 'download.fg') elif role == Qt.BackgroundRole: diff --git a/qutebrowser/test/utils/test_misc.py b/qutebrowser/test/utils/test_misc.py index 8cdace43e..8d7853587 100644 --- a/qutebrowser/test/utils/test_misc.py +++ b/qutebrowser/test/utils/test_misc.py @@ -525,5 +525,33 @@ class InterpolateColorTests(TestCase): self.assertEqual(color, expected) +class FormatSecondsTests(TestCase): + + """Tests for format_seconds. + + Class attributes: + TESTS: A list of (input, output) tuples. + """ + + TESTS = [ + (-1, '-0:01'), + (0, '0:00'), + (59, '0:59'), + (60, '1:00'), + (60.4, '1:00'), + (61, '1:01'), + (-61, '-1:01'), + (3599, '59:59'), + (3600, '1:00:00'), + (3601, '1:00:01'), + (36000, '10:00:00'), + ] + + def test_format_seconds(self): + """Test format_seconds with several tests.""" + for seconds, out in self.TESTS: + self.assertEqual(utils.format_seconds(seconds), out, seconds) + + if __name__ == '__main__': unittest.main() diff --git a/qutebrowser/utils/misc.py b/qutebrowser/utils/misc.py index 8ef39e166..782a9d973 100644 --- a/qutebrowser/utils/misc.py +++ b/qutebrowser/utils/misc.py @@ -354,3 +354,19 @@ def interpolate_color(start, end, percent, colorspace=QColor.Rgb): else: raise ValueError("Invalid colorspace!") return out.convertTo(start.spec()) + + +def format_seconds(total_seconds): + """Format a count of seconds to get a [H:]M:SS string.""" + prefix = '-' if total_seconds < 0 else '' + hours, rem = divmod(abs(round(total_seconds)), 3600) + minutes, seconds = divmod(rem, 60) + chunks = [] + if hours: + chunks.append(str(hours)) + min_format = '{:02}' + else: + min_format = '{}' + chunks.append(min_format.format(minutes)) + chunks.append('{:02}'.format(seconds)) + return prefix + ':'.join(chunks)