Use per-tab QNAM for QtNetwork downloads again

When starting a download due to unsupportedContent being emitted, we
need to use (and later adopt) the page's QNetworkAccessManager.

Since we need the whole adopting logic for that case anyways, let's keep
things as they were and always run downloads in per-tab QNAMs.

This reverts 53e360ec4b and fixes #2134.
This commit is contained in:
Florian Bruhin 2016-11-22 11:10:37 +01:00
parent c5cacbc439
commit c363982d05
6 changed files with 109 additions and 8 deletions

View File

@ -1332,11 +1332,22 @@ class CommandDispatcher:
elif mhtml_: elif mhtml_:
self._download_mhtml(dest) self._download_mhtml(dest)
else: else:
tab = self._current_widget()
# FIXME:qtwebengine have a proper API for this
# pylint: disable=protected-access
try:
qnam = tab._widget.page().networkAccessManager()
except AttributeError:
# QtWebEngine
qnam = None
# pylint: enable=protected-access
if dest is None: if dest is None:
target = None target = None
else: else:
target = downloads.FileDownloadTarget(dest) target = downloads.FileDownloadTarget(dest)
download_manager.get(self._current_url(), target=target) download_manager.get(self._current_url(), qnam=qnam, target=target)
def _download_mhtml(self, dest=None): def _download_mhtml(self, dest=None):
"""Download the current page as an MHTML file, including all assets. """Download the current page as an MHTML file, including all assets.

View File

@ -284,10 +284,19 @@ class HintActions:
else: else:
prompt = None prompt = None
# FIXME:qtwebengine get a proper API for this
# pylint: disable=protected-access
try:
qnam = elem._elem.webFrame().page().networkAccessManager()
except AttributeError:
# QtWebEngine
qnam = None
# pylint: enable=protected-access
# FIXME:qtwebengine do this with QtWebEngine downloads? # FIXME:qtwebengine do this with QtWebEngine downloads?
download_manager = objreg.get('qtnetwork-download-manager', download_manager = objreg.get('qtnetwork-download-manager',
scope='window', window=self._win_id) scope='window', window=self._win_id)
download_manager.get(url, prompt_download_directory=prompt) download_manager.get(url, qnam=qnam, prompt_download_directory=prompt)
def call_userscript(self, elem, context): def call_userscript(self, elem, context):
"""Call a userscript from a hint. """Call a userscript from a hint.

View File

@ -24,7 +24,7 @@ import shutil
import functools import functools
import collections import collections
from PyQt5.QtCore import pyqtSlot, QTimer from PyQt5.QtCore import pyqtSlot, pyqtSignal, QTimer
from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply
from qutebrowser.utils import message, usertypes, log, urlutils from qutebrowser.utils import message, usertypes, log, urlutils
@ -67,9 +67,15 @@ class DownloadItem(downloads.AbstractDownloadItem):
_reply: The QNetworkReply associated with this download. _reply: The QNetworkReply associated with this download.
_autoclose: Whether to close the associated file when the download is _autoclose: Whether to close the associated file when the download is
done. done.
Signals:
adopt_download: Emitted when a download is retried and should be
adopted by the QNAM if needed.
arg 0: The new DownloadItem
""" """
_MAX_REDIRECTS = 10 _MAX_REDIRECTS = 10
adopt_download = pyqtSignal(object) # DownloadItem
def __init__(self, reply, manager): def __init__(self, reply, manager):
"""Constructor. """Constructor.
@ -162,7 +168,9 @@ class DownloadItem(downloads.AbstractDownloadItem):
assert self.done assert self.done
assert not self.successful assert not self.successful
new_reply = self._retry_info.manager.get(self._retry_info.request) new_reply = self._retry_info.manager.get(self._retry_info.request)
self._manager.fetch(new_reply, suggested_filename=self.basename) new_download = self._manager.fetch(new_reply,
suggested_filename=self.basename)
self.adopt_download.emit(new_download)
self.cancel() self.cancel()
def _get_open_filename(self): def _get_open_filename(self):
@ -335,6 +343,14 @@ class DownloadItem(downloads.AbstractDownloadItem):
old_reply.deleteLater() old_reply.deleteLater()
return True return True
def _uses_nam(self, nam):
"""Check if this download uses the given QNetworkAccessManager."""
running_nam = self._reply is not None and self._reply.manager() is nam
# user could request retry after tab is closed.
retry_nam = (self.done and (not self.successful) and
self._retry_info.manager is nam)
return running_nam or retry_nam
class DownloadManager(downloads.AbstractDownloadManager): class DownloadManager(downloads.AbstractDownloadManager):
@ -409,17 +425,20 @@ class DownloadManager(downloads.AbstractDownloadManager):
suggested_filename=suggested_fn, suggested_filename=suggested_fn,
**kwargs) **kwargs)
def _fetch_request(self, request, **kwargs): def _fetch_request(self, request, *, qnam=None, **kwargs):
"""Download a QNetworkRequest to disk. """Download a QNetworkRequest to disk.
Args: Args:
request: The QNetworkRequest to download. request: The QNetworkRequest to download.
qnam: The QNetworkAccessManager to use.
**kwargs: passed to fetch(). **kwargs: passed to fetch().
Return: Return:
The created DownloadItem. The created DownloadItem.
""" """
reply = self._networkmanager.get(request) if qnam is None:
qnam = self._networkmanager
reply = qnam.get(request)
return self.fetch(reply, **kwargs) return self.fetch(reply, **kwargs)
@pyqtSlot('QNetworkReply') @pyqtSlot('QNetworkReply')
@ -467,3 +486,18 @@ class DownloadManager(downloads.AbstractDownloadManager):
message.global_bridge.ask(question, blocking=False) message.global_bridge.ask(question, blocking=False)
return download return download
def has_downloads_with_nam(self, nam):
"""Check if the DownloadManager has any downloads with the given QNAM.
Args:
nam: The QNetworkAccessManager to check.
Return:
A boolean.
"""
assert nam.adopted_downloads == 0
for download in self.downloads:
if download._uses_nam(nam): # pylint: disable=protected-access
nam.adopt_download(download)
return nam.adopted_downloads

