Browse Source

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).
283
Neil Booth 9 years ago
parent
commit
16397b1ed7
  1. 10
      gui/qt/installwizard.py
  2. 4
      gui/qt/main_window.py
  3. 24
      lib/plugins.py
  4. 6
      lib/util.py
  5. 3
      lib/wizard.py
  6. 12
      plugins/hw_wallet/qt.py
  7. 4
      plugins/trezor/clientbase.py
  8. 2
      plugins/trezor/plugin.py
  9. 6
      plugins/trezor/qt_generic.py

10
gui/qt/installwizard.py

@ -1,4 +1,4 @@
from sys import stdout import sys
from PyQt4.QtGui import * from PyQt4.QtGui import *
from PyQt4.QtCore 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.wallet import Wallet
from electrum.mnemonic import prepare_seed from electrum.mnemonic import prepare_seed
from electrum.util import SilentException from electrum.util import UserCancelled
from electrum.wizard import (WizardBase, UserCancelled, from electrum.wizard import (WizardBase,
MSG_ENTER_PASSWORD, MSG_RESTORE_PASSPHRASE, MSG_ENTER_PASSWORD, MSG_RESTORE_PASSPHRASE,
MSG_COSIGNER, MSG_ENTER_SEED_OR_MPK, MSG_COSIGNER, MSG_ENTER_SEED_OR_MPK,
MSG_SHOW_MPK, MSG_VERIFY_SEED, MSG_SHOW_MPK, MSG_VERIFY_SEED,
@ -119,7 +119,7 @@ class InstallWizard(QDialog, MessageBoxMixin, WizardBase):
self.refresh_gui() self.refresh_gui()
def on_error(self, exc_info): 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) traceback.print_exception(*exc_info)
self.show_error(str(exc_info[1])) 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.print_error("wallet creation cancelled by user")
self.accept() # For when called from menu self.accept() # For when called from menu
except BaseException as e: except BaseException as e:
self.show_error(str(e)) self.on_error(sys.exc_info())
raise raise
return wallet return wallet

4
gui/qt/main_window.py

@ -40,7 +40,7 @@ from electrum.i18n import _
from electrum.util import (block_explorer, block_explorer_info, format_time, from electrum.util import (block_explorer, block_explorer_info, format_time,
block_explorer_URL, format_satoshis, PrintError, block_explorer_URL, format_satoshis, PrintError,
format_satoshis_plain, NotEnoughFunds, StoreDict, format_satoshis_plain, NotEnoughFunds, StoreDict,
SilentException) UserCancelled)
from electrum import Transaction, mnemonic from electrum import Transaction, mnemonic
from electrum import util, bitcoin, commands from electrum import util, bitcoin, commands
from electrum import SimpleConfig, COIN_CHOOSERS, paymentrequest from electrum import SimpleConfig, COIN_CHOOSERS, paymentrequest
@ -214,7 +214,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.raise_() self.raise_()
def on_error(self, exc_info): 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) traceback.print_exception(*exc_info)
self.show_error(str(exc_info[1])) self.show_error(str(exc_info[1]))

24
lib/plugins.py

@ -26,7 +26,7 @@ import time
from util import * from util import *
from i18n import _ from i18n import _
from util import profiler, PrintError, DaemonThread from util import profiler, PrintError, DaemonThread, UserCancelled
import wallet import wallet
class Plugins(DaemonThread): class Plugins(DaemonThread):
@ -386,7 +386,7 @@ class DeviceMgr(PrintError):
# The wallet has not been previously paired, so let the user # The wallet has not been previously paired, so let the user
# choose an unpaired device and compare its first address. # choose an unpaired device and compare its first address.
info = self.select_device(wallet, plugin, devices) info = self.select_device(wallet, plugin, devices)
if info:
client = self.client_lookup(info.device.id_) client = self.client_lookup(info.device.id_)
if client and client.is_pairable(): if client and client.is_pairable():
# See comment above for same code # See comment above for same code
@ -397,16 +397,10 @@ class DeviceMgr(PrintError):
self.pair_wallet(wallet, info.device.id_) self.pair_wallet(wallet, info.device.id_)
return client return client
if info and client: # The user input has wrong PIN or passphrase, or it is not pairable
# The user input has wrong PIN or passphrase
raise DeviceUnpairableError( raise DeviceUnpairableError(
_('Unable to pair with your %s.') % plugin.device) _('Unable to pair with your %s.') % plugin.device)
raise DeviceNotFoundError(
_('Could not connect to your %s. Verify the cable is '
'connected and that no other application is using it.')
% plugin.device)
def unpaired_device_infos(self, handler, plugin, devices=None): def unpaired_device_infos(self, handler, plugin, devices=None):
'''Returns a list of DeviceInfo objects: one for each connected, '''Returns a list of DeviceInfo objects: one for each connected,
unpaired device accepted by the plugin.''' unpaired device accepted by the plugin.'''
@ -432,9 +426,17 @@ class DeviceMgr(PrintError):
def select_device(self, wallet, plugin, devices=None): def select_device(self, wallet, plugin, devices=None):
'''Ask the user to select a device to use if there is more than one, '''Ask the user to select a device to use if there is more than one,
and return the DeviceInfo for the device.''' and return the DeviceInfo for the device.'''
while True:
infos = self.unpaired_device_infos(wallet.handler, plugin, devices) infos = self.unpaired_device_infos(wallet.handler, plugin, devices)
if not infos: if infos:
return None 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: if len(infos) == 1:
return infos[0] return infos[0]
msg = _("Please select which %s device to use:") % plugin.device msg = _("Please select which %s device to use:") % plugin.device

6
lib/util.py

@ -21,8 +21,10 @@ class InvalidPassword(Exception):
def __str__(self): def __str__(self):
return _("Incorrect password") return _("Incorrect password")
class SilentException(Exception): # Throw this exception to unwind the stack like when an error occurs.
'''An exception that should probably be suppressed from the user''' # However unlike other exceptions the user won't be informed.
class UserCancelled(Exception):
'''An exception that is suppressed from the user'''
pass pass
class MyEncoder(json.JSONEncoder): class MyEncoder(json.JSONEncoder):

3
lib/wizard.py

@ -36,9 +36,6 @@ MSG_RESTORE_PASSPHRASE = \
"Note this is NOT a password. Enter nothing if you did not use " "Note this is NOT a password. Enter nothing if you did not use "
"one or are unsure.") "one or are unsure.")
class UserCancelled(Exception):
pass
class WizardBase(PrintError): class WizardBase(PrintError):
'''Base class for gui-specific install wizards.''' '''Base class for gui-specific install wizards.'''
user_actions = ('create', 'restore') user_actions = ('create', 'restore')

12
plugins/hw_wallet/qt.py

@ -34,6 +34,7 @@ class QtHandlerBase(QObject, PrintError):
logic for handling I/O.''' logic for handling I/O.'''
qcSig = pyqtSignal(object, object) qcSig = pyqtSignal(object, object)
ynSig = pyqtSignal(object)
def __init__(self, win, device): def __init__(self, win, device):
super(QtHandlerBase, self).__init__() 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('passphrase_dialog'), self.passphrase_dialog)
win.connect(win, SIGNAL('word_dialog'), self.word_dialog) win.connect(win, SIGNAL('word_dialog'), self.word_dialog)
self.qcSig.connect(self.win_query_choice) self.qcSig.connect(self.win_query_choice)
self.ynSig.connect(self.win_yes_no_question)
self.win = win self.win = win
self.device = device self.device = device
self.dialog = None self.dialog = None
@ -60,6 +62,12 @@ class QtHandlerBase(QObject, PrintError):
self.done.wait() self.done.wait()
return self.choice 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): def show_message(self, msg, on_cancel=None):
self.win.emit(SIGNAL('message_dialog'), msg, on_cancel) self.win.emit(SIGNAL('message_dialog'), msg, on_cancel)
@ -126,3 +134,7 @@ class QtHandlerBase(QObject, PrintError):
def win_query_choice(self, msg, labels): def win_query_choice(self, msg, labels):
self.choice = self.win.query_choice(msg, labels) self.choice = self.win.query_choice(msg, labels)
self.done.set() self.done.set()
def win_yes_no_question(self, msg):
self.ok = self.top_level_window().question(msg)
self.done.set()

4
plugins/trezor/clientbase.py

@ -1,7 +1,7 @@
from sys import stderr from sys import stderr
from electrum.i18n import _ from electrum.i18n import _
from electrum.util import PrintError, SilentException from electrum.util import PrintError, UserCancelled
class GuiMixin(object): class GuiMixin(object):
@ -27,7 +27,7 @@ class GuiMixin(object):
# gets old very quickly, so we suppress those. # gets old very quickly, so we suppress those.
if msg.code in (self.types.Failure_PinCancelled, if msg.code in (self.types.Failure_PinCancelled,
self.types.Failure_ActionCancelled): self.types.Failure_ActionCancelled):
raise SilentException() raise UserCancelled()
raise RuntimeError(msg.message) raise RuntimeError(msg.message)
def callback_ButtonRequest(self, msg): def callback_ButtonRequest(self, msg):

2
plugins/trezor/plugin.py

@ -220,8 +220,6 @@ class TrezorCompatiblePlugin(HW_PluginBase):
process. Then create the wallet accounts.''' process. Then create the wallet accounts.'''
devmgr = self.device_manager() devmgr = self.device_manager()
device_info = devmgr.select_device(wallet, self) device_info = devmgr.select_device(wallet, self)
if not device_info:
raise RuntimeError(_("No devices found"))
devmgr.pair_wallet(wallet, device_info.device.id_) devmgr.pair_wallet(wallet, device_info.device.id_)
if device_info.initialized: if device_info.initialized:
task = partial(wallet.create_hd_account, None) task = partial(wallet.create_hd_account, None)

6
plugins/trezor/qt_generic.py

@ -11,9 +11,8 @@ from ..hw_wallet.qt import QtHandlerBase
from electrum.i18n import _ from electrum.i18n import _
from electrum.plugins import hook, DeviceMgr 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.wallet import Wallet, BIP44_Wallet
from electrum.wizard import UserCancelled
PASSPHRASE_HELP_SHORT =_( PASSPHRASE_HELP_SHORT =_(
"Passphrases allow you to access new wallets, each " "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) device_id = self.device_manager().wallet_id(window.wallet)
if not device_id: if not device_id:
info = self.device_manager().select_device(window.wallet, self) info = self.device_manager().select_device(window.wallet, self)
if info:
device_id = info.device.id_ device_id = info.device.id_
else:
window.wallet.handler.show_error(_("No devices found"))
return device_id return device_id
def query_choice(self, window, msg, choices): def query_choice(self, window, msg, choices):

Loading…
Cancel
Save