From 16397b1ed772234bc617444fcdc215a2b4323adc Mon Sep 17 00:00:00 2001 From: Neil Booth Date: Sat, 6 Feb 2016 19:51:39 +0900 Subject: [PATCH] trezor: more user friendly when cannot connect Tell the user and ask if they want to try again. If they say no, raise a silent exception. Apply this more friendly behaviour to the install wizard too (see issue #1668). --- gui/qt/installwizard.py | 10 ++++---- gui/qt/main_window.py | 4 ++-- lib/plugins.py | 46 +++++++++++++++++++----------------- lib/util.py | 6 +++-- lib/wizard.py | 3 --- plugins/hw_wallet/qt.py | 12 ++++++++++ plugins/trezor/clientbase.py | 4 ++-- plugins/trezor/plugin.py | 2 -- plugins/trezor/qt_generic.py | 8 ++----- 9 files changed, 51 insertions(+), 44 deletions(-) diff --git a/gui/qt/installwizard.py b/gui/qt/installwizard.py index ba3dabe53..7843a5929 100644 --- a/gui/qt/installwizard.py +++ b/gui/qt/installwizard.py @@ -1,4 +1,4 @@ -from sys import stdout +import sys from PyQt4.QtGui import * from PyQt4.QtCore import * @@ -14,8 +14,8 @@ from password_dialog import PasswordLayout, PW_NEW, PW_PASSPHRASE from electrum.wallet import Wallet from electrum.mnemonic import prepare_seed -from electrum.util import SilentException -from electrum.wizard import (WizardBase, UserCancelled, +from electrum.util import UserCancelled +from electrum.wizard import (WizardBase, MSG_ENTER_PASSWORD, MSG_RESTORE_PASSPHRASE, MSG_COSIGNER, MSG_ENTER_SEED_OR_MPK, MSG_SHOW_MPK, MSG_VERIFY_SEED, @@ -119,7 +119,7 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase): self.refresh_gui() def on_error(self, exc_info): - if not isinstance(exc_info[1], SilentException): + if not isinstance(exc_info[1], UserCancelled): traceback.print_exception(*exc_info) self.show_error(str(exc_info[1])) @@ -167,7 +167,7 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase): self.print_error("wallet creation cancelled by user") self.accept() # For when called from menu except BaseException as e: - self.show_error(str(e)) + self.on_error(sys.exc_info()) raise return wallet diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index ddec4f588..89244b4fd 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -40,7 +40,7 @@ from electrum.i18n import _ from electrum.util import (block_explorer, block_explorer_info, format_time, block_explorer_URL, format_satoshis, PrintError, format_satoshis_plain, NotEnoughFunds, StoreDict, - SilentException) + UserCancelled) from electrum import Transaction, mnemonic from electrum import util, bitcoin, commands from electrum import SimpleConfig, COIN_CHOOSERS, paymentrequest @@ -214,7 +214,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): self.raise_() def on_error(self, exc_info): - if not isinstance(exc_info[1], SilentException): + if not isinstance(exc_info[1], UserCancelled): traceback.print_exception(*exc_info) self.show_error(str(exc_info[1])) diff --git a/lib/plugins.py b/lib/plugins.py index 0a6754c29..fc33c0598 100644 --- a/lib/plugins.py +++ b/lib/plugins.py @@ -26,7 +26,7 @@ import time from util import * from i18n import _ -from util import profiler, PrintError, DaemonThread +from util import profiler, PrintError, DaemonThread, UserCancelled import wallet class Plugins(DaemonThread): @@ -386,26 +386,20 @@ class DeviceMgr(PrintError): # The wallet has not been previously paired, so let the user # choose an unpaired device and compare its first address. info = self.select_device(wallet, plugin, devices) - if info: - client = self.client_lookup(info.device.id_) - if client and client.is_pairable(): - # See comment above for same code - client.handler = wallet.handler - # This will trigger a PIN/passphrase entry request - client_first_address = client.first_address(derivation) - if client_first_address == first_address: - self.pair_wallet(wallet, info.device.id_) - return client - if info and client: - # The user input has wrong PIN or passphrase - raise DeviceUnpairableError( - _('Unable to pair with your %s.') % plugin.device) + client = self.client_lookup(info.device.id_) + if client and client.is_pairable(): + # See comment above for same code + client.handler = wallet.handler + # This will trigger a PIN/passphrase entry request + client_first_address = client.first_address(derivation) + if client_first_address == first_address: + self.pair_wallet(wallet, info.device.id_) + return client - raise DeviceNotFoundError( - _('Could not connect to your %s. Verify the cable is ' - 'connected and that no other application is using it.') - % plugin.device) + # The user input has wrong PIN or passphrase, or it is not pairable + raise DeviceUnpairableError( + _('Unable to pair with your %s.') % plugin.device) def unpaired_device_infos(self, handler, plugin, devices=None): '''Returns a list of DeviceInfo objects: one for each connected, @@ -432,9 +426,17 @@ class DeviceMgr(PrintError): def select_device(self, wallet, plugin, devices=None): '''Ask the user to select a device to use if there is more than one, and return the DeviceInfo for the device.''' - infos = self.unpaired_device_infos(wallet.handler, plugin, devices) - if not infos: - return None + while True: + infos = self.unpaired_device_infos(wallet.handler, plugin, devices) + if infos: + break + msg = _('Could not connect to your %s. Verify the cable is ' + 'connected and that no other application is using it.\n\n' + 'Try to connect again?') % plugin.device + if not wallet.handler.yes_no_question(msg): + raise UserCancelled() + devices = None + if len(infos) == 1: return infos[0] msg = _("Please select which %s device to use:") % plugin.device diff --git a/lib/util.py b/lib/util.py index 37ffa73fd..d70a61bbd 100644 --- a/lib/util.py +++ b/lib/util.py @@ -21,8 +21,10 @@ class InvalidPassword(Exception): def __str__(self): return _("Incorrect password") -class SilentException(Exception): - '''An exception that should probably be suppressed from the user''' +# Throw this exception to unwind the stack like when an error occurs. +# However unlike other exceptions the user won't be informed. +class UserCancelled(Exception): + '''An exception that is suppressed from the user''' pass class MyEncoder(json.JSONEncoder): diff --git a/lib/wizard.py b/lib/wizard.py index 07d91c7df..80a8c5261 100644 --- a/lib/wizard.py +++ b/lib/wizard.py @@ -36,9 +36,6 @@ MSG_RESTORE_PASSPHRASE = \ "Note this is NOT a password. Enter nothing if you did not use " "one or are unsure.") -class UserCancelled(Exception): - pass - class WizardBase(PrintError): '''Base class for gui-specific install wizards.''' user_actions = ('create', 'restore') diff --git a/plugins/hw_wallet/qt.py b/plugins/hw_wallet/qt.py index a8ac3ab85..f5272882a 100644 --- a/plugins/hw_wallet/qt.py +++ b/plugins/hw_wallet/qt.py @@ -34,6 +34,7 @@ class QtHandlerBase(QObject, PrintError): logic for handling I/O.''' qcSig = pyqtSignal(object, object) + ynSig = pyqtSignal(object) def __init__(self, win, device): super(QtHandlerBase, self).__init__() @@ -43,6 +44,7 @@ class QtHandlerBase(QObject, PrintError): win.connect(win, SIGNAL('passphrase_dialog'), self.passphrase_dialog) win.connect(win, SIGNAL('word_dialog'), self.word_dialog) self.qcSig.connect(self.win_query_choice) + self.ynSig.connect(self.win_yes_no_question) self.win = win self.device = device self.dialog = None @@ -60,6 +62,12 @@ class QtHandlerBase(QObject, PrintError): self.done.wait() return self.choice + def yes_no_question(self, msg): + self.done.clear() + self.ynSig.emit(msg) + self.done.wait() + return self.ok + def show_message(self, msg, on_cancel=None): self.win.emit(SIGNAL('message_dialog'), msg, on_cancel) @@ -126,3 +134,7 @@ class QtHandlerBase(QObject, PrintError): def win_query_choice(self, msg, labels): self.choice = self.win.query_choice(msg, labels) self.done.set() + + def win_yes_no_question(self, msg): + self.ok = self.top_level_window().question(msg) + self.done.set() diff --git a/plugins/trezor/clientbase.py b/plugins/trezor/clientbase.py index d1d81a499..a33a5c765 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, SilentException +from electrum.util import PrintError, UserCancelled class GuiMixin(object): @@ -27,7 +27,7 @@ class GuiMixin(object): # gets old very quickly, so we suppress those. if msg.code in (self.types.Failure_PinCancelled, self.types.Failure_ActionCancelled): - raise SilentException() + raise UserCancelled() raise RuntimeError(msg.message) def callback_ButtonRequest(self, msg): diff --git a/plugins/trezor/plugin.py b/plugins/trezor/plugin.py index db15772a0..af046fb03 100644 --- a/plugins/trezor/plugin.py +++ b/plugins/trezor/plugin.py @@ -220,8 +220,6 @@ class TrezorCompatiblePlugin(HW_PluginBase): process. Then create the wallet accounts.''' devmgr = self.device_manager() device_info = devmgr.select_device(wallet, self) - if not device_info: - raise RuntimeError(_("No devices found")) devmgr.pair_wallet(wallet, device_info.device.id_) if device_info.initialized: task = partial(wallet.create_hd_account, None) diff --git a/plugins/trezor/qt_generic.py b/plugins/trezor/qt_generic.py index 9db492bac..db906bbcb 100644 --- a/plugins/trezor/qt_generic.py +++ b/plugins/trezor/qt_generic.py @@ -11,9 +11,8 @@ from ..hw_wallet.qt import QtHandlerBase from electrum.i18n import _ from electrum.plugins import hook, DeviceMgr -from electrum.util import PrintError +from electrum.util import PrintError, UserCancelled from electrum.wallet import Wallet, BIP44_Wallet -from electrum.wizard import UserCancelled PASSPHRASE_HELP_SHORT =_( "Passphrases allow you to access new wallets, each " @@ -317,10 +316,7 @@ def qt_plugin_class(base_plugin_class): device_id = self.device_manager().wallet_id(window.wallet) if not device_id: info = self.device_manager().select_device(window.wallet, self) - if info: - device_id = info.device.id_ - else: - window.wallet.handler.show_error(_("No devices found")) + device_id = info.device.id_ return device_id def query_choice(self, window, msg, choices):