Commit 38a3b673 authored by Daniel Brötzmann's avatar Daniel Brötzmann Committed by Philipp Hörist
Browse files

Exceptions: Rework and basic Sentry integration

parent 32cf1968
......@@ -21,6 +21,7 @@ ### Runtime Requirements
### Optional Runtime Requirements
- python3-pil (pillow) for support of webp avatars
- python3-sentry-sdk for Sentry error reporting to dev.gajim.org (users decide whether to send reports or not)
- gir1.2-gspell-1 and hunspell-LANG where lang is your locale eg. en, fr etc
- gir1.2-secret-1 for GNOME Keyring or KDE support as password storage
- D-Bus running to have gajim-remote working
......
......@@ -126,6 +126,7 @@ def __init__(self):
'IDLE': False,
'APPINDICATOR': False,
'AYATANA_APPINDICATOR': False,
'SENTRY_SDK': False,
}
_tasks: dict[int, list[Any]] = defaultdict(list)
......@@ -277,6 +278,13 @@ def detect_dependencies() -> None:
except (ImportError, ValueError):
pass
# SENTRY SDK
try:
import sentry_sdk # pylint: disable=unused-import
_dependencies['SENTRY_SDK'] = True
except ImportError:
pass
# Print results
for dep, val in _dependencies.items():
log('gajim').info('%-13s %s', dep, val)
......
......@@ -78,6 +78,8 @@
from gi.repository import Gio
from gi.repository import GLib
from gi.repository import GObject
from gi.repository import Soup
import precis_i18n.codec # pylint: disable=unused-import
......@@ -757,6 +759,18 @@ def get_proxy(proxy_name: str) -> Optional[ProxyData]:
password=password)
def is_proxy_in_use() -> bool:
return get_global_proxy() is not None or is_account_proxy_in_use()
def is_account_proxy_in_use() -> bool:
for client in app.get_clients():
account_proxy = get_account_proxy(client.account, fallback=False)
if account_proxy is not None:
return True
return False
def version_condition(current_version: str, required_version: str) -> bool:
if V(current_version) < V(required_version):
return False
......@@ -1444,6 +1458,42 @@ def get_start_of_day(date_time: datetime) -> datetime:
microsecond=0)
def get_os_name() -> str:
if sys.platform in ('win32', 'darwin'):
return platform.system()
if os.name == 'posix':
try:
import distro
return distro.name(pretty=True)
except ImportError:
return platform.system()
return ''
def get_os_version() -> str:
if sys.platform in ('win32', 'darwin'):
return platform.release()
if os.name == 'posix':
try:
import distro
return distro.version(pretty=True)
except ImportError:
return platform.release()
return ''
def get_gobject_version() -> str:
gobject_ver = '.'.join(map(str, GObject.pygobject_version))
return gobject_ver
def get_glib_version() -> str:
glib_ver = '.'.join(map(str, [GLib.MAJOR_VERSION,
GLib.MINOR_VERSION,
GLib.MICRO_VERSION]))
return glib_ver
def make_path_from_jid(base_path: Path, jid: JID) -> Path:
assert jid.domain is not None
domain = jid.domain[:50]
......@@ -1455,3 +1505,21 @@ def make_path_from_jid(base_path: Path, jid: JID) -> Path:
if jid.resource is not None:
return path / jid.resource[:30]
return path
def make_http_request(uri: str, callback: Any) -> None:
proxy = get_global_proxy()
if proxy is None:
if is_account_proxy_in_use():
raise ValueError('No global proxy found, '
'but account proxies are in use')
resolver = None
else:
resolver = proxy.get_resolver()
session = Soup.Session()
session.props.proxy_resolver = resolver
session.props.user_agent = f'Gajim {app.version}'
message = Soup.Message.new('GET', uri)
session.queue_message(message, callback)
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.36.0 -->
<!-- Generated with glade 3.38.2 -->
<interface>
<requires lib="gtk+" version="3.22"/>
<object class="GtkApplicationWindow" id="exception_dialog">
<property name="can_focus">False</property>
<property name="window_position">center</property>
<property name="type_hint">dialog</property>
<requires lib="gtk+" version="3.24"/>
<object class="GtkBox" id="exception_box">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkInfoBar" id="infobar">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can-focus">False</property>
<property name="layout-style">end</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child internal-child="content_area">
<object class="GtkBox">
<property name="can-focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">dialog-information-symbolic</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Install &lt;tt&gt;sentry-sdk&lt;/tt&gt; to make reporting issues easier.</property>
<property name="use-markup">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="border_width">18</property>
<property name="can-focus">False</property>
<property name="border-width">18</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<property name="spacing">24</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">12</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">center</property>
<property name="icon_name">dialog-error</property>
<property name="can-focus">False</property>
<property name="valign">start</property>
<property name="icon-name">face-embarrassed</property>
<property name="icon_size">6</property>
</object>
<packing>
......@@ -33,22 +95,87 @@
</packing>
</child>
<child>
<object class="GtkBox">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Sorry, that should not have happened</property>
<property name="justify">center</property>
<property name="wrap">True</property>
<property name="max-width-chars">50</property>
<style>
<class name="large-header"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Gajim encountered an error.
Please help us fixing this issue by sending this report.</property>
<property name="justify">center</property>
<property name="wrap">True</property>
<property name="max-width-chars">60</property>
<property name="lines">3</property>
<style>
<class name="dim-label"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="height-request">200</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="margin-top">6</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkTextView" id="exception_view">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="editable">False</property>
<property name="left-margin">4</property>
<property name="right-margin">4</property>
<property name="top-margin">4</property>
<property name="bottom-margin">4</property>
<property name="accepts-tab">False</property>
<property name="monospace">True</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkBox" id="user_feedback_box">
<property name="can-focus">False</property>
<property name="halign">center</property>
<property name="margin-top">6</property>
<property name="spacing">12</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Sorry, that should not have happened</property>
<property name="label" translatable="yes">What happened</property>
<property name="wrap">True</property>
<property name="max_width_chars">30</property>
<property name="xalign">0</property>
<style>
<class name="large-header"/>
<class name="dim-label"/>
</style>
</object>
<packing>
......@@ -58,16 +185,13 @@
</packing>
</child>
<child>
<object class="GtkLabel">
<object class="GtkEntry" id="user_feedback_entry">
<property name="width-request">400</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can-focus">True</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Gajim encountered an error. A report is shown below.
By reporting this bug you might help people to fix this.</property>
<property name="wrap">True</property>
<property name="max_width_chars">60</property>
<property name="lines">3</property>
<property name="xalign">0</property>
<property name="activates-default">True</property>
<property name="placeholder-text" translatable="yes">Add some details…</property>
</object>
<packing>
<property name="expand">False</property>
......@@ -79,7 +203,7 @@ By reporting this bug you might help people to fix this.</property>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
<property name="position">4</property>
</packing>
</child>
</object>
......@@ -90,79 +214,72 @@ By reporting this bug you might help people to fix this.</property>
</packing>
</child>
<child>
<object class="GtkButtonBox">
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">6</property>
<property name="layout_style">end</property>
<property name="can-focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkButton" id="close_btn">
<property name="label">_Close</property>
<object class="GtkButton" id="close_button">
<property name="label" translatable="yes">_Close</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_underline">True</property>
<signal name="clicked" handler="on_close_clicked" swapped="no"/>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="use-underline">True</property>
<signal name="clicked" handler="_on_close_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack-type">end</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="report_btn">
<property name="label" translatable="yes">_Report Bug</property>
<object class="GtkButton" id="report_button">
<property name="label" translatable="yes">Send _Report</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_underline">True</property>
<signal name="clicked" handler="on_report_clicked" swapped="no"/>
<property name="can-focus">True</property>
<property name="can-default">True</property>
<property name="receives-default">True</property>
<property name="use-underline">True</property>
<signal name="clicked" handler="_on_report_clicked" swapped="no"/>
<style>
<class name="suggested-action"/>
</style>
</object>
<packing>
<property name="expand">True</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
<property name="pack-type">end</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="height_request">200</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="vexpand">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTextView" id="exception_view">
<object class="GtkSpinner" id="report_spinner">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="editable">False</property>
<property name="left_margin">4</property>
<property name="right_margin">4</property>
<property name="accepts_tab">False</property>
<property name="can-focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack-type">end</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
<property name="pack-type">end</property>
<property name="position">4</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</interface>
......@@ -313,10 +313,14 @@ class EmojiChooserBuilder(Builder):
class ExceptionDialogBuilder(Builder):
exception_dialog: Gtk.ApplicationWindow
close_btn: Gtk.Button
report_btn: Gtk.Button
exception_box: Gtk.Box
infobar: Gtk.InfoBar
exception_view: Gtk.TextView
user_feedback_box: Gtk.Box
user_feedback_entry: Gtk.Entry
close_button: Gtk.Button
report_button: Gtk.Button
report_spinner: Gtk.Spinner
class FileTransferBuilder(Builder):
......
......@@ -17,29 +17,46 @@
# You should have received a copy of the GNU General Public License
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
from typing import Type
from types import TracebackType
import sys
import os
import json
import traceback
import threading
import webbrowser
import platform
from io import StringIO
from urllib.parse import urlencode
from gi.repository import Gdk
from gi.repository import Gtk
from gi.repository import GObject
from gi.repository import GLib
from gi.repository import Soup
import nbxmpp
import gajim
from gajim.common import configpaths
from gajim.common import app
from gajim.common.helpers import get_glib_version
from gajim.common.helpers import get_gobject_version
from gajim.common.helpers import get_os_name
from gajim.common.helpers import get_os_version
from gajim.common.helpers import is_proxy_in_use
from gajim.common.helpers import make_http_request
from gajim.common.i18n import _
from .builder import get_builder
from .util import get_gtk_version
try:
import sentry_sdk
except ImportError:
pass
_exception_in_progress = threading.Lock()
ISSUE_TEXT = '''## Versions
ISSUE_URL = 'https://dev.gajim.org/gajim/gajim/issues/new'
ISSUE_TEXT = '''## Versions:
- OS: {}
- GTK Version: {}
- PyGObject Version: {}
......@@ -55,7 +72,10 @@
...'''
def _hook(type_, value, tb):
def _hook(type_: Type[BaseException],
value: BaseException,
tb: TracebackType
) -> None:
if not _exception_in_progress.acquire(False):
# Exceptions have piled up, so we use the default exception
# handler for such exceptions
......@@ -66,65 +86,159 @@ def _hook(type_, value, tb):
_exception_in_progress.release()
class ExceptionDialog():
def __init__(self, type_, value, tb):
path = configpaths.get('GUI') / 'exception_dialog.ui'
self._ui = get_builder(path.resolve())
self._ui.connect_signals(self)
class ExceptionDialog(Gtk.ApplicationWindow):
def __init__(self,
type_: Type[BaseException],
value: BaseException,
tb: TracebackType
) -> None:
Gtk.ApplicationWindow.__init__(self)
self.set_application(app.app)
self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
self.set_position(Gtk.WindowPosition.CENTER)
self.set_show_menubar(False)
self.set_resizable(True)
self.set_default_size(700, -1)
self.set_title(_('Gajim - Error'))
self._ui.report_btn.grab_focus()
self._traceback_data = (type_, value, tb)
self._sentry_available = app.is_installed('SENTRY_SDK')
self._ui = get_builder('exception_dialog.ui')
self.add(self._ui.exception_box)
if not self._sentry_available:
self._ui.user_feedback_box.set_no_show_all(True)
self._ui.infobar.set_revealed(True)
self._ui.report_button.grab_focus()
self._ui.report_button.grab_default()
buffer_ = self._ui.exception_view.get_buffer()
trace = StringIO()
traceback.print_exception(type_, value, tb, None, trace)
self.text = self.get_issue_text(trace.getvalue())
buffer_.set_text(self.text)
print(self.text, file=sys.stderr)
self._ui.exception_view.set_editable(False)
self._ui.exception_dialog.show()
self._issue_text = self._get_issue_text(trace.getvalue())
buffer_ = self._ui.exception_view.get_buffer()
buffer_.set_text(self._issue_text)
def on_report_clicked(self, *args):
issue_url = 'https://dev.gajim.org/gajim/gajim/issues/new'
params = {'issue[description]': self.text}
url = '{}?{}'.format(issue_url, urlencode(params))
self._ui.connect_signals(self)
self.show_all()
if self._sentry_available: