From 38ef8fe8c73d4728e1e4c7967eb6e3f2842951d2 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 20 Feb 2014 23:08:27 +0100 Subject: [PATCH] First attempt at proper AboutSchemeHandler --- qutebrowser/network/about.py | 28 +++++- qutebrowser/network/networkmanager.py | 21 ++++- qutebrowser/network/schemehandler.py | 118 ++++++++++++++++++++++++++ qutebrowser/widgets/browser.py | 12 +-- 4 files changed, 165 insertions(+), 14 deletions(-) create mode 100644 qutebrowser/network/schemehandler.py diff --git a/qutebrowser/network/about.py b/qutebrowser/network/about.py index 6e3496a94..7552839b9 100644 --- a/qutebrowser/network/about.py +++ b/qutebrowser/network/about.py @@ -17,8 +17,10 @@ """Handler functions for different about:... pages.""" +from qutebrowser.network.schemehandler import (SchemeHandler, + SpecialNetworkReply) from qutebrowser.utils.version import version -from qutebrowser.utils.url import is_about_url +from qutebrowser.utils.url import is_about_url, urlstring _HTML_TEMPLATE = """ @@ -84,6 +86,30 @@ def _get_html(title, snippet): return _HTML_TEMPLATE.format(title=title, body=snippet).encode('UTF-8') +class AboutSchemeHandler(SchemeHandler): + + """Scheme handler for about: URLs.""" + + def createRequest(self, op, request, outgoingData=None): + """Create a new request. + + Args: + op: Operation op + req: const QNetworkRequest & req + outgoing_data: QIODevice * outgoingData + + Return: + A QNetworkReply. + + """ + + # FIXME handle unknown pages + # FIXME adjust URLutils based on handlers + + data = handle(urlstring(request.url())) + return SpecialNetworkReply(request, data, "text/html", self.parent()) + + class AboutHandlers: """Handlers for about:... pages.""" diff --git a/qutebrowser/network/networkmanager.py b/qutebrowser/network/networkmanager.py index ec1009c09..83ec0e0ea 100644 --- a/qutebrowser/network/networkmanager.py +++ b/qutebrowser/network/networkmanager.py @@ -17,20 +17,30 @@ """Our own QNetworkAccessManager.""" +import logging + from PyQt5.QtNetwork import QNetworkAccessManager +from qutebrowser.network.about import AboutSchemeHandler + + class NetworkManager(QNetworkAccessManager): """Our own QNetworkAccessManager. Attributes: _requests: Pending requests. + _scheme_handlers: A dictionary (scheme -> handler) of supported custom + schemes. """ def __init__(self, parent=None): - self._requests = {} super().__init__(parent) + self._requests = {} + self._scheme_handlers = { + 'about': AboutSchemeHandler, + } def abort_requests(self): """Abort all running requests.""" @@ -41,7 +51,7 @@ class NetworkManager(QNetworkAccessManager): """Return a new QNetworkReply object. Extend QNetworkAccessManager::createRequest to save requests in - self._requests. + self._requests and handle custom schemes. Args: op: Operation op @@ -52,6 +62,13 @@ class NetworkManager(QNetworkAccessManager): A QNetworkReply. """ + scheme = req.url().scheme() + logging.debug("new req, scheme {}, handlers {}".format(scheme, + self._scheme_handlers)) + if scheme in self._scheme_handlers: + reply = self._scheme_handlers[scheme].createRequest( + op, req, outgoing_data) + return reply reply = super().createRequest(op, req, outgoing_data) self._requests[id(reply)] = reply reply.destroyed.connect(lambda obj: self._requests.pop(id(obj))) diff --git a/qutebrowser/network/schemehandler.py b/qutebrowser/network/schemehandler.py new file mode 100644 index 000000000..ec119f4d3 --- /dev/null +++ b/qutebrowser/network/schemehandler.py @@ -0,0 +1,118 @@ +# Copyright 2014 Florian Bruhin (The Compiler) +# +# Based on the Eric5 helpviewer, +# Copyright (c) 2009 - 2014 Detlev Offenbach +# +# 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 . + +"""Base class for custom scheme handlers.""" + +from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest +from PyQt5.QtCore import pyqtSlot, QObject, QIODevice, QByteArray + +class SchemeHandler(QObject): + + """Abstract base class for custom scheme handlers.""" + + def createRequest(self, op, request, outgoingData=None): + """Create a new request. + + Args: + op: Operation op + req: const QNetworkRequest & req + outgoing_data: QIODevice * outgoingData + + Return: + A QNetworkReply. + + Raise: + NotImplementedError because this needs to be overwritten by + subclasses. + """ + raise NotImplementedError + + + +class SpecialNetworkReply(QNetworkReply): + + """QNetworkReply subclass for special data.""" + + def __init__(self, request, fileData, mimeType, parent=None): + """Constructor. + + Args: + request: reference to the request object (QNetworkRequest) + fileData: reference to the data buffer (QByteArray) + mimeType: for the reply (string) + parent: reference to the parent object (QObject) + + Emit: + metaDataChanged and readyRead after initializing. + + """ + super().__init__(parent) + + self._data = fileData + + self.setRequest(request) + self.setOpenMode(QIODevice.ReadOnly) + + self.setHeader(QNetworkRequest.ContentTypeHeader, mimeType) + self.setHeader(QNetworkRequest.ContentLengthHeader, + QByteArray.number(fileData.length())) + self.setAttribute(QNetworkRequest.HttpStatusCodeAttribute, 200) + self.setAttribute(QNetworkRequest.HttpReasonPhraseAttribute, "OK") + self.metaDataChanged.emit() + self.readyRead.emit() + + @pyqtSlot() + def abort(self): + """Abort the operation.""" + pass + + def bytesAvailable(self): + """Determine the bytes available for being read. + + Return: + bytes available (int) + + Emit: + finished: if the data length is 0 + + """ + if self._data.length() == 0: + self.finished.emit() + return self._data.length() + super().bytesAvailable() + + def readData(self, maxlen): + """Retrieve data from the reply object. + + Args: + maxlen maximum number of bytes to read (int) + + Return: + bytestring containing the data + + Emit: + finished: if all data was read + + """ + len_ = min(maxlen, self._data.length()) + buf = bytes(self._data[:len_]) + self._data.remove(0, len_) + if self._data.length() == 0: + self.finished.emit() + return buf diff --git a/qutebrowser/widgets/browser.py b/qutebrowser/widgets/browser.py index 21c817a80..224e1062a 100644 --- a/qutebrowser/widgets/browser.py +++ b/qutebrowser/widgets/browser.py @@ -36,7 +36,6 @@ from PyQt5.QtWebKitWidgets import QWebView, QWebPage import qutebrowser.utils.url as urlutils import qutebrowser.utils.config as config -import qutebrowser.network.about as about from qutebrowser.widgets.tabbar import TabWidget from qutebrowser.network.networkmanager import NetworkManager from qutebrowser.utils.signals import SignalCache, dbg_signal @@ -719,16 +718,7 @@ class BrowserTab(QWebView): logging.debug('New title: {}'.format(urlutils.urlstring(u))) self.titleChanged.emit(urlutils.urlstring(u)) self.urlChanged.emit(urlutils.qurl(u)) - if urlutils.is_about_url(u): - try: - content = about.handle(urlutils.urlstring(u)) - except AttributeError: - return self.load(u) - else: - self.setUrl(u) - self.setContent(content, 'text/html') - else: - return self.load(u) + return self.load(u) def zoom(self, offset): """Increase/Decrease the zoom level.