diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index 07e5c7c30..19880535d 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -27,6 +27,7 @@ import collections import functools import pathlib import tempfile +import errno import sip from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QObject, QModelIndex, @@ -137,7 +138,8 @@ def create_full_filename(basename, filename): encoding = sys.getfilesystemencoding() filename = utils.force_encoding(filename, encoding) basename = utils.force_encoding(basename, encoding) - if os.path.isabs(filename) and os.path.isdir(filename): + if os.path.isabs(filename) and (os.path.isdir(filename) or + os.path.join(filename, "") == filename): # We got an absolute directory from the user, so we save it under # the default filename in that directory. return os.path.join(filename, basename) @@ -657,11 +659,42 @@ class AbstractDownloadItem(QObject): self._filename = create_full_filename(self.basename, os.path.expanduser('~')) + dirname = os.path.dirname(self._filename) + if not os.path.exists(dirname): + txt = ("{} does not exist. Create it?". + format(html.escape( + os.path.join(dirname, "")))) + self._ask_create_parent_question("Create directory?", txt, + force_overwrite, + remember_directory) + else: + self._after_create_parent_question(force_overwrite, + remember_directory) + + def _after_create_parent_question(self, + force_overwrite, remember_directory): + """After asking about parent directory. + + Args: + force_overwrite: Force overwriting existing files. + remember_directory: If True, remember the directory for future + downloads. + """ + global last_used_directory + + try: + os.makedirs(os.path.dirname(self._filename)) + except OSError as e: + # Unlikely, but could be created before + # we get a chance to create it. + if e.errno != errno.EEXIST: + raise + self.basename = os.path.basename(self._filename) if remember_directory: last_used_directory = os.path.dirname(self._filename) - log.downloads.debug("Setting filename to {}".format(filename)) + log.downloads.debug("Setting filename to {}".format(self._filename)) if force_overwrite: self._after_set_filename() elif os.path.isfile(self._filename): diff --git a/qutebrowser/browser/qtnetworkdownloads.py b/qutebrowser/browser/qtnetworkdownloads.py index 921fee54d..709c8207b 100644 --- a/qutebrowser/browser/qtnetworkdownloads.py +++ b/qutebrowser/browser/qtnetworkdownloads.py @@ -203,6 +203,17 @@ class DownloadItem(downloads.AbstractDownloadItem): no_action=no_action, cancel_action=no_action, abort_on=[self.cancelled, self.error]) + def _ask_create_parent_question(self, title, msg, + force_overwrite, remember_directory): + no_action = functools.partial(self.cancel, remove_data=False) + message.confirm_async(title=title, text=msg, + yes_action=(lambda: + self._after_create_parent_question( + force_overwrite, + remember_directory)), + no_action=no_action, cancel_action=no_action, + abort_on=[self.cancelled, self.error]) + def _set_fileobj(self, fileobj, *, autoclose=True): """"Set the file object to write the download to. diff --git a/qutebrowser/browser/webengine/webenginedownloads.py b/qutebrowser/browser/webengine/webenginedownloads.py index ebb03828b..03baac359 100644 --- a/qutebrowser/browser/webengine/webenginedownloads.py +++ b/qutebrowser/browser/webengine/webenginedownloads.py @@ -120,6 +120,22 @@ class DownloadItem(downloads.AbstractDownloadItem): "state {} (not in requested state)!".format( filename, self, state_name)) + def _ask_create_parent_question(self, title, msg, + force_overwrite, remember_directory): + no_action = functools.partial(self.cancel, remove_data=False) + question = usertypes.Question() + question.title = title + question.text = msg + question.mode = usertypes.PromptMode.yesno + question.answered_yes.connect(lambda: + self._after_create_parent_question( + force_overwrite, remember_directory)) + question.answered_no.connect(no_action) + question.cancelled.connect(no_action) + self.cancelled.connect(question.abort) + self.error.connect(question.abort) + message.global_bridge.ask(question, blocking=True) + def _ask_confirm_question(self, title, msg): no_action = functools.partial(self.cancel, remove_data=False) question = usertypes.Question() diff --git a/tests/end2end/features/downloads.feature b/tests/end2end/features/downloads.feature index 103ad0123..3df2e18a4 100644 --- a/tests/end2end/features/downloads.feature +++ b/tests/end2end/features/downloads.feature @@ -256,8 +256,11 @@ Feature: Downloading things from a website. Then the error "Can only download the current page as mhtml." should be shown Scenario: :download with a directory which doesn't exist - When I run :download --dest (tmpdir)/downloads/somedir/filename http://localhost:(port)/ - Then the error "Download error: No such file or directory" should be shown + When I run :download --dest (tmpdir)/downloads/somedir/filename http://localhost:(port)/data/downloads/download.bin + And I wait for "Asking question text='* does not exist. Create it?' title='Create directory?'>, *" in the log + And I run :prompt-accept yes + And I wait until the download filename is finished + Then the downloaded file filename should not exist ## mhtml downloads