Use a QNAM to pastebin from crash dialog.

Fixes #280.
This commit is contained in:
Florian Bruhin 2014-12-11 23:34:03 +01:00
parent aefa637bc5
commit decfd02033
3 changed files with 146 additions and 43 deletions

View File

@ -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!")

View File

@ -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)

View File

@ -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):