summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Bruhin <git@the-compiler.org>2016-07-26 10:54:59 +0200
committerFlorian Bruhin <git@the-compiler.org>2016-07-26 10:54:59 +0200
commitd70f3a041738e2c51d870e30e5dded6b7863b023 (patch)
tree49672fa74852b6567fe143058f9d5231c9380cc1
parent36b005423820e561c2a2c7d89d23a26e0b09f2c3 (diff)
parentd5cf8ef8948007b61a4afdb074afde823cf283b8 (diff)
downloadqutebrowser-d70f3a041738e2c51d870e30e5dded6b7863b023.tar.gz
qutebrowser-d70f3a041738e2c51d870e30e5dded6b7863b023.zip
Merge branch 'Kingdread-open-download'
-rw-r--r--CHANGELOG.asciidoc2
-rw-r--r--doc/help/commands.asciidoc5
-rw-r--r--qutebrowser/app.py6
-rw-r--r--qutebrowser/browser/adblock.py5
-rw-r--r--qutebrowser/browser/commands.py12
-rw-r--r--qutebrowser/browser/webkit/downloads.py187
-rw-r--r--qutebrowser/browser/webkit/mhtml.py3
-rw-r--r--qutebrowser/config/configdata.py1
-rw-r--r--qutebrowser/mainwindow/statusbar/prompter.py29
-rw-r--r--qutebrowser/utils/usertypes.py47
-rw-r--r--scripts/dev/misc_checks.py4
-rw-r--r--tests/end2end/features/downloads.feature7
-rw-r--r--tests/end2end/features/misc.feature2
-rw-r--r--tests/end2end/features/test_downloads_bdd.py2
-rw-r--r--tests/unit/browser/test_adblock.py4
-rw-r--r--tests/unit/utils/usertypes/test_downloadtarget.py54
16 files changed, 289 insertions, 81 deletions
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index f0bd07795..55c928a09 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -32,6 +32,8 @@ Added
Note that two former default bundings conflict with that binding, unbinding
them via `:unbind .i` and `:unbind .o` is recommended.
- New `qute:bookmarks` page which displays all bookmarks and quickmarks.
+- New `:prompt-open-download` (bound to `Ctrl-X`) which can be used to open a
+ download directly when getting the filename prompt.
Changed
~~~~~~~
diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc
index eea3f0d7f..af9089135 100644
--- a/doc/help/commands.asciidoc
+++ b/doc/help/commands.asciidoc
@@ -936,6 +936,7 @@ How many steps to zoom out.
|<<paste-primary,paste-primary>>|Paste the primary selection at cursor position.
|<<prompt-accept,prompt-accept>>|Accept the current prompt.
|<<prompt-no,prompt-no>>|Answer no to a yes/no prompt.
+|<<prompt-open-download,prompt-open-download>>|Immediately open a download.
|<<prompt-yes,prompt-yes>>|Answer yes to a yes/no prompt.
|<<repeat-command,repeat-command>>|Repeat the last executed command.
|<<rl-backward-char,rl-backward-char>>|Move back a character.
@@ -1160,6 +1161,10 @@ Accept the current prompt.
=== prompt-no
Answer no to a yes/no prompt.
+[[prompt-open-download]]
+=== prompt-open-download
+Immediately open a download.
+
[[prompt-yes]]
=== prompt-yes
Answer yes to a yes/no prompt.
diff --git a/qutebrowser/app.py b/qutebrowser/app.py
index f691595d4..6d7b6a3ea 100644
--- a/qutebrowser/app.py
+++ b/qutebrowser/app.py
@@ -47,7 +47,7 @@ from qutebrowser.completion.models import instances as completionmodels
from qutebrowser.commands import cmdutils, runners, cmdexc
from qutebrowser.config import style, config, websettings, configexc
from qutebrowser.browser import urlmarks, adblock
-from qutebrowser.browser.webkit import cookies, cache, history
+from qutebrowser.browser.webkit import cookies, cache, history, downloads
from qutebrowser.browser.webkit.network import (qutescheme, proxy,
networkmanager)
from qutebrowser.mainwindow import mainwindow
@@ -436,6 +436,8 @@ def _init_modules(args, crash_handler):
os.environ.pop('QT_WAYLAND_DISABLE_WINDOWDECORATION', None)
_maybe_hide_mouse_cursor()
objreg.get('config').changed.connect(_maybe_hide_mouse_cursor)
+ temp_downloads = downloads.TempDownloadManager(qApp)
+ objreg.register('temporary-downloads', temp_downloads)
def _init_late_modules(args):
@@ -708,6 +710,8 @@ class Quitter:
not restart):
atexit.register(shutil.rmtree, self._args.basedir,
ignore_errors=True)
+ # Delete temp download dir
+ objreg.get('temporary-downloads').cleanup()
# If we don't kill our custom handler here we might get segfaults
log.destroy.debug("Deactivating message handler...")
qInstallMessageHandler(None)
diff --git a/qutebrowser/browser/adblock.py b/qutebrowser/browser/adblock.py
index 9fc09bd7b..afdc9922e 100644
--- a/qutebrowser/browser/adblock.py
+++ b/qutebrowser/browser/adblock.py
@@ -27,7 +27,7 @@ import zipfile
import fnmatch
from qutebrowser.config import config
-from qutebrowser.utils import objreg, standarddir, log, message
+from qutebrowser.utils import objreg, standarddir, log, message, usertypes
from qutebrowser.commands import cmdutils, cmdexc
@@ -210,7 +210,8 @@ class HostBlocker:
else:
fobj = io.BytesIO()
fobj.name = 'adblock: ' + url.host()
- download = download_manager.get(url, fileobj=fobj,
+ target = usertypes.FileObjDownloadTarget(fobj)
+ download = download_manager.get(url, target=target,
auto_remove=True)
self._in_progress.append(download)
download.finished.connect(
diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py
index 49eaa5dcb..503a9cfd9 100644
--- a/qutebrowser/browser/commands.py
+++ b/qutebrowser/browser/commands.py
@@ -1267,15 +1267,23 @@ class CommandDispatcher:
" as mhtml.")
url = urlutils.qurl_from_user_input(url)
urlutils.raise_cmdexc_if_invalid(url)
- download_manager.get(url, filename=dest)
+ if dest is None:
+ target = None
+ else:
+ target = usertypes.FileDownloadTarget(dest)
+ download_manager.get(url, target=target)
elif mhtml_:
self._download_mhtml(dest)
else:
# FIXME:qtwebengine have a proper API for this
tab = self._current_widget()
page = tab._widget.page() # pylint: disable=protected-access
+ if dest is None:
+ target = None
+ else:
+ target = usertypes.FileDownloadTarget(dest)
download_manager.get(self._current_url(), page=page,
- filename=dest)
+ target=target)
def _download_mhtml(self, dest=None):
"""Download the current page as an MHTML file, including all assets.
diff --git a/qutebrowser/browser/webkit/downloads.py b/qutebrowser/browser/webkit/downloads.py
index 0673ed43f..87e038841 100644
--- a/qutebrowser/browser/webkit/downloads.py
+++ b/qutebrowser/browser/webkit/downloads.py
@@ -25,6 +25,7 @@ import sys
import os.path
import shutil
import functools
+import tempfile
import collections
import sip
@@ -280,6 +281,7 @@ class DownloadItem(QObject):
_read_timer: A Timer which reads the QNetworkReply into self._buffer
periodically.
_win_id: The window ID the DownloadItem runs in.
+ _dead: Whether the Download has _die()'d.
Signals:
data_changed: The downloads metadata changed.
@@ -328,6 +330,7 @@ class DownloadItem(QObject):
self.init_reply(reply)
self._win_id = win_id
self.raw_headers = {}
+ self._dead = False
def __repr__(self):
return utils.get_repr(self, basename=self.basename)
@@ -395,6 +398,21 @@ class DownloadItem(QObject):
def _die(self, msg):
"""Abort the download and emit an error."""
assert not self.successful
+ # Prevent actions if calling _die() twice. This might happen if the
+ # error handler correctly connects, and the error occurs in init_reply
+ # between reply.error.connect and the reply.error() check. In this
+ # case, the connected error handlers will be called twice, once via the
+ # direct error.emit() and once here in _die(). The stacks look like
+ # this then:
+ # <networkmanager error.emit> -> on_reply_error -> _die ->
+ # self.error.emit()
+ # and
+ # [init_reply -> <single shot timer> ->] <lambda in init_reply> ->
+ # self.error.emit()
+ # which may lead to duplicate error messages (and failing tests)
+ if self._dead:
+ return
+ self._dead = True
self._read_timer.stop()
self.reply.downloadProgress.disconnect()
self.reply.finished.disconnect()
@@ -441,7 +459,7 @@ class DownloadItem(QObject):
# Here no signals are connected to the DownloadItem yet, so we use a
# singleShot QTimer to emit them after they are connected.
if reply.error() != QNetworkReply.NoError:
- QTimer.singleShot(0, lambda: self.error.emit(reply.errorString()))
+ QTimer.singleShot(0, lambda: self._die(reply.errorString()))
def get_status_color(self, position):
"""Choose an appropriate color for presenting the download's status.
@@ -513,7 +531,13 @@ class DownloadItem(QObject):
def open_file(self):
"""Open the downloaded file."""
assert self.successful
- url = QUrl.fromLocalFile(self._filename)
+ filename = self._filename
+ if filename is None:
+ filename = getattr(self.fileobj, 'name', None)
+ if filename is None:
+ log.downloads.error("No filename to open the download!")
+ return
+ url = QUrl.fromLocalFile(filename)
QDesktopServices.openUrl(url)
def set_filename(self, filename):
@@ -738,6 +762,9 @@ class DownloadManager(QAbstractListModel):
def _postprocess_question(self, q):
"""Postprocess a Question object that is asked."""
q.destroyed.connect(functools.partial(self.questions.remove, q))
+ # We set the mode here so that other code that uses ask_for_filename
+ # doesn't need to handle the special download mode.
+ q.mode = usertypes.PromptMode.download
self.questions.append(q)
@pyqtSlot()
@@ -757,10 +784,7 @@ class DownloadManager(QAbstractListModel):
**kwargs: passed to get_request().
Return:
- If the download could start immediately, (fileobj/filename given),
- the created DownloadItem.
-
- If not, None.
+ The created DownloadItem.
"""
if not url.isValid():
urlutils.invalid_url_error(self._win_id, url, "start download")
@@ -768,27 +792,17 @@ class DownloadManager(QAbstractListModel):
req = QNetworkRequest(url)
return self.get_request(req, **kwargs)
- def get_request(self, request, *, fileobj=None, filename=None,
- prompt_download_directory=None, **kwargs):
+ def get_request(self, request, *, target=None, **kwargs):
"""Start a download with a QNetworkRequest.
Args:
request: The QNetworkRequest to download.
- fileobj: The file object to write the answer to.
- filename: A path to write the data to.
- prompt_download_directory: Whether to prompt for the download dir
- or automatically download. If None, the
- config is used.
+ target: Where to save the download as usertypes.DownloadTarget.
**kwargs: Passed to fetch_request.
Return:
- If the download could start immediately, (fileobj/filename given),
- the created DownloadItem.
-
- If not, None.
+ The created DownloadItem.
"""
- if fileobj is not None and filename is not None: # pragma: no cover
- raise TypeError("Only one of fileobj/filename may be given!")
# WORKAROUND for Qt corrupting data loaded from cache:
# https://bugreports.qt.io/browse/QTBUG-42757
request.setAttribute(QNetworkRequest.CacheLoadControlAttribute,
@@ -816,27 +830,10 @@ class DownloadManager(QAbstractListModel):
if suggested_fn is None:
suggested_fn = 'qutebrowser-download'
- # We won't need a question if a filename or fileobj is already given
- if fileobj is None and filename is None:
- filename, q = ask_for_filename(
- suggested_fn, self._win_id, parent=self,
- prompt_download_directory=prompt_download_directory
- )
-
- if fileobj is not None or filename is not None:
- return self.fetch_request(request,
- fileobj=fileobj,
- filename=filename,
- suggested_filename=suggested_fn,
- **kwargs)
- q.answered.connect(
- lambda fn: self.fetch_request(request,
- filename=fn,
- suggested_filename=suggested_fn,
- **kwargs))
- self._postprocess_question(q)
- q.ask()
- return None
+ return self.fetch_request(request,
+ target=target,
+ suggested_filename=suggested_fn,
+ **kwargs)
def fetch_request(self, request, *, page=None, **kwargs):
"""Download a QNetworkRequest to disk.
@@ -857,27 +854,25 @@ class DownloadManager(QAbstractListModel):
return self.fetch(reply, **kwargs)
@pyqtSlot('QNetworkReply')
- def fetch(self, reply, *, fileobj=None, filename=None, auto_remove=False,
+ def fetch(self, reply, *, target=None, auto_remove=False,
suggested_filename=None, prompt_download_directory=None):
"""Download a QNetworkReply to disk.
Args:
reply: The QNetworkReply to download.
- fileobj: The file object to write the answer to.
- filename: A path to write the data to.
+ target: Where to save the download as usertypes.DownloadTarget.
auto_remove: Whether to remove the download even if
ui -> remove-finished-downloads is set to -1.
Return:
The created DownloadItem.
"""
- if fileobj is not None and filename is not None: # pragma: no cover
- raise TypeError("Only one of fileobj/filename may be given!")
if not suggested_filename:
- if filename is not None:
- suggested_filename = os.path.basename(filename)
- elif fileobj is not None and getattr(fileobj, 'name', None):
- suggested_filename = fileobj.name
+ if isinstance(target, usertypes.FileDownloadTarget):
+ suggested_filename = os.path.basename(target.filename)
+ elif (isinstance(target, usertypes.FileObjDownloadTarget) and
+ getattr(target.fileobj, 'name', None)):
+ suggested_filename = target.fileobj.name
else:
_, suggested_filename = http.parse_content_disposition(reply)
log.downloads.debug("fetch: {} -> {}".format(reply.url(),
@@ -909,13 +904,8 @@ class DownloadManager(QAbstractListModel):
if not self._update_timer.isActive():
self._update_timer.start()
- if fileobj is not None:
- download.set_fileobj(fileobj)
- download.autoclose = False
- return download
-
- if filename is not None:
- download.set_filename(filename)
+ if target is not None:
+ self._set_download_target(download, suggested_filename, target)
return download
# Neither filename nor fileobj were given, prepare a question
@@ -926,12 +916,15 @@ class DownloadManager(QAbstractListModel):
# User doesn't want to be asked, so just use the download_dir
if filename is not None:
- download.set_filename(filename)
+ target = usertypes.FileDownloadTarget(filename)
+ self._set_download_target(download, suggested_filename, target)
return download
# Ask the user for a filename
self._postprocess_question(q)
- q.answered.connect(download.set_filename)
+ q.answered.connect(
+ functools.partial(self._set_download_target, download,
+ suggested_filename))
q.cancelled.connect(download.cancel)
download.cancelled.connect(q.abort)
download.error.connect(q.abort)
@@ -939,6 +932,28 @@ class DownloadManager(QAbstractListModel):
return download
+ def _set_download_target(self, download, suggested_filename, target):
+ """Set the target for a given download.
+
+ Args:
+ download: The download to set the filename for.
+ suggested_filename: The suggested filename.
+ target: The usertypes.DownloadTarget for this download.
+ """
+ if isinstance(target, usertypes.FileObjDownloadTarget):
+ download.set_fileobj(target.fileobj)
+ download.autoclose = False
+ elif isinstance(target, usertypes.FileDownloadTarget):
+ download.set_filename(target.filename)
+ elif isinstance(target, usertypes.OpenFileDownloadTarget):
+ tmp_manager = objreg.get('temporary-downloads')
+ fobj = tmp_manager.get_tmpfile(suggested_filename)
+ download.finished.connect(download.open_file)
+ download.autoclose = True
+ download.set_fileobj(fobj)
+ else:
+ log.downloads.error("Unknown download target: {}".format(target))
+
def raise_no_download(self, count):
"""Raise an exception that the download doesn't exist.
@@ -1249,3 +1264,59 @@ class DownloadManager(QAbstractListModel):
The number of unfinished downloads.
"""
return sum(1 for download in self.downloads if not download.done)
+
+
+class TempDownloadManager(QObject):
+
+ """Manager to handle temporary download files.
+
+ The downloads are downloaded to a temporary location and then openened with
+ the system standard application. The temporary files are deleted when
+ qutebrowser is shutdown.
+
+ Attributes:
+ files: A list of NamedTemporaryFiles of downloaded items.
+ """
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.files = []
+ self._tmpdir = None
+
+ def cleanup(self):
+ """Clean up any temporary files."""
+ if self._tmpdir is not None:
+ self._tmpdir.cleanup()
+ self._tmpdir = None
+
+ def _get_tmpdir(self):
+ """Return the temporary directory that is used for downloads.
+
+ The directory is created lazily on first access.
+
+ Return:
+ The tempfile.TemporaryDirectory that is used.
+ """
+ if self._tmpdir is None:
+ self._tmpdir = tempfile.TemporaryDirectory(
+ prefix='qutebrowser-downloads-')
+ return self._tmpdir
+
+ def get_tmpfile(self, suggested_name):
+ """Return a temporary file in the temporary downloads directory.
+
+ The files are kept as long as qutebrowser is running and automatically
+ cleaned up at program exit.
+
+ Args:
+ suggested_name: str of the "suggested"/original filename. Used as a
+ suffix, so any file extenions are preserved.
+
+ Return:
+ A tempfile.NamedTemporaryFile that should be used to save the file.
+ """
+ tmpdir = self._get_tmpdir()
+ fobj = tempfile.NamedTemporaryFile(dir=tmpdir.name, delete=False,
+ suffix=suggested_name)
+ self.files.append(fobj)
+ return fobj
diff --git a/qutebrowser/browser/webkit/mhtml.py b/qutebrowser/browser/webkit/mhtml.py
index c402b3041..8a157fa03 100644
--- a/qutebrowser/browser/webkit/mhtml.py
+++ b/qutebrowser/browser/webkit/mhtml.py
@@ -343,7 +343,8 @@ class _Downloader:
download_manager = objreg.get('download-manager', scope='window',
window=self._win_id)
- item = download_manager.get(url, fileobj=_NoCloseBytesIO(),
+ target = usertypes.FileObjDownloadTarget(_NoCloseBytesIO())
+ item = download_manager.get(url, target=target,
auto_remove=True)
self.pending_downloads.add((url, item))
item.finished.connect(functools.partial(self._finished, url, item))
diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py
index c28244358..5110a6090 100644
--- a/qutebrowser/config/configdata.py
+++ b/qutebrowser/config/configdata.py
@@ -1574,6 +1574,7 @@ KEY_DATA = collections.OrderedDict([
('prompt-accept', RETURN_KEYS),
('prompt-yes', ['y']),
('prompt-no', ['n']),
+ ('prompt-open-download', ['<Ctrl-X>']),
])),
('command,prompt', collections.OrderedDict([
diff --git a/qutebrowser/mainwindow/statusbar/prompter.py b/qutebrowser/mainwindow/statusbar/prompter.py
index 6d60156d6..d67c843ee 100644
--- a/qutebrowser/mainwindow/statusbar/prompter.py
+++ b/qutebrowser/mainwindow/statusbar/prompter.py
@@ -80,6 +80,7 @@ class Prompter(QObject):
usertypes.PromptMode.text: usertypes.KeyMode.prompt,
usertypes.PromptMode.user_pwd: usertypes.KeyMode.prompt,
usertypes.PromptMode.alert: usertypes.KeyMode.prompt,
+ usertypes.PromptMode.download: usertypes.KeyMode.prompt,
}
show_prompt = pyqtSignal()
@@ -164,12 +165,9 @@ class Prompter(QObject):
suffix = " (no)"
prompt.txt.setText(self._question.text + suffix)
prompt.lineedit.hide()
- elif self._question.mode == usertypes.PromptMode.text:
- prompt.txt.setText(self._question.text)
- if self._question.default:
- prompt.lineedit.setText(self._question.default)
- prompt.lineedit.show()
- elif self._question.mode == usertypes.PromptMode.user_pwd:
+ elif self._question.mode in [usertypes.PromptMode.text,
+ usertypes.PromptMode.user_pwd,
+ usertypes.PromptMode.download]:
prompt.txt.setText(self._question.text)
if self._question.default:
prompt.lineedit.setText(self._question.default)
@@ -248,6 +246,13 @@ class Prompter(QObject):
modeman.maybe_leave(self._win_id, usertypes.KeyMode.prompt,
'prompt accept')
self._question.done()
+ elif self._question.mode == usertypes.PromptMode.download:
+ # User just entered a path for a download.
+ target = usertypes.FileDownloadTarget(prompt.lineedit.text())
+ self._question.answer = target
+ modeman.maybe_leave(self._win_id, usertypes.KeyMode.prompt,
+ 'prompt accept')
+ self._question.done()
elif self._question.mode == usertypes.PromptMode.yesno:
# User wants to accept the default of a yes/no question.
self._question.answer = self._question.default
@@ -287,6 +292,18 @@ class Prompter(QObject):
'prompt accept')
self._question.done()
+ @cmdutils.register(instance='prompter', hide=True, scope='window',
+ modes=[usertypes.KeyMode.prompt])
+ def prompt_open_download(self):
+ """Immediately open a download."""
+ if self._question.mode != usertypes.PromptMode.download:
+ # We just ignore this if we don't have a download question.
+ return
+ self._question.answer = usertypes.OpenFileDownloadTarget()
+ modeman.maybe_leave(self._win_id, usertypes.KeyMode.prompt,
+ 'download open')
+ self._question.done()
+
@pyqtSlot(usertypes.Question, bool)
def ask_question(self, question, blocking):
"""Display a question in the statusbar.
diff --git a/qutebrowser/utils/usertypes.py b/qutebrowser/utils/usertypes.py
index 872531d4f..e365d5645 100644
--- a/qutebrowser/utils/usertypes.py
+++ b/qutebrowser/utils/usertypes.py
@@ -221,7 +221,8 @@ class NeighborList(collections.abc.Sequence):
# The mode of a Question.
-PromptMode = enum('PromptMode', ['yesno', 'text', 'user_pwd', 'alert'])
+PromptMode = enum('PromptMode', ['yesno', 'text', 'user_pwd', 'alert',
+ 'download'])
# Where to open a clicked link.
@@ -255,6 +256,50 @@ LoadStatus = enum('LoadStatus', ['none', 'success', 'success_https', 'error',
Backend = enum('Backend', ['QtWebKit', 'QtWebEngine'])
+# Where a download should be saved
+class DownloadTarget:
+
+ """Abstract base class for different download targets."""
+
+ def __init__(self):
+ raise NotImplementedError
+
+
+class FileDownloadTarget(DownloadTarget):
+
+ """Save the download to the given file.
+
+ Attributes:
+ filename: Filename where the download should be saved.
+ """
+
+ def __init__(self, filename):
+ # pylint: disable=super-init-not-called
+ self.filename = filename
+
+
+class FileObjDownloadTarget(DownloadTarget):
+
+ """Save the download to the given file-like object.
+
+ Attributes:
+ fileobj: File-like object where the download should be written to.
+ """
+
+ def __init__(self, fileobj):
+ # pylint: disable=super-init-not-called
+ self.fileobj = fileobj
+
+
+class OpenFileDownloadTarget(DownloadTarget):
+
+ """Save the download in a temp dir and directly open it."""
+
+ def __init__(self):
+ # pylint: disable=super-init-not-called
+ pass
+
+
class Question(QObject):
"""A question asked to the user, e.g. via the status bar.
diff --git a/scripts/dev/misc_checks.py b/scripts/dev/misc_checks.py
index b759c39de..842888e01 100644
--- a/scripts/dev/misc_checks.py
+++ b/scripts/dev/misc_checks.py
@@ -81,14 +81,14 @@ def check_spelling():
"""Check commonly misspelled words."""
# Words which I often misspell
words = {'[Bb]ehaviour', '[Qq]uitted', 'Ll]ikelyhood', '[Ss]ucessfully',
- '[Oo]ccur[^r .]', '[Ss]eperator', '[Ee]xplicitely', '[Rr]esetted',
+ '[Oo]ccur[^rs .]', '[Ss]eperator', '[Ee]xplicitely',
'[Aa]uxillary', '[Aa]ccidentaly', '[Aa]mbigious', '[Ll]oosly',
'[Ii]nitialis', '[Cc]onvienence', '[Ss]imiliar', '[Uu]ncommited',
'[Rr]eproducable', '[Aa]n [Uu]ser', '[Cc]onvienience',
'[Ww]ether', '[Pp]rogramatically', '[Ss]plitted', '[Ee]xitted',
'[Mm]ininum', '[Rr]esett?ed', '[Rr]ecieved', '[Rr]egularily',
'[Uu]nderlaying', '[Ii]nexistant', '[Ee]lipsis', 'commiting',
- 'existant'}
+ 'existant', '[Rr]esetted'}
# Words which look better when splitted, but might need some fine tuning.
words |= {'[Ww]ebelements', '[Mm]ouseevent', '[Kk]eysequence',
diff --git a/tests/end2end/features/downloads.feature b/tests/end2end/features/downloads.feature
index 7016f3284..7548c0dda 100644
--- a/tests/end2end/features/downloads.feature
+++ b/tests/end2end/features/downloads.feature
@@ -30,9 +30,8 @@ Feature: Downloading things from a website.
And I open data/downloads/issue1243.html
And I run :hint links download
And I run :follow-hint a
- And I wait for "Asking question <qutebrowser.utils.usertypes.Question default='qutebrowser-download' mode=<PromptMode.text: 2> text='Save file to:'>, *" in the log
- And I run :leave-mode
- Then no crash should happen
+ And I wait for "Asking question <qutebrowser.utils.usertypes.Question default='qutebrowser-download' mode=<PromptMode.download: 5> text='Save file to:'>, *" in the log
+ Then the error "Download error: No handler found for qute://!" should be shown
Scenario: Downloading a data: link (issue 1214)
When I set completion -> download-path-suggestion to filename
@@ -40,7 +39,7 @@ Feature: Downloading things from a website.
And I open data/downloads/issue1214.html
And I run :hint links download
And I run :follow-hint a
- And I wait for "Asking question <qutebrowser.utils.usertypes.Question default='binary blob' mode=<PromptMode.text: 2> text='Save file to:'>, *" in the log
+ And I wait for "Asking question <qutebrowser.utils.usertypes.Question default='binary blob' mode=<PromptMode.download: 5> text='Save file to:'>, *" in the log
And I run :leave-mode
Then no crash should happen
diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature
index 0781a3a70..fb0c22f1a 100644
--- a/tests/end2end/features/misc.feature
+++ b/tests/end2end/features/misc.feature
@@ -312,7 +312,7 @@ Feature: Various utility commands.
And I open data/misc/test.pdf
And I wait for "[qute://pdfjs/*] PDF * (PDF.js: *)" in the log
And I run :jseval document.getElementById("download").click()
- And I wait for "Asking question <qutebrowser.utils.usertypes.Question default='test.pdf' mode=<PromptMode.text: 2> text='Save file to:'>, *" in the log
+ And I wait for "Asking question <qutebrowser.utils.usertypes.Question default='test.pdf' mode=<PromptMode.download: 5> text='Save file to:'>, *" in the log
And I run :leave-mode
Then no crash should happen
diff --git a/tests/end2end/features/test_downloads_bdd.py b/tests/end2end/features/test_downloads_bdd.py
index dc5a478eb..06271cb5c 100644
--- a/tests/end2end/features/test_downloads_bdd.py
+++ b/tests/end2end/features/test_downloads_bdd.py
@@ -64,7 +64,7 @@ def download_should_exist(filename, tmpdir):
def download_prompt(tmpdir, quteproc, path):
full_path = path.replace('{downloaddir}', str(tmpdir)).replace('/', os.sep)
msg = ("Asking question <qutebrowser.utils.usertypes.Question "
- "default={full_path!r} mode=<PromptMode.text: 2> "
+ "default={full_path!r} mode=<PromptMode.download: 5> "
"text='Save file to:'>, *".format(full_path=full_path))
quteproc.wait_for(message=msg)
quteproc.send_cmd(':leave-mode')
diff --git a/tests/unit/browser/test_adblock.py b/tests/unit/browser/test_adblock.py
index b300c8a26..8f8d4d65f 100644
--- a/tests/unit/browser/test_adblock.py
+++ b/tests/unit/browser/test_adblock.py
@@ -85,12 +85,12 @@ class FakeDownloadManager:
"""Mock browser.downloads.DownloadManager."""
- def get(self, url, fileobj, **kwargs):
+ def get(self, url, target, **kwargs):
"""Return a FakeDownloadItem instance with a fileobj.
The content is copied from the file the given url links to.
"""
- download_item = FakeDownloadItem(fileobj, name=url.path())
+ download_item = FakeDownloadItem(target.fileobj, name=url.path())
with open(url.path(), 'rb') as fake_url_file:
shutil.copyfileobj(fake_url_file, download_item.fileobj)
return download_item
diff --git a/tests/unit/utils/usertypes/test_downloadtarget.py b/tests/unit/utils/usertypes/test_downloadtarget.py
new file mode 100644
index 000000000..e89401144
--- /dev/null
+++ b/tests/unit/utils/usertypes/test_downloadtarget.py
@@ -0,0 +1,54 @@
+# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
+
+# Copyright 2016 Daniel Schadt
+#
+# This file is part of qutebrowser.
+#
+# qutebrowser is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# qutebrowser is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
+
+"""Tests for the DownloadTarget class."""
+
+from qutebrowser.utils import usertypes
+
+import pytest
+
+
+def test_base():
+ with pytest.raises(NotImplementedError):
+ usertypes.DownloadTarget()
+
+
+def test_filename():
+ target = usertypes.FileDownloadTarget("/foo/bar")
+ assert target.filename == "/foo/bar"
+
+
+def test_fileobj():
+ fobj = object()
+ target = usertypes.FileObjDownloadTarget(fobj)
+ assert target.fileobj is fobj
+
+
+def test_openfile():
+ # Just make sure no error is raised, that should be enough.
+ usertypes.OpenFileDownloadTarget()
+
+
+@pytest.mark.parametrize('obj', [
+ usertypes.FileDownloadTarget('foobar'),
+ usertypes.FileObjDownloadTarget(None),
+ usertypes.OpenFileDownloadTarget(),
+])
+def test_class_hierarchy(obj):
+ assert isinstance(obj, usertypes.DownloadTarget)