Merge branch 'download_cmd_interface' of https://github.com/oed/qutebrowser into oed-download_cmd_interface
This commit is contained in:
commit
8e2e996369
@ -130,12 +130,12 @@ Contributors, sorted by the number of commits in descending order:
|
|||||||
// QUTE_AUTHORS_START
|
// QUTE_AUTHORS_START
|
||||||
* Florian Bruhin
|
* Florian Bruhin
|
||||||
* Claude
|
* Claude
|
||||||
|
* Joel Torstensson
|
||||||
* ZDarian
|
* ZDarian
|
||||||
* Peter Vilim
|
* Peter Vilim
|
||||||
* John ShaggyTwoDope Jenkins
|
* John ShaggyTwoDope Jenkins
|
||||||
* rikn00
|
* rikn00
|
||||||
* Martin Zimmermann
|
* Martin Zimmermann
|
||||||
* Joel Torstensson
|
|
||||||
* Error 800
|
* Error 800
|
||||||
* Brian Jackson
|
* Brian Jackson
|
||||||
* Patric Schmitz
|
* Patric Schmitz
|
||||||
|
@ -8,10 +8,12 @@
|
|||||||
|<<adblock-update,adblock-update>>|Update the adblock block lists.
|
|<<adblock-update,adblock-update>>|Update the adblock block lists.
|
||||||
|<<back,back>>|Go back in the history of the current tab.
|
|<<back,back>>|Go back in the history of the current tab.
|
||||||
|<<bind,bind>>|Bind a key to a command.
|
|<<bind,bind>>|Bind a key to a command.
|
||||||
|<<cancel-download,cancel-download>>|Cancel the first/[count]th download.
|
|
||||||
|<<close,close>>|Close the current window.
|
|<<close,close>>|Close the current window.
|
||||||
|<<download,download>>|Download a given URL, given as string.
|
|<<download,download>>|Download a given URL, or current page if no URL given.
|
||||||
|<<download-page,download-page>>|Download the current page.
|
|<<download-cancel,download-cancel>>|Cancel the last/[count]th download.
|
||||||
|
|<<download-delete,download-delete>>|Delete the last/[count]th download from disk.
|
||||||
|
|<<download-open,download-open>>|Open the last/[count]th download.
|
||||||
|
|<<download-remove,download-remove>>|Remove the last/[count]th download from the list.
|
||||||
|<<forward,forward>>|Go forward in the history of the current tab.
|
|<<forward,forward>>|Go forward in the history of the current tab.
|
||||||
|<<fullscreen,fullscreen>>|Toggle fullscreen mode.
|
|<<fullscreen,fullscreen>>|Toggle fullscreen mode.
|
||||||
|<<help,help>>|Show help about a command or setting.
|
|<<help,help>>|Show help about a command or setting.
|
||||||
@ -85,30 +87,52 @@ Bind a key to a command.
|
|||||||
* +*-m*+, +*--mode*+: A comma-separated list of modes to bind the key in (default: `normal`).
|
* +*-m*+, +*--mode*+: A comma-separated list of modes to bind the key in (default: `normal`).
|
||||||
|
|
||||||
|
|
||||||
[[cancel-download]]
|
|
||||||
=== cancel-download
|
|
||||||
Cancel the first/[count]th download.
|
|
||||||
|
|
||||||
==== count
|
|
||||||
The index of the download to cancel.
|
|
||||||
|
|
||||||
[[close]]
|
[[close]]
|
||||||
=== close
|
=== close
|
||||||
Close the current window.
|
Close the current window.
|
||||||
|
|
||||||
[[download]]
|
[[download]]
|
||||||
=== download
|
=== download
|
||||||
Syntax: +:download 'url' ['dest']+
|
Syntax: +:download ['url'] ['dest']+
|
||||||
|
|
||||||
Download a given URL, given as string.
|
Download a given URL, or current page if no URL given.
|
||||||
|
|
||||||
==== positional arguments
|
==== positional arguments
|
||||||
* +'url'+: The URL to download
|
* +'url'+: The URL to download. If not given, download the current page.
|
||||||
* +'dest'+: The file path to write the download to to ask.
|
* +'dest'+: The file path to write the download to to ask.
|
||||||
|
|
||||||
[[download-page]]
|
[[download-cancel]]
|
||||||
=== download-page
|
=== download-cancel
|
||||||
Download the current page.
|
Cancel the last/[count]th download.
|
||||||
|
|
||||||
|
==== count
|
||||||
|
The index of the download to cancel.
|
||||||
|
|
||||||
|
[[download-delete]]
|
||||||
|
=== download-delete
|
||||||
|
Delete the last/[count]th download from disk.
|
||||||
|
|
||||||
|
==== count
|
||||||
|
The index of the download to cancel.
|
||||||
|
|
||||||
|
[[download-open]]
|
||||||
|
=== download-open
|
||||||
|
Open the last/[count]th download.
|
||||||
|
|
||||||
|
==== count
|
||||||
|
The index of the download to cancel.
|
||||||
|
|
||||||
|
[[download-remove]]
|
||||||
|
=== download-remove
|
||||||
|
Syntax: +:download-remove [*--all*]+
|
||||||
|
|
||||||
|
Remove the last/[count]th download from the list.
|
||||||
|
|
||||||
|
==== optional arguments
|
||||||
|
* +*-a*+, +*--all*+: If given removes all finished downloads.
|
||||||
|
|
||||||
|
==== count
|
||||||
|
The index of the download to cancel.
|
||||||
|
|
||||||
[[forward]]
|
[[forward]]
|
||||||
=== forward
|
=== forward
|
||||||
|
@ -912,13 +912,29 @@ class CommandDispatcher:
|
|||||||
cur.inspector.show()
|
cur.inspector.show()
|
||||||
|
|
||||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||||
def download_page(self):
|
def download(self, url=None, dest=None):
|
||||||
"""Download the current page."""
|
"""Download a given URL, or current page if no URL given.
|
||||||
page = self._current_widget().page()
|
|
||||||
|
Args:
|
||||||
|
url: The URL to download. If not given, download the current page.
|
||||||
|
dest: The file path to write the download to, or None to ask.
|
||||||
|
"""
|
||||||
download_manager = objreg.get('download-manager', scope='window',
|
download_manager = objreg.get('download-manager', scope='window',
|
||||||
window=self._win_id)
|
window=self._win_id)
|
||||||
|
if url:
|
||||||
|
url = urlutils.qurl_from_user_input(url)
|
||||||
|
urlutils.raise_cmdexc_if_invalid(url)
|
||||||
|
download_manager.get(url, filename=dest)
|
||||||
|
else:
|
||||||
|
page = self._current_widget().page()
|
||||||
download_manager.get(self._current_url(), page)
|
download_manager.get(self._current_url(), page)
|
||||||
|
|
||||||
|
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||||
|
deprecated="Use :download instead.")
|
||||||
|
def download_page(self):
|
||||||
|
"""Download the current page."""
|
||||||
|
self.download()
|
||||||
|
|
||||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||||
def view_source(self):
|
def view_source(self):
|
||||||
"""Show the source of the current page."""
|
"""Show the source of the current page."""
|
||||||
|
@ -159,6 +159,7 @@ class DownloadItem(QObject):
|
|||||||
Attributes:
|
Attributes:
|
||||||
done: Whether the download is finished.
|
done: Whether the download is finished.
|
||||||
stats: A DownloadItemStats object.
|
stats: A DownloadItemStats object.
|
||||||
|
index: The index of the download in the view.
|
||||||
successful: Whether the download has completed sucessfully.
|
successful: Whether the download has completed sucessfully.
|
||||||
error_msg: The current error message, or None
|
error_msg: The current error message, or None
|
||||||
autoclose: Whether to close the associated file if the download is
|
autoclose: Whether to close the associated file if the download is
|
||||||
@ -206,6 +207,7 @@ class DownloadItem(QObject):
|
|||||||
self.done = False
|
self.done = False
|
||||||
self.stats = DownloadItemStats(self)
|
self.stats = DownloadItemStats(self)
|
||||||
self.stats.updated.connect(self.data_changed)
|
self.stats.updated.connect(self.data_changed)
|
||||||
|
self.index = 0
|
||||||
self.autoclose = True
|
self.autoclose = True
|
||||||
self.reply = None
|
self.reply = None
|
||||||
self._buffer = io.BytesIO()
|
self._buffer = io.BytesIO()
|
||||||
@ -238,8 +240,9 @@ class DownloadItem(QObject):
|
|||||||
else:
|
else:
|
||||||
errmsg = " - {}".format(self.error_msg)
|
errmsg = " - {}".format(self.error_msg)
|
||||||
if all(e is None for e in (perc, remaining, self.stats.total)):
|
if all(e is None for e in (perc, remaining, self.stats.total)):
|
||||||
return ('{name} [{speed:>10}|{down}]{errmsg}'.format(
|
return ('{index}: {name} [{speed:>10}|{down}]{errmsg}'.format(
|
||||||
name=self.basename, speed=speed, down=down, errmsg=errmsg))
|
index=self.index, name=self.basename, speed=speed,
|
||||||
|
down=down, errmsg=errmsg))
|
||||||
if perc is None:
|
if perc is None:
|
||||||
perc = '??'
|
perc = '??'
|
||||||
else:
|
else:
|
||||||
@ -250,14 +253,15 @@ class DownloadItem(QObject):
|
|||||||
remaining = utils.format_seconds(remaining)
|
remaining = utils.format_seconds(remaining)
|
||||||
total = utils.format_size(self.stats.total, suffix='B')
|
total = utils.format_size(self.stats.total, suffix='B')
|
||||||
if self.done:
|
if self.done:
|
||||||
return ('{name} [{perc:>2}%|{total}]{errmsg}'.format(
|
return ('{index}: {name} [{perc:>2}%|{total}]{errmsg}'.format(
|
||||||
name=self.basename, perc=perc, total=total,
|
index=self.index, name=self.basename, perc=perc,
|
||||||
errmsg=errmsg))
|
total=total, errmsg=errmsg))
|
||||||
else:
|
else:
|
||||||
return ('{name} [{speed:>10}|{remaining:>5}|{perc:>2}%|'
|
return ('{index}: {name} [{speed:>10}|{remaining:>5}|{perc:>2}%|'
|
||||||
'{down}/{total}]{errmsg}'.format(
|
'{down}/{total}]{errmsg}'.format(
|
||||||
name=self.basename, speed=speed, remaining=remaining,
|
index=self.index, name=self.basename, speed=speed,
|
||||||
perc=perc, down=down, total=total, errmsg=errmsg))
|
remaining=remaining, perc=perc, down=down,
|
||||||
|
total=total, errmsg=errmsg))
|
||||||
|
|
||||||
def _create_fileobj(self):
|
def _create_fileobj(self):
|
||||||
"""Creates a file object using the internal filename."""
|
"""Creates a file object using the internal filename."""
|
||||||
@ -354,16 +358,20 @@ class DownloadItem(QObject):
|
|||||||
self.reply = None
|
self.reply = None
|
||||||
if self.fileobj is not None:
|
if self.fileobj is not None:
|
||||||
self.fileobj.close()
|
self.fileobj.close()
|
||||||
try:
|
if remove_data:
|
||||||
if (self._filename is not None and os.path.exists(self._filename)
|
self.delete()
|
||||||
and remove_data):
|
|
||||||
os.remove(self._filename)
|
|
||||||
except OSError:
|
|
||||||
log.downloads.exception("Failed to remove partial file")
|
|
||||||
self.done = True
|
self.done = True
|
||||||
self.finished.emit()
|
self.finished.emit()
|
||||||
self.data_changed.emit()
|
self.data_changed.emit()
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
"""Delete the downloaded file"""
|
||||||
|
try:
|
||||||
|
if self._filename is not None and os.path.exists(self._filename):
|
||||||
|
os.remove(self._filename)
|
||||||
|
except OSError:
|
||||||
|
log.downloads.exception("Failed to remove partial file")
|
||||||
|
|
||||||
def retry(self):
|
def retry(self):
|
||||||
"""Retry a failed download."""
|
"""Retry a failed download."""
|
||||||
self.cancel()
|
self.cancel()
|
||||||
@ -580,18 +588,6 @@ class DownloadManager(QAbstractListModel):
|
|||||||
self.questions.append(q)
|
self.questions.append(q)
|
||||||
return q
|
return q
|
||||||
|
|
||||||
@cmdutils.register(instance='download-manager', scope='window')
|
|
||||||
def download(self, url, dest=None):
|
|
||||||
"""Download a given URL, given as string.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
url: The URL to download
|
|
||||||
dest: The file path to write the download to, or None to ask.
|
|
||||||
"""
|
|
||||||
url = urlutils.qurl_from_user_input(url)
|
|
||||||
urlutils.raise_cmdexc_if_invalid(url)
|
|
||||||
self.get(url, filename=dest)
|
|
||||||
|
|
||||||
@pyqtSlot('QUrl', 'QWebPage')
|
@pyqtSlot('QUrl', 'QWebPage')
|
||||||
def get(self, url, page=None, fileobj=None, filename=None,
|
def get(self, url, page=None, fileobj=None, filename=None,
|
||||||
auto_remove=False):
|
auto_remove=False):
|
||||||
@ -644,7 +640,7 @@ class DownloadManager(QAbstractListModel):
|
|||||||
request.setAttribute(QNetworkRequest.CacheLoadControlAttribute,
|
request.setAttribute(QNetworkRequest.CacheLoadControlAttribute,
|
||||||
QNetworkRequest.AlwaysNetwork)
|
QNetworkRequest.AlwaysNetwork)
|
||||||
if fileobj is not None or filename is not None:
|
if fileobj is not None or filename is not None:
|
||||||
return self.fetch_request(request, filename, fileobj, page,
|
return self.fetch_request(request, page, fileobj, filename,
|
||||||
auto_remove)
|
auto_remove)
|
||||||
q = self._prepare_question()
|
q = self._prepare_question()
|
||||||
filename = urlutils.filename_from_url(request.url())
|
filename = urlutils.filename_from_url(request.url())
|
||||||
@ -719,6 +715,7 @@ class DownloadManager(QAbstractListModel):
|
|||||||
download.do_retry.connect(self.fetch)
|
download.do_retry.connect(self.fetch)
|
||||||
download.basename = suggested_filename
|
download.basename = suggested_filename
|
||||||
idx = len(self.downloads) + 1
|
idx = len(self.downloads) + 1
|
||||||
|
download.index = idx
|
||||||
self.beginInsertRows(QModelIndex(), idx, idx)
|
self.beginInsertRows(QModelIndex(), idx, idx)
|
||||||
self.downloads.append(download)
|
self.downloads.append(download)
|
||||||
self.endInsertRows()
|
self.endInsertRows()
|
||||||
@ -744,20 +741,78 @@ class DownloadManager(QAbstractListModel):
|
|||||||
|
|
||||||
return download
|
return download
|
||||||
|
|
||||||
|
def raise_no_download(self, count):
|
||||||
|
"""Raise an exception that the download doesn't exist
|
||||||
|
|
||||||
|
Args:
|
||||||
|
count: The index of the download
|
||||||
|
"""
|
||||||
|
if not count:
|
||||||
|
raise cmdexc.CommandError("There's no download!")
|
||||||
|
raise cmdexc.CommandError("There's no download {}!".format(count))
|
||||||
|
|
||||||
@cmdutils.register(instance='download-manager', scope='window')
|
@cmdutils.register(instance='download-manager', scope='window')
|
||||||
|
def download_cancel(self, count: {'special': 'count'}=0):
|
||||||
|
"""Cancel the last/[count]th download.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
count: The index of the download to cancel.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
download = self.downloads[count - 1]
|
||||||
|
except IndexError:
|
||||||
|
self.raise_no_download(count)
|
||||||
|
if download.done:
|
||||||
|
if not count:
|
||||||
|
count = len(self.downloads)
|
||||||
|
raise cmdexc.CommandError("Download {} is already done!"
|
||||||
|
.format(count))
|
||||||
|
download.cancel()
|
||||||
|
|
||||||
|
@cmdutils.register(instance='download-manager', scope='window')
|
||||||
|
def download_delete(self, count: {'special': 'count'}=0):
|
||||||
|
"""Delete the last/[count]th download from disk.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
count: The index of the download to cancel.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
download = self.downloads[count - 1]
|
||||||
|
except IndexError:
|
||||||
|
self.raise_no_download(count)
|
||||||
|
if not download.successful:
|
||||||
|
if not count:
|
||||||
|
count = len(self.downloads)
|
||||||
|
raise cmdexc.CommandError("Download {} is not done!".format(count))
|
||||||
|
download.delete()
|
||||||
|
self.remove_item(download)
|
||||||
|
|
||||||
|
@cmdutils.register(instance='download-manager', scope='window',
|
||||||
|
deprecated="Use :download instead.")
|
||||||
def cancel_download(self, count: {'special': 'count'}=1):
|
def cancel_download(self, count: {'special': 'count'}=1):
|
||||||
"""Cancel the first/[count]th download.
|
"""Cancel the first/[count]th download.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
count: The index of the download to cancel.
|
count: The index of the download to cancel.
|
||||||
"""
|
"""
|
||||||
if count == 0:
|
self.download_cancel(count)
|
||||||
return
|
|
||||||
|
@cmdutils.register(instance='download-manager', scope='window')
|
||||||
|
def download_open(self, count: {'special': 'count'}=0):
|
||||||
|
"""Open the last/[count]th download.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
count: The index of the download to cancel.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
download = self.downloads[count - 1]
|
download = self.downloads[count - 1]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
raise cmdexc.CommandError("There's no download {}!".format(count))
|
self.raise_no_download(count)
|
||||||
download.cancel()
|
if not download.successful:
|
||||||
|
if not count:
|
||||||
|
count = len(self.downloads)
|
||||||
|
raise cmdexc.CommandError("Download {} is not done!".format(count))
|
||||||
|
download.open_file()
|
||||||
|
|
||||||
@pyqtSlot(QNetworkRequest, QNetworkReply)
|
@pyqtSlot(QNetworkRequest, QNetworkReply)
|
||||||
def on_redirect(self, download, request, reply):
|
def on_redirect(self, download, request, reply):
|
||||||
@ -815,14 +870,31 @@ class DownloadManager(QAbstractListModel):
|
|||||||
|
|
||||||
def can_clear(self):
|
def can_clear(self):
|
||||||
"""Check if there are finished downloads to clear."""
|
"""Check if there are finished downloads to clear."""
|
||||||
if self.downloads:
|
|
||||||
return any(download.done for download in self.downloads)
|
return any(download.done for download in self.downloads)
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def clear(self):
|
@cmdutils.register(instance='download-manager', scope='window')
|
||||||
"""Remove all finished downloads."""
|
def download_remove(self, all_: {'name': 'all'}=False,
|
||||||
self.remove_items(d for d in self.downloads if d.done)
|
count: {'special': 'count'}=0):
|
||||||
|
"""Remove the last/[count]th download from the list.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
all_: If given removes all finished downloads.
|
||||||
|
count: The index of the download to cancel.
|
||||||
|
"""
|
||||||
|
if all_:
|
||||||
|
finished_items = [d for d in self.downloads if d.done]
|
||||||
|
self.remove_items(finished_items)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
download = self.downloads[count - 1]
|
||||||
|
except IndexError:
|
||||||
|
self.raise_no_download(count)
|
||||||
|
if not download.done:
|
||||||
|
if not count:
|
||||||
|
count = len(self.downloads)
|
||||||
|
raise cmdexc.CommandError("Download {} is not done!"
|
||||||
|
.format(count))
|
||||||
|
self.remove_item(download)
|
||||||
|
|
||||||
def last_index(self):
|
def last_index(self):
|
||||||
"""Get the last index in the model.
|
"""Get the last index in the model.
|
||||||
@ -844,6 +916,7 @@ class DownloadManager(QAbstractListModel):
|
|||||||
del self.downloads[idx]
|
del self.downloads[idx]
|
||||||
self.endRemoveRows()
|
self.endRemoveRows()
|
||||||
download.deleteLater()
|
download.deleteLater()
|
||||||
|
self.update_indexes()
|
||||||
|
|
||||||
def remove_items(self, downloads):
|
def remove_items(self, downloads):
|
||||||
"""Remove an iterable of downloads."""
|
"""Remove an iterable of downloads."""
|
||||||
@ -873,6 +946,18 @@ class DownloadManager(QAbstractListModel):
|
|||||||
download.deleteLater()
|
download.deleteLater()
|
||||||
self.endRemoveRows()
|
self.endRemoveRows()
|
||||||
|
|
||||||
|
def update_indexes(self):
|
||||||
|
"""Update indexes of all DownloadItems"""
|
||||||
|
first_idx = None
|
||||||
|
for i, d in enumerate(self.downloads, 1):
|
||||||
|
if first_idx is None and d.index != i:
|
||||||
|
first_idx = i - 1
|
||||||
|
d.index = i
|
||||||
|
if first_idx is not None:
|
||||||
|
model_idx = self.index(first_idx, 0)
|
||||||
|
qtutils.ensure_valid(model_idx)
|
||||||
|
self.dataChanged.emit(model_idx, self.last_index())
|
||||||
|
|
||||||
def headerData(self, section, orientation, role):
|
def headerData(self, section, orientation, role):
|
||||||
"""Simple constant header."""
|
"""Simple constant header."""
|
||||||
if (section == 0 and orientation == Qt.Horizontal and
|
if (section == 0 and orientation == Qt.Horizontal and
|
||||||
|
@ -142,7 +142,8 @@ class DownloadView(QListView):
|
|||||||
actions.append(("Cancel", item.cancel))
|
actions.append(("Cancel", item.cancel))
|
||||||
if self.model().can_clear():
|
if self.model().can_clear():
|
||||||
actions.append((None, None))
|
actions.append((None, None))
|
||||||
actions.append(("Remove all finished", self.model().clear))
|
actions.append(("Remove all finished", functools.partial(
|
||||||
|
self.model().download_remove, True)))
|
||||||
return actions
|
return actions
|
||||||
|
|
||||||
@pyqtSlot('QPoint')
|
@pyqtSlot('QPoint')
|
||||||
|
@ -1055,8 +1055,8 @@ KEY_DATA = collections.OrderedDict([
|
|||||||
('navigate increment', ['<Ctrl-A>']),
|
('navigate increment', ['<Ctrl-A>']),
|
||||||
('navigate decrement', ['<Ctrl-X>']),
|
('navigate decrement', ['<Ctrl-X>']),
|
||||||
('inspector', ['wi']),
|
('inspector', ['wi']),
|
||||||
('download-page', ['gd']),
|
('download', ['gd']),
|
||||||
('cancel-download', ['ad']),
|
('download-cancel', ['ad']),
|
||||||
('view-source', ['gf']),
|
('view-source', ['gf']),
|
||||||
('tab-focus last', ['<Ctrl-Tab>']),
|
('tab-focus last', ['<Ctrl-Tab>']),
|
||||||
('enter-mode passthrough', ['<Ctrl-V>']),
|
('enter-mode passthrough', ['<Ctrl-V>']),
|
||||||
|
Loading…
Reference in New Issue
Block a user