From 06c262d0dc4c90c0478234d51e14df1fb7091bcc Mon Sep 17 00:00:00 2001 From: Neil Booth Date: Sun, 17 Jan 2016 19:38:32 +0900 Subject: [PATCH] TrezorClient: should be in a separate thread First steps; get show_address working. Client is not responsible for showing exceptions. Suppress uninteresting exceptions. --- gui/qt/main_window.py | 14 +++++++++----- lib/util.py | 4 ++++ plugins/trezor/clientbase.py | 16 ++++++++++++---- plugins/trezor/plugin.py | 4 ++++ plugins/trezor/qt_generic.py | 7 ++++--- 5 files changed, 33 insertions(+), 12 deletions(-) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index 8131a32b..6e6673b4 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -37,9 +37,10 @@ import icons_rc from electrum.bitcoin import COIN, is_valid, TYPE_ADDRESS from electrum.plugins import run_hook from electrum.i18n import _ -from electrum.util import block_explorer, block_explorer_info, block_explorer_URL -from electrum.util import format_satoshis, format_satoshis_plain, format_time -from electrum.util import PrintError, NotEnoughFunds, StoreDict +from electrum.util import (block_explorer, block_explorer_info, format_time, + block_explorer_URL, format_satoshis, PrintError, + format_satoshis_plain, NotEnoughFunds, StoreDict, + SilentException) from electrum import Transaction, mnemonic from electrum import util, bitcoin, commands from electrum import SimpleConfig, COIN_CHOOSERS, paymentrequest @@ -198,8 +199,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): self.raise_() def on_error(self, exc_info): - traceback.print_exception(*exc_info) - self.show_error(str(exc_info[1])) + if not isinstance(exc_info[1], SilentException): + traceback.print_exception(*exc_info) + self.show_error(str(exc_info[1])) def on_network(self, event, *args): if event == 'updated': @@ -254,6 +256,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): run_hook('close_wallet', self.wallet) def load_wallet(self, wallet): + wallet.thread = TaskThread(self, self.on_error) self.wallet = wallet self.update_recently_visited(wallet.storage.path) self.import_old_contacts() @@ -2856,6 +2859,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): event.accept() def clean_up(self): + self.wallet.thread.stop() if self.network: self.network.unregister_callback(self.on_network) self.config.set_key("is_maximized", self.isMaximized()) diff --git a/lib/util.py b/lib/util.py index 4bf3e36c..37ffa73f 100644 --- a/lib/util.py +++ b/lib/util.py @@ -21,6 +21,10 @@ class InvalidPassword(Exception): def __str__(self): return _("Incorrect password") +class SilentException(Exception): + '''An exception that should probably be suppressed from the user''' + pass + class MyEncoder(json.JSONEncoder): def default(self, obj): from transaction import Transaction diff --git a/plugins/trezor/clientbase.py b/plugins/trezor/clientbase.py index c4557cc1..8500bc57 100644 --- a/plugins/trezor/clientbase.py +++ b/plugins/trezor/clientbase.py @@ -1,7 +1,7 @@ from sys import stderr from electrum.i18n import _ -from electrum.util import PrintError +from electrum.util import PrintError, SilentException class GuiMixin(object): @@ -20,6 +20,16 @@ class GuiMixin(object): 'passphrase': _("Confirm on %s device to continue"), } + def callback_Failure(self, msg): + # BaseClient's unfortunate call() implementation forces us to + # raise exceptions on failure in order to unwind the stack. + # However, making the user acknowledge they cancelled + # gets old very quickly, so we suppress those. + if msg.code in (self.types.Failure_PinCancelled, + self.types.Failure_ActionCancelled): + raise SilentException() + raise RuntimeError(msg.message) + def callback_ButtonRequest(self, msg): msg_code = self.msg_code_override or msg.code message = self.messages.get(msg_code, self.messages['default']) @@ -65,6 +75,7 @@ class TrezorClientBase(GuiMixin, PrintError): self.handler = handler self.hid_id_ = hid_id self.tx_api = plugin + self.types = plugin.types self.msg_code_override = None def __str__(self): @@ -172,9 +183,6 @@ class TrezorClientBase(GuiMixin, PrintError): def wrapped(self, *args, **kwargs): try: return func(self, *args, **kwargs) - except BaseException as e: - self.handler.show_error(str(e)) - raise e finally: self.handler.finished() diff --git a/plugins/trezor/plugin.py b/plugins/trezor/plugin.py index 9b58d9b7..50d4b494 100644 --- a/plugins/trezor/plugin.py +++ b/plugins/trezor/plugin.py @@ -1,5 +1,6 @@ import base64 import re +import threading import time from binascii import unhexlify @@ -172,6 +173,7 @@ class TrezorCompatiblePlugin(BasePlugin, ThreadJob): def __init__(self, parent, config, name): BasePlugin.__init__(self, parent, config, name) + self.main_thread = threading.current_thread() self.device = self.wallet_class.device self.wallet_class.plugin = self self.prevent_timeout = time.time() + 3600 * 24 * 365 @@ -216,6 +218,8 @@ class TrezorCompatiblePlugin(BasePlugin, ThreadJob): return self.client_class(transport, handler, self, hid_id) def get_client(self, wallet, force_pair=True, check_firmware=True): + assert self.main_thread != threading.current_thread() + '''check_firmware is ignored unless force_pair is True.''' client = self.device_manager().get_client(wallet, force_pair) diff --git a/plugins/trezor/qt_generic.py b/plugins/trezor/qt_generic.py index 58d5da7e..2675cb3c 100644 --- a/plugins/trezor/qt_generic.py +++ b/plugins/trezor/qt_generic.py @@ -231,7 +231,7 @@ def qt_plugin_class(base_plugin_class): window.statusBar().addPermanentWidget(window.tzb) wallet.handler = self.create_handler(window) # Trigger a pairing - self.get_client(wallet) + wallet.thread.add(partial(self.get_client, wallet)) def on_create_wallet(self, wallet, wizard): assert type(wallet) == self.wallet_class @@ -242,8 +242,9 @@ def qt_plugin_class(base_plugin_class): @hook def receive_menu(self, menu, addrs, wallet): if type(wallet) == self.wallet_class and len(addrs) == 1: - menu.addAction(_("Show on %s") % self.device, - lambda: self.show_address(wallet, addrs[0])) + def show_address(): + wallet.thread.add(partial(self.show_address, wallet, addrs[0])) + menu.addAction(_("Show on %s") % self.device, show_address) def settings_dialog(self, window): hid_id = self.choose_device(window)