From ab52c4a7c03e80c6939a19883eda02f48ff21452 Mon Sep 17 00:00:00 2001 From: MattHag <16444067+MattHag@users.noreply.github.com> Date: Sat, 9 Nov 2024 15:14:28 +0100 Subject: [PATCH] Introduce error types Related #2273 --- lib/solaar/listener.py | 10 ++++++---- lib/solaar/ui/action.py | 4 ++-- lib/solaar/ui/common.py | 23 +++++++++++++++-------- tests/solaar/ui/test_common.py | 6 +++--- 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/lib/solaar/listener.py b/lib/solaar/listener.py index 4416f4b31c..3846510146 100644 --- a/lib/solaar/listener.py +++ b/lib/solaar/listener.py @@ -25,6 +25,7 @@ from collections import namedtuple from functools import partial +from typing import Callable import gi import logitech_receiver @@ -38,6 +39,7 @@ from . import configuration from . import dbus from . import i18n +from .ui import common if typing.TYPE_CHECKING: from hidapi.common import DeviceInfo @@ -345,7 +347,7 @@ def ping_all(resuming=False): _error_callback = None # GUI callback to report errors -def setup_scanner(status_changed_callback, setting_changed_callback, error_callback): +def setup_scanner(status_changed_callback: Callable, setting_changed_callback: Callable, error_callback: Callable): global _status_callback, _error_callback, _setting_callback assert _status_callback is None, "scanner was already set-up" _status_callback = status_changed_callback @@ -368,11 +370,11 @@ def _process_add(device_info: DeviceInfo, retry): if retry: GLib.timeout_add(2000.0, _process_add, device_info, retry - 1) else: - _error_callback("permissions", device_info.path) + _error_callback(common.ErrorReason.PERMISSIONS, device_info.path) else: - _error_callback("nodevice", device_info.path) + _error_callback(common.ErrorReason.NO_DEVICE, device_info.path) except exceptions.NoReceiver: - _error_callback("nodevice", device_info.path) + _error_callback(common.ErrorReason.NO_DEVICE, device_info.path) # receiver add/remove events will start/stop listener threads diff --git a/lib/solaar/ui/action.py b/lib/solaar/ui/action.py index 4e4c777dca..0015f55371 100644 --- a/lib/solaar/ui/action.py +++ b/lib/solaar/ui/action.py @@ -19,9 +19,9 @@ from gi.repository import Gtk from solaar.i18n import _ +from solaar.ui import common from . import pair_window -from .common import error_dialog def make_image_menu_item(label, icon_name, function, *args): @@ -95,4 +95,4 @@ def unpair(window, device): try: del receiver[device_number] except Exception: - error_dialog("unpair", device) + common.error_dialog(common.ErrorReason.UNPAIR, device) diff --git a/lib/solaar/ui/common.py b/lib/solaar/ui/common.py index 93cab51653..9ccce1d88d 100644 --- a/lib/solaar/ui/common.py +++ b/lib/solaar/ui/common.py @@ -16,6 +16,7 @@ import logging +from enum import Enum from typing import Tuple import gi @@ -30,22 +31,28 @@ logger = logging.getLogger(__name__) -def _create_error_text(reason: str, object_) -> Tuple[str, str]: - if reason == "permissions": +class ErrorReason(Enum): + PERMISSIONS = "Permissions" + NO_DEVICE = "No device" + UNPAIR = "Unpair" + + +def _create_error_text(reason: ErrorReason, object_) -> Tuple[str, str]: + if reason == ErrorReason.PERMISSIONS: title = _("Permissions error") text = ( _("Found a Logitech receiver or device (%s), but did not have permission to open it.") % object_ + "\n\n" + _("If you've just installed Solaar, try disconnecting the receiver or device and then reconnecting it.") ) - elif reason == "nodevice": + elif reason == ErrorReason.NO_DEVICE: title = _("Cannot connect to device error") text = ( _("Found a Logitech receiver or device at %s, but encountered an error connecting to it.") % object_ + "\n\n" + _("Try disconnecting the device and then reconnecting it or turning it off and then on.") ) - elif reason == "unpair": + elif reason == ErrorReason.UNPAIR: title = _("Unpairing failed") text = ( _("Failed to unpair %{device} from %{receiver}.").format( @@ -56,11 +63,11 @@ def _create_error_text(reason: str, object_) -> Tuple[str, str]: + _("The receiver returned an error, with no further details.") ) else: - raise Exception("ui.error_dialog: don't know how to handle (%s, %s)", reason, object_) + raise Exception("ui.error_dialog: don't know how to handle (%s, %s)", reason.name, object_) return title, text -def _error_dialog(reason: str, object_): +def _error_dialog(reason: ErrorReason, object_): logger.error("error: %s %s", reason, object_) title, text = _create_error_text(reason, object_) @@ -70,8 +77,7 @@ def _error_dialog(reason: str, object_): m.destroy() -def error_dialog(reason, object_): - assert reason is not None +def error_dialog(reason: ErrorReason, object_): GLib.idle_add(_error_dialog, reason, object_) @@ -91,5 +97,6 @@ def stop_async(): def ui_async(function, *args, **kwargs): + """Runs a function asynchronously.""" if _task_runner: _task_runner(function, *args, **kwargs) diff --git a/tests/solaar/ui/test_common.py b/tests/solaar/ui/test_common.py index 0c3a33dc1b..92c9e1c681 100644 --- a/tests/solaar/ui/test_common.py +++ b/tests/solaar/ui/test_common.py @@ -10,12 +10,12 @@ "reason, expected_in_title, expected_in_text", [ ( - "permissions", + common.ErrorReason.PERMISSIONS, "Permissions error", "not have permission to open", ), - ("nodevice", "connect to device error", "error connecting"), - ("unpair", "Unpairing failed", "receiver returned an error"), + (common.ErrorReason.NO_DEVICE, "connect to device error", "error connecting"), + (common.ErrorReason.UNPAIR, "Unpairing failed", "receiver returned an error"), ], ) def test_create_error_text(reason, expected_in_title, expected_in_text):