summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Bruhin <git@the-compiler.org>2014-12-11 23:34:03 +0100
committerFlorian Bruhin <git@the-compiler.org>2014-12-11 23:34:03 +0100
commitdecfd020331c2025f15e0e10da3c9e3e68a2281e (patch)
treeafd5874fa2d3c050b7cf3a2840de3663f6dc1de9
parentaefa637bc58f2bacd2702c73edff499bb0afd6e8 (diff)
downloadqutebrowser-decfd020331c2025f15e0e10da3c9e3e68a2281e.tar.gz
qutebrowser-decfd020331c2025f15e0e10da3c9e3e68a2281e.zip
Use a QNAM to pastebin from crash dialog.
Fixes #280.
-rw-r--r--qutebrowser/network/pastebin.py101
-rw-r--r--qutebrowser/utils/utils.py32
-rw-r--r--qutebrowser/widgets/crash.py56
3 files changed, 146 insertions, 43 deletions
diff --git a/qutebrowser/network/pastebin.py b/qutebrowser/network/pastebin.py
new file mode 100644
index 000000000..d32a7d3a8
--- /dev/null
+++ b/qutebrowser/network/pastebin.py
@@ -0,0 +1,101 @@
+# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
+
+# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
+#
+# 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/>.
+
+"""Client for the pastebin."""
+
+import functools
+import urllib.request
+import urllib.parse
+
+from PyQt5.QtCore import pyqtSignal, QObject, QUrl
+from PyQt5.QtNetwork import (QNetworkAccessManager, QNetworkRequest,
+ QNetworkReply)
+
+
+class PastebinClient(QObject):
+
+ """A client for http://p.cmpl.cc/ using QNetworkAccessManager.
+
+ Attributes:
+ _nam: The QNetworkAccessManager used.
+
+ Class attributes:
+ API_URL: The base API URL.
+
+ Signals:
+ success: Emitted when the paste succeeded.
+ arg: The URL of the paste, as string.
+ error: Emitted when the paste failed.
+ arg: The error message, as string.
+ """
+
+ API_URL = 'http://paste.the-compiler.org/api/'
+ success = pyqtSignal(str)
+ error = pyqtSignal(str)
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self._nam = QNetworkAccessManager(self)
+
+ def paste(self, name, title, text, parent=None):
+ """Paste the text into a pastebin and return the URL.
+
+ Args:
+ name: The username to post as.
+ title: The post title.
+ text: The text to post.
+ parent: The parent paste to reply to.
+ """
+ data = {
+ 'text': text,
+ 'title': title,
+ 'name': name,
+ }
+ if parent is not None:
+ data['reply'] = parent
+ encoded_data = urllib.parse.urlencode(data).encode('utf-8')
+ create_url = urllib.parse.urljoin(self.API_URL, 'create')
+ request = QNetworkRequest(QUrl(create_url))
+ request.setHeader(QNetworkRequest.ContentTypeHeader,
+ 'application/x-www-form-urlencoded;charset=utf-8')
+ reply = self._nam.post(request, encoded_data)
+ if reply.isFinished():
+ self.on_reply_finished(reply)
+ else:
+ reply.finished.connect(functools.partial(
+ self.on_reply_finished, reply))
+
+ def on_reply_finished(self, reply):
+ """Read the data and finish when the reply finished.
+
+ Args:
+ reply: The QNetworkReply which finished.
+ """
+ if reply.error() != QNetworkReply.NoError:
+ self.error.emit(reply.errorString())
+ return
+ try:
+ url = bytes(reply.readAll()).decode('utf-8')
+ except UnicodeDecodeError:
+ self.error.emit("Invalid UTF-8 data received in reply!")
+ return
+ if url.startswith('http://'):
+ self.success.emit(url)
+ else:
+ self.error.emit("Invalid data received in reply!")
diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py
index c10c36cf1..d47920c71 100644
--- a/qutebrowser/utils/utils.py
+++ b/qutebrowser/utils/utils.py
@@ -24,8 +24,6 @@ import sys
import enum
import inspect
import os.path
-import urllib.request
-import urllib.parse
import collections
import functools
import contextlib
@@ -85,36 +83,6 @@ def read_file(filename):
return data.decode('UTF-8')
-def pastebin(name, title, text, parent=None):
- """Paste the text into a pastebin and return the URL.
-
- Args:
- name: The username to post as.
- title: The post title.
- text: The text to post.
- parent: The parent paste to reply to.
- """
- api_url = 'http://paste.the-compiler.org/api/'
- data = {
- 'text': text,
- 'title': title,
- 'name': name,
- }
- if parent is not None:
- data['reply'] = parent
- encoded_data = urllib.parse.urlencode(data).encode('utf-8')
- create_url = urllib.parse.urljoin(api_url, 'create')
- headers = {
- 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
- }
- request = urllib.request.Request(create_url, encoded_data, headers)
- response = urllib.request.urlopen(request)
- url = response.read().decode('utf-8').rstrip()
- if not url.startswith('http'):
- raise ValueError("Got unexpected response: {}".format(url))
- return url
-
-
def actute_warning():
"""Display a warning about the dead_actute issue if needed."""
# WORKAROUND (remove this when we bump the requirements to 5.3.0)
diff --git a/qutebrowser/widgets/crash.py b/qutebrowser/widgets/crash.py
index 901f4ce4e..b21b8eb85 100644
--- a/qutebrowser/widgets/crash.py
+++ b/qutebrowser/widgets/crash.py
@@ -33,6 +33,7 @@ from PyQt5.QtWidgets import (QDialog, QLabel, QTextEdit, QPushButton,
from qutebrowser.utils import version, log, utils, objreg
from qutebrowser.widgets.misc import DetailFold
+from qutebrowser.network import pastebin
class _CrashDialog(QDialog):
@@ -50,6 +51,9 @@ class _CrashDialog(QDialog):
_hbox: The QHboxLayout containing the buttons
_url: Pastebin URL QLabel.
_crash_info: A list of tuples with title and crash information.
+ _paste_client: A PastebinClient instance to use.
+ _paste_text: The text to pastebin.
+ _resolution: Whether the dialog should be accepted on close.
"""
NAME = None
@@ -68,9 +72,12 @@ class _CrashDialog(QDialog):
self._hbox = None
self._lbl = None
self._chk_report = None
+ self._resolution = None
+ self._paste_text = None
self.setWindowTitle("Whoops!")
self.resize(QSize(640, 600))
self._vbox = QVBoxLayout(self)
+ self._paste_client = pastebin.PastebinClient(self)
self._init_text()
info = QLabel("What were you doing when this crash/bug happened?")
@@ -179,20 +186,20 @@ class _CrashDialog(QDialog):
lines.append(self._contact.toPlainText())
lines.append("========== Debug log ==========")
lines.append(self._debug_log.toPlainText())
- text = '\n\n'.join(lines)
+ self._paste_text = '\n\n'.join(lines)
try:
user = getpass.getuser()
except Exception as e:
log.misc.exception("Error while getting user")
user = 'unknown'
try:
- utils.pastebin(user, "qutebrowser {}".format(self.NAME), text,
- parent='90286958') # http://p.cmpl.cc/90286958
+ # parent: http://p.cmpl.cc/90286958
+ self._paste_client.paste(user, "qutebrowser {}".format(self.NAME),
+ self._paste_text, parent='90286958')
except Exception as e:
log.misc.exception("Error while paste-binning")
exc_text = '{}: {}'.format(e.__class__.__name__, e)
- error_dlg = ReportErrorDialog(exc_text, text, self)
- error_dlg.exec_()
+ self.show_error(exc_text)
@pyqtSlot()
def on_button_clicked(self, button, accept):
@@ -200,18 +207,44 @@ class _CrashDialog(QDialog):
button.setText("Reporting...")
for btn in self._buttons:
btn.setEnabled(False)
- self.hide()
- self.maybe_report()
- if accept:
+ self._resolution = accept
+ self._paste_client.success.connect(self.finish)
+ self._paste_client.error.connect(self.show_error)
+ reported = self.maybe_report()
+ if not reported:
+ self.finish()
+
+ @pyqtSlot(str)
+ def show_error(self, text):
+ """Show a paste error dialog.
+
+ Args:
+ text: The paste text to show.
+ """
+ error_dlg = ReportErrorDialog(text, self._paste_text, self)
+ error_dlg.finished.connect(self.finish)
+ error_dlg.show()
+
+ @pyqtSlot()
+ def finish(self):
+ """Accept/reject the dialog when reporting is done."""
+ if self._resolution:
self.accept()
else:
self.reject()
@pyqtSlot()
def maybe_report(self):
- """Report the bug if the user allowed us to."""
+ """Report the bug if the user allowed us to.
+
+ Return:
+ True if a report was done, False otherwise.
+ """
if self._chk_report.isChecked():
self.report()
+ return True
+ else:
+ return False
class ExceptionCrashDialog(_CrashDialog):
@@ -371,8 +404,8 @@ class ReportDialog(_CrashDialog):
def _init_buttons(self):
super()._init_buttons()
self._btn_report = QPushButton("Report", default=True)
- self._btn_report.clicked.connect(self.report)
- self._btn_report.clicked.connect(self.close)
+ self._btn_report.clicked.connect(
+ functools.partial(self.on_button_clicked, self._btn_report, True))
self._hbox.addWidget(self._btn_report)
def _init_checkboxes(self, _debug):
@@ -400,6 +433,7 @@ class ReportDialog(_CrashDialog):
report, which would be pretty useless without this info.
"""
self.report()
+ return True
class ReportErrorDialog(QDialog):