View File

@ -116,6 +116,11 @@ class NetworkManager(QNetworkAccessManager):
"""Our own QNetworkAccessManager. """Our own QNetworkAccessManager.
Attributes: Attributes:
adopted_downloads: If downloads are running with this QNAM but the
associated tab gets closed already, the NAM gets
reparented to the DownloadManager. This counts the
still running downloads, so the QNAM can clean
itself up when this reaches zero again.
_requests: Pending requests. _requests: Pending requests.
_scheme_handlers: A dictionary (scheme -> handler) of supported custom _scheme_handlers: A dictionary (scheme -> handler) of supported custom
schemes. schemes.
@ -137,6 +142,7 @@ class NetworkManager(QNetworkAccessManager):
# http://www.riverbankcomputing.com/pipermail/pyqt/2014-November/035045.html # http://www.riverbankcomputing.com/pipermail/pyqt/2014-November/035045.html
super().__init__(parent) super().__init__(parent)
log.init.debug("NetworkManager init done") log.init.debug("NetworkManager init done")
self.adopted_downloads = 0
self._win_id = win_id self._win_id = win_id
self._tab_id = tab_id self._tab_id = tab_id
self._requests = [] self._requests = []
@ -328,6 +334,28 @@ class NetworkManager(QNetworkAccessManager):
# switched from private mode to normal mode # switched from private mode to normal mode
self._set_cookiejar() self._set_cookiejar()
@pyqtSlot()
def on_adopted_download_destroyed(self):
"""Check if we can clean up if an adopted download was destroyed.
See the description for adopted_downloads for details.
"""
self.adopted_downloads -= 1
log.downloads.debug("Adopted download destroyed, {} left.".format(
self.adopted_downloads))
assert self.adopted_downloads >= 0
if self.adopted_downloads == 0:
self.deleteLater()
@pyqtSlot(object) # DownloadItem
def adopt_download(self, download):
"""Adopt a new DownloadItem."""
self.adopted_downloads += 1
log.downloads.debug("Adopted download, {} adopted.".format(
self.adopted_downloads))
download.destroyed.connect(self.on_adopted_download_destroyed)
download.adopt_download.connect(self.adopt_download)
def set_referer(self, req, current_url): def set_referer(self, req, current_url):
"""Set the referer header.""" """Set the referer header."""
referer_header_conf = config.get('network', 'referer-header') referer_header_conf = config.get('network', 'referer-header')

View File

@ -210,7 +210,13 @@ class BrowserPage(QWebPage):
"""Prepare the web page for being deleted.""" """Prepare the web page for being deleted."""
self._is_shutting_down = True self._is_shutting_down = True
self.shutting_down.emit() self.shutting_down.emit()
self.networkAccessManager().shutdown() download_manager = objreg.get('qtnetwork-download-manager',
scope='window', window=self._win_id)
nam = self.networkAccessManager()
if download_manager.has_downloads_with_nam(nam):
nam.setParent(download_manager)
else:
nam.shutdown()
def display_content(self, reply, mimetype): def display_content(self, reply, mimetype):
"""Display a QNetworkReply with an explicitly set mimetype.""" """Display a QNetworkReply with an explicitly set mimetype."""
@ -239,7 +245,7 @@ class BrowserPage(QWebPage):
req = QNetworkRequest(request) req = QNetworkRequest(request)
download_manager = objreg.get('qtnetwork-download-manager', download_manager = objreg.get('qtnetwork-download-manager',
scope='window', window=self._win_id) scope='window', window=self._win_id)
download_manager.get_request(req) download_manager.get_request(req, qnam=self.networkAccessManager())
@pyqtSlot('QNetworkReply*') @pyqtSlot('QNetworkReply*')
def on_unsupported_content(self, reply): def on_unsupported_content(self, reply):

View File

@ -100,6 +100,19 @@ Feature: Downloading things from a website.
And I run :close And I run :close
Then qutebrowser should quit Then qutebrowser should quit
# https://github.com/The-Compiler/qutebrowser/issues/2134
@qtwebengine_skip
Scenario: Downloading, then closing a tab
When I set storage -> prompt-download-directory to false
And I open about:blank
And I open data/downloads/issue2134.html in a new tab
# This needs to be a download connected to the tabs QNAM
And I hint with args "links normal" and follow a
And I wait for "fetch: * -> drip" in the log
And I run :tab-close
And I wait for "Download drip finished" in the log
Then the downloaded file drip should contain 128 bytes
## :download-retry ## :download-retry
Scenario: Retrying a failed download Scenario: Retrying a failed download