Merge branch 'oed-download_filename_handling'
This commit is contained in:
commit
f7b036cf15
@ -130,8 +130,8 @@ Contributors, sorted by the number of commits in descending order:
|
|||||||
|
|
||||||
// QUTE_AUTHORS_START
|
// QUTE_AUTHORS_START
|
||||||
* Florian Bruhin
|
* Florian Bruhin
|
||||||
* Claude
|
|
||||||
* Joel Torstensson
|
* Joel Torstensson
|
||||||
|
* Claude
|
||||||
* ZDarian
|
* ZDarian
|
||||||
* Peter Vilim
|
* Peter Vilim
|
||||||
* John ShaggyTwoDope Jenkins
|
* John ShaggyTwoDope Jenkins
|
||||||
|
@ -59,6 +59,7 @@
|
|||||||
[options="header",width="75%",cols="25%,75%"]
|
[options="header",width="75%",cols="25%,75%"]
|
||||||
|==============
|
|==============
|
||||||
|Setting|Description
|
|Setting|Description
|
||||||
|
|<<completion-download-path-suggestion,download-path-suggestion>>|What to display in the download filename input.
|
||||||
|<<completion-show,show>>|Whether to show the autocompletion window.
|
|<<completion-show,show>>|Whether to show the autocompletion window.
|
||||||
|<<completion-height,height>>|The height of the completion, in px or as percentage of the window.
|
|<<completion-height,height>>|The height of the completion, in px or as percentage of the window.
|
||||||
|<<completion-history-length,history-length>>|How many commands to save in the history.
|
|<<completion-history-length,history-length>>|How many commands to save in the history.
|
||||||
@ -601,6 +602,18 @@ Default: +pass:[true]+
|
|||||||
== completion
|
== completion
|
||||||
Options related to completion and command history.
|
Options related to completion and command history.
|
||||||
|
|
||||||
|
[[completion-download-path-suggestion]]
|
||||||
|
=== download-path-suggestion
|
||||||
|
What to display in the download filename input.
|
||||||
|
|
||||||
|
Valid values:
|
||||||
|
|
||||||
|
* +path+: Show only the download path.
|
||||||
|
* +filename+: Show only download filename.
|
||||||
|
* +both+: Show download path and filename.
|
||||||
|
|
||||||
|
Default: +pass:[path]+
|
||||||
|
|
||||||
[[completion-show]]
|
[[completion-show]]
|
||||||
=== show
|
=== show
|
||||||
Whether to show the autocompletion window.
|
Whether to show the autocompletion window.
|
||||||
|
@ -49,6 +49,32 @@ ModelRole = usertypes.enum('ModelRole', ['item'], start=Qt.UserRole,
|
|||||||
RetryInfo = collections.namedtuple('RetryInfo', ['request', 'manager'])
|
RetryInfo = collections.namedtuple('RetryInfo', ['request', 'manager'])
|
||||||
|
|
||||||
|
|
||||||
|
def _download_dir():
|
||||||
|
"""Get the download directory to use."""
|
||||||
|
directory = config.get('storage', 'download-directory')
|
||||||
|
if directory is None:
|
||||||
|
directory = standarddir.download()
|
||||||
|
return directory
|
||||||
|
|
||||||
|
|
||||||
|
def _path_suggestion(filename):
|
||||||
|
"""Get the suggested file path
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filename: The filename to use if included in the suggestion.
|
||||||
|
"""
|
||||||
|
suggestion = config.get('completion', 'download-path-suggestion')
|
||||||
|
if suggestion == 'path':
|
||||||
|
# add trailing '/' if not present
|
||||||
|
return os.path.join(_download_dir(), '')
|
||||||
|
elif suggestion == 'filename':
|
||||||
|
return filename
|
||||||
|
elif suggestion == 'both':
|
||||||
|
return os.path.join(_download_dir(), filename)
|
||||||
|
else:
|
||||||
|
raise ValueError("Invalid suggestion value {}!".format(suggestion))
|
||||||
|
|
||||||
|
|
||||||
class DownloadItemStats(QObject):
|
class DownloadItemStats(QObject):
|
||||||
|
|
||||||
"""Statistics (bytes done, total bytes, time, etc.) about a download.
|
"""Statistics (bytes done, total bytes, time, etc.) about a download.
|
||||||
@ -364,7 +390,6 @@ class DownloadItem(QObject):
|
|||||||
self.finished.emit()
|
self.finished.emit()
|
||||||
self.data_changed.emit()
|
self.data_changed.emit()
|
||||||
|
|
||||||
@pyqtSlot()
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
"""Delete the downloaded file"""
|
"""Delete the downloaded file"""
|
||||||
try:
|
try:
|
||||||
@ -403,23 +428,12 @@ class DownloadItem(QObject):
|
|||||||
# See https://github.com/The-Compiler/qutebrowser/issues/427
|
# See https://github.com/The-Compiler/qutebrowser/issues/427
|
||||||
encoding = sys.getfilesystemencoding()
|
encoding = sys.getfilesystemencoding()
|
||||||
filename = utils.force_encoding(filename, encoding)
|
filename = utils.force_encoding(filename, encoding)
|
||||||
if os.path.isabs(filename) and os.path.isdir(filename):
|
if not self._create_full_filename(filename):
|
||||||
# We got an absolute directory from the user, so we save it under
|
# We only got a filename (without directory) or a relative path
|
||||||
# the default filename in that directory.
|
# from the user, so we append that to the default directory and
|
||||||
self._filename = os.path.join(filename, self.basename)
|
# try again.
|
||||||
elif os.path.isabs(filename):
|
self._create_full_filename(os.path.join(_download_dir(), filename))
|
||||||
# We got an absolute filename from the user, so we save it under
|
|
||||||
# that filename.
|
|
||||||
self._filename = filename
|
|
||||||
self.basename = os.path.basename(self._filename)
|
|
||||||
else:
|
|
||||||
# We only got a filename (without directory) from the user, so we
|
|
||||||
# save it under that filename in the default directory.
|
|
||||||
download_dir = config.get('storage', 'download-directory')
|
|
||||||
if download_dir is None:
|
|
||||||
download_dir = standarddir.download()
|
|
||||||
self._filename = os.path.join(download_dir, filename)
|
|
||||||
self.basename = filename
|
|
||||||
log.downloads.debug("Setting filename to {}".format(filename))
|
log.downloads.debug("Setting filename to {}".format(filename))
|
||||||
if os.path.isfile(self._filename):
|
if os.path.isfile(self._filename):
|
||||||
# The file already exists, so ask the user if it should be
|
# The file already exists, so ask the user if it should be
|
||||||
@ -428,6 +442,25 @@ class DownloadItem(QObject):
|
|||||||
else:
|
else:
|
||||||
self._create_fileobj()
|
self._create_fileobj()
|
||||||
|
|
||||||
|
def _create_full_filename(self, filename):
|
||||||
|
"""Tries to create the full filename.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
True if the full filename was created, False otherwise.
|
||||||
|
"""
|
||||||
|
if os.path.isabs(filename) and os.path.isdir(filename):
|
||||||
|
# We got an absolute directory from the user, so we save it under
|
||||||
|
# the default filename in that directory.
|
||||||
|
self._filename = os.path.join(filename, self.basename)
|
||||||
|
return True
|
||||||
|
elif os.path.isabs(filename):
|
||||||
|
# We got an absolute filename from the user, so we save it under
|
||||||
|
# that filename.
|
||||||
|
self._filename = filename
|
||||||
|
self.basename = os.path.basename(self._filename)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def set_fileobj(self, fileobj):
|
def set_fileobj(self, fileobj):
|
||||||
""""Set the file object to write the download to.
|
""""Set the file object to write the download to.
|
||||||
|
|
||||||
@ -641,24 +674,25 @@ class DownloadManager(QAbstractListModel):
|
|||||||
# https://bugreports.qt-project.org/browse/QTBUG-42757
|
# https://bugreports.qt-project.org/browse/QTBUG-42757
|
||||||
request.setAttribute(QNetworkRequest.CacheLoadControlAttribute,
|
request.setAttribute(QNetworkRequest.CacheLoadControlAttribute,
|
||||||
QNetworkRequest.AlwaysNetwork)
|
QNetworkRequest.AlwaysNetwork)
|
||||||
|
suggested_fn = urlutils.filename_from_url(request.url())
|
||||||
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, page, fileobj, filename,
|
return self.fetch_request(request, page, fileobj, filename,
|
||||||
auto_remove)
|
auto_remove, suggested_fn)
|
||||||
q = self._prepare_question()
|
|
||||||
filename = urlutils.filename_from_url(request.url())
|
|
||||||
encoding = sys.getfilesystemencoding()
|
encoding = sys.getfilesystemencoding()
|
||||||
filename = utils.force_encoding(filename, encoding)
|
suggested_fn = utils.force_encoding(suggested_fn, encoding)
|
||||||
q.default = filename
|
q = self._prepare_question()
|
||||||
|
q.default = _path_suggestion(suggested_fn)
|
||||||
message_bridge = objreg.get('message-bridge', scope='window',
|
message_bridge = objreg.get('message-bridge', scope='window',
|
||||||
window=self._win_id)
|
window=self._win_id)
|
||||||
q.answered.connect(
|
q.answered.connect(
|
||||||
lambda fn: self.fetch_request(request, filename=fn, page=page,
|
lambda fn: self.fetch_request(request, page, filename=fn,
|
||||||
auto_remove=auto_remove))
|
auto_remove=auto_remove,
|
||||||
|
suggested_filename=suggested_fn))
|
||||||
message_bridge.ask(q, blocking=False)
|
message_bridge.ask(q, blocking=False)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def fetch_request(self, request, page=None, fileobj=None, filename=None,
|
def fetch_request(self, request, page=None, fileobj=None, filename=None,
|
||||||
auto_remove=False):
|
auto_remove=False, suggested_filename=None):
|
||||||
"""Download a QNetworkRequest to disk.
|
"""Download a QNetworkRequest to disk.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -677,10 +711,12 @@ class DownloadManager(QAbstractListModel):
|
|||||||
else:
|
else:
|
||||||
nam = page.networkAccessManager()
|
nam = page.networkAccessManager()
|
||||||
reply = nam.get(request)
|
reply = nam.get(request)
|
||||||
return self.fetch(reply, fileobj, filename, auto_remove)
|
return self.fetch(reply, fileobj, filename, auto_remove,
|
||||||
|
suggested_filename)
|
||||||
|
|
||||||
@pyqtSlot('QNetworkReply')
|
@pyqtSlot('QNetworkReply')
|
||||||
def fetch(self, reply, fileobj=None, filename=None, auto_remove=False):
|
def fetch(self, reply, fileobj=None, filename=None, auto_remove=False,
|
||||||
|
suggested_filename=None):
|
||||||
"""Download a QNetworkReply to disk.
|
"""Download a QNetworkReply to disk.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -695,12 +731,13 @@ class DownloadManager(QAbstractListModel):
|
|||||||
"""
|
"""
|
||||||
if fileobj is not None and filename is not None:
|
if fileobj is not None and filename is not None:
|
||||||
raise TypeError("Only one of fileobj/filename may be given!")
|
raise TypeError("Only one of fileobj/filename may be given!")
|
||||||
if filename is not None:
|
if not suggested_filename:
|
||||||
suggested_filename = os.path.basename(filename)
|
if filename is not None:
|
||||||
elif fileobj is not None and getattr(fileobj, 'name', None):
|
suggested_filename = os.path.basename(filename)
|
||||||
suggested_filename = fileobj.name
|
elif fileobj is not None and getattr(fileobj, 'name', None):
|
||||||
else:
|
suggested_filename = fileobj.name
|
||||||
_inline, suggested_filename = http.parse_content_disposition(reply)
|
else:
|
||||||
|
_, suggested_filename = http.parse_content_disposition(reply)
|
||||||
log.downloads.debug("fetch: {} -> {}".format(reply.url(),
|
log.downloads.debug("fetch: {} -> {}".format(reply.url(),
|
||||||
suggested_filename))
|
suggested_filename))
|
||||||
download = DownloadItem(reply, self._win_id, self)
|
download = DownloadItem(reply, self._win_id, self)
|
||||||
@ -729,10 +766,7 @@ class DownloadManager(QAbstractListModel):
|
|||||||
download.autoclose = False
|
download.autoclose = False
|
||||||
else:
|
else:
|
||||||
q = self._prepare_question()
|
q = self._prepare_question()
|
||||||
encoding = sys.getfilesystemencoding()
|
q.default = _path_suggestion(suggested_filename)
|
||||||
suggested_filename = utils.force_encoding(suggested_filename,
|
|
||||||
encoding)
|
|
||||||
q.default = suggested_filename
|
|
||||||
q.answered.connect(download.set_filename)
|
q.answered.connect(download.set_filename)
|
||||||
q.cancelled.connect(download.cancel)
|
q.cancelled.connect(download.cancel)
|
||||||
download.cancelled.connect(q.abort)
|
download.cancelled.connect(q.abort)
|
||||||
|
@ -280,12 +280,13 @@ class BrowserPage(QWebPage):
|
|||||||
At some point we might want to implement the MIME Sniffing standard
|
At some point we might want to implement the MIME Sniffing standard
|
||||||
here: http://mimesniff.spec.whatwg.org/
|
here: http://mimesniff.spec.whatwg.org/
|
||||||
"""
|
"""
|
||||||
inline, _suggested_filename = http.parse_content_disposition(reply)
|
inline, suggested_filename = http.parse_content_disposition(reply)
|
||||||
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 not inline:
|
if not inline:
|
||||||
# Content-Disposition: attachment -> force download
|
# Content-Disposition: attachment -> force download
|
||||||
download_manager.fetch(reply)
|
download_manager.fetch(reply,
|
||||||
|
suggested_filename=suggested_filename)
|
||||||
return
|
return
|
||||||
mimetype, _rest = http.parse_content_type(reply)
|
mimetype, _rest = http.parse_content_type(reply)
|
||||||
if mimetype == 'image/jpg':
|
if mimetype == 'image/jpg':
|
||||||
@ -300,7 +301,8 @@ class BrowserPage(QWebPage):
|
|||||||
self.display_content, reply, 'image/jpeg'))
|
self.display_content, reply, 'image/jpeg'))
|
||||||
else:
|
else:
|
||||||
# Unknown mimetype, so download anyways.
|
# Unknown mimetype, so download anyways.
|
||||||
download_manager.fetch(reply)
|
download_manager.fetch(reply,
|
||||||
|
suggested_filename=suggested_filename)
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def on_load_started(self):
|
def on_load_started(self):
|
||||||
|
@ -305,6 +305,10 @@ DATA = collections.OrderedDict([
|
|||||||
)),
|
)),
|
||||||
|
|
||||||
('completion', sect.KeyValue(
|
('completion', sect.KeyValue(
|
||||||
|
('download-path-suggestion',
|
||||||
|
SettingValue(typ.DownloadPathSuggestion(), 'path'),
|
||||||
|
"What to display in the download filename input."),
|
||||||
|
|
||||||
('show',
|
('show',
|
||||||
SettingValue(typ.Bool(), 'true'),
|
SettingValue(typ.Bool(), 'true'),
|
||||||
"Whether to show the autocompletion window."),
|
"Whether to show the autocompletion window."),
|
||||||
|
@ -1416,6 +1416,15 @@ class NewInstanceOpenTarget(BaseType):
|
|||||||
('window', "Open in a new window."))
|
('window', "Open in a new window."))
|
||||||
|
|
||||||
|
|
||||||
|
class DownloadPathSuggestion(BaseType):
|
||||||
|
|
||||||
|
"""How to format the question when downloading."""
|
||||||
|
|
||||||
|
valid_values = ValidValues(('path', "Show only the download path."),
|
||||||
|
('filename', "Show only download filename."),
|
||||||
|
('both', "Show download path and filename."))
|
||||||
|
|
||||||
|
|
||||||
class UserAgent(BaseType):
|
class UserAgent(BaseType):
|
||||||
|
|
||||||
"""The user agent to use."""
|
"""The user agent to use."""
|
||||||
|
Loading…
Reference in New Issue
Block a user