Add basic download info to view
This commit is contained in:
parent
2ffc9bb00a
commit
8c673ee66c
@ -26,7 +26,7 @@ import qutebrowser.config.config as config
|
|||||||
import qutebrowser.utils.message as message
|
import qutebrowser.utils.message as message
|
||||||
from qutebrowser.utils.log import downloads as logger
|
from qutebrowser.utils.log import downloads as logger
|
||||||
from qutebrowser.utils.usertypes import PromptMode
|
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):
|
class DownloadItem(QObject):
|
||||||
@ -45,21 +45,17 @@ class DownloadItem(QObject):
|
|||||||
fileobj: The file object to download the file to.
|
fileobj: The file object to download the file to.
|
||||||
_last_done: The count of bytes which where downloaded when calculating
|
_last_done: The count of bytes which where downloaded when calculating
|
||||||
the speed the last time.
|
the speed the last time.
|
||||||
_last_percentage: The remembered percentage for percentage_changed.
|
_last_percentage: The remembered percentage for data_changed.
|
||||||
|
|
||||||
Signals:
|
Signals:
|
||||||
speed_changed: The download speed changed.
|
data_changed: The downloads metadata changed.
|
||||||
arg: The speed in bytes/s
|
|
||||||
percentage_changed: The download percentage changed.
|
|
||||||
arg: The new percentage, -1 if unknown.
|
|
||||||
finished: The download was finished.
|
finished: The download was finished.
|
||||||
error: An error with the download occured.
|
error: An error with the download occured.
|
||||||
arg: The error message as string.
|
arg: The error message as string.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
REFRESH_INTERVAL = 200
|
REFRESH_INTERVAL = 200
|
||||||
speed_changed = pyqtSignal(float)
|
data_changed = pyqtSignal()
|
||||||
percentage_changed = pyqtSignal(int)
|
|
||||||
finished = pyqtSignal()
|
finished = pyqtSignal()
|
||||||
error = pyqtSignal(str)
|
error = pyqtSignal(str)
|
||||||
|
|
||||||
@ -74,6 +70,7 @@ class DownloadItem(QObject):
|
|||||||
self.bytes_done = None
|
self.bytes_done = None
|
||||||
self.bytes_total = None
|
self.bytes_total = None
|
||||||
self.speed = None
|
self.speed = None
|
||||||
|
self.basename = '???'
|
||||||
self.fileobj = None
|
self.fileobj = None
|
||||||
self._do_delayed_write = False
|
self._do_delayed_write = False
|
||||||
self._last_done = None
|
self._last_done = None
|
||||||
@ -88,6 +85,19 @@ class DownloadItem(QObject):
|
|||||||
self.timer.setInterval(self.REFRESH_INTERVAL)
|
self.timer.setInterval(self.REFRESH_INTERVAL)
|
||||||
self.timer.start()
|
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):
|
def _die(self, msg):
|
||||||
"""Abort the download and emit an error."""
|
"""Abort the download and emit an error."""
|
||||||
self.error.emit(msg)
|
self.error.emit(msg)
|
||||||
@ -112,6 +122,15 @@ class DownloadItem(QObject):
|
|||||||
else:
|
else:
|
||||||
return 100 * self.bytes_done / self.bytes_total
|
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):
|
def bg_color(self):
|
||||||
"""Background color to be shown."""
|
"""Background color to be shown."""
|
||||||
start = config.get('colors', 'download.bg.start')
|
start = config.get('colors', 'download.bg.start')
|
||||||
@ -139,6 +158,7 @@ class DownloadItem(QObject):
|
|||||||
if self.fileobj is not None:
|
if self.fileobj is not None:
|
||||||
raise ValueError("Filename was already set! filename: {}, "
|
raise ValueError("Filename was already set! filename: {}, "
|
||||||
"existing: {}".format(filename, self.fileobj))
|
"existing: {}".format(filename, self.fileobj))
|
||||||
|
self.basename = os.path.basename(filename)
|
||||||
try:
|
try:
|
||||||
self.fileobj = open(filename, 'wb')
|
self.fileobj = open(filename, 'wb')
|
||||||
if self._do_delayed_write:
|
if self._do_delayed_write:
|
||||||
@ -174,11 +194,7 @@ class DownloadItem(QObject):
|
|||||||
"""
|
"""
|
||||||
self.bytes_done = bytes_done
|
self.bytes_done = bytes_done
|
||||||
self.bytes_total = bytes_total
|
self.bytes_total = bytes_total
|
||||||
perc = round(self.percentage)
|
self.data_changed.emit()
|
||||||
if perc != self._last_percentage:
|
|
||||||
logger.debug("{}% downloaded".format(perc))
|
|
||||||
self.percentage_changed.emit(perc)
|
|
||||||
self._last_percentage = perc
|
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def on_reply_finished(self):
|
def on_reply_finished(self):
|
||||||
@ -233,7 +249,7 @@ class DownloadItem(QObject):
|
|||||||
self.speed = delta * 1000 / self.REFRESH_INTERVAL
|
self.speed = delta * 1000 / self.REFRESH_INTERVAL
|
||||||
logger.debug("Download speed: {} bytes/sec".format(self.speed))
|
logger.debug("Download speed: {} bytes/sec".format(self.speed))
|
||||||
self._last_done = self.bytes_done
|
self._last_done = self.bytes_done
|
||||||
self.speed_changed.emit(self.speed)
|
self.data_changed.emit()
|
||||||
|
|
||||||
|
|
||||||
class DownloadManager(QObject):
|
class DownloadManager(QObject):
|
||||||
@ -296,21 +312,21 @@ class DownloadManager(QObject):
|
|||||||
"""
|
"""
|
||||||
suggested_filename = self._get_filename(reply)
|
suggested_filename = self._get_filename(reply)
|
||||||
download_location = config.get('storage', 'download-directory')
|
download_location = config.get('storage', 'download-directory')
|
||||||
suggested_filename = os.path.join(download_location,
|
suggested_filepath = os.path.join(download_location,
|
||||||
suggested_filename)
|
suggested_filename)
|
||||||
logger.debug("fetch: {} -> {}".format(reply.url(), suggested_filename))
|
logger.debug("fetch: {} -> {}".format(reply.url(), suggested_filepath))
|
||||||
download = DownloadItem(reply)
|
download = DownloadItem(reply)
|
||||||
download.finished.connect(self.on_finished)
|
download.finished.connect(self.on_finished)
|
||||||
download.percentage_changed.connect(self.on_data_changed)
|
download.data_changed.connect(self.on_data_changed)
|
||||||
download.speed_changed.connect(self.on_data_changed)
|
|
||||||
download.error.connect(self.on_error)
|
download.error.connect(self.on_error)
|
||||||
|
download.basename = suggested_filename
|
||||||
self.download_about_to_be_added.emit(len(self.downloads) + 1)
|
self.download_about_to_be_added.emit(len(self.downloads) + 1)
|
||||||
self.downloads.append(download)
|
self.downloads.append(download)
|
||||||
self.download_added.emit()
|
self.download_added.emit()
|
||||||
message.question("Save file to:", mode=PromptMode.text,
|
message.question("Save file to:", mode=PromptMode.text,
|
||||||
handler=download.set_filename,
|
handler=download.set_filename,
|
||||||
cancelled_handler=download.cancel,
|
cancelled_handler=download.cancel,
|
||||||
default=suggested_filename)
|
default=suggested_filepath)
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def on_finished(self):
|
def on_finished(self):
|
||||||
|
@ -69,11 +69,7 @@ class DownloadModel(QAbstractListModel):
|
|||||||
except IndexError:
|
except IndexError:
|
||||||
return QVariant()
|
return QVariant()
|
||||||
if role == Qt.DisplayRole:
|
if role == Qt.DisplayRole:
|
||||||
perc = item.percentage
|
data = str(item)
|
||||||
if perc is None:
|
|
||||||
data = QVariant()
|
|
||||||
else:
|
|
||||||
data = str(round(perc)) # FIXME
|
|
||||||
elif role == Qt.ForegroundRole:
|
elif role == Qt.ForegroundRole:
|
||||||
data = config.get('colors', 'download.fg')
|
data = config.get('colors', 'download.fg')
|
||||||
elif role == Qt.BackgroundRole:
|
elif role == Qt.BackgroundRole:
|
||||||
|
@ -525,5 +525,33 @@ class InterpolateColorTests(TestCase):
|
|||||||
self.assertEqual(color, expected)
|
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__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -354,3 +354,19 @@ def interpolate_color(start, end, percent, colorspace=QColor.Rgb):
|
|||||||
else:
|
else:
|
||||||
raise ValueError("Invalid colorspace!")
|
raise ValueError("Invalid colorspace!")
|
||||||
return out.convertTo(start.spec())
|
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)
|
||||||
|
Loading…
Reference in New Issue
Block a user