Add basic download info to view

This commit is contained in:
Florian Bruhin 2014-06-12 23:29:34 +02:00
parent 2ffc9bb00a
commit 8c673ee66c
4 changed files with 80 additions and 24 deletions

View File

@ -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):

View File

@ -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:

View File

@ -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()

View File

@ -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)