ttrezor: more user friendly when cannot connect - electrum - Electrum Bitcoin wallet
 (HTM) git clone https://git.parazyd.org/electrum
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Submodules
       ---
 (DIR) commit 16397b1ed772234bc617444fcdc215a2b4323adc
 (DIR) parent 317e6cea329c0cf32aa7750c8c33615a179e346c
 (HTM) Author: Neil Booth <kyuupichan@gmail.com>
       Date:   Sat,  6 Feb 2016 19:51:39 +0900
       
       ttrezor: 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).
       
       Diffstat:
         M gui/qt/installwizard.py             |      10 +++++-----
         M gui/qt/main_window.py               |       4 ++--
         M lib/plugins.py                      |      46 ++++++++++++++++---------------
         M lib/util.py                         |       6 ++++--
         M lib/wizard.py                       |       3 ---
         M plugins/hw_wallet/qt.py             |      12 ++++++++++++
         M plugins/trezor/clientbase.py        |       4 ++--
         M plugins/trezor/plugin.py            |       2 --
         M plugins/trezor/qt_generic.py        |       8 ++------
       
       9 files changed, 51 insertions(+), 44 deletions(-)
       ---
 (DIR) diff --git a/gui/qt/installwizard.py b/gui/qt/installwizard.py
       t@@ -1,4 +1,4 @@
       -from sys import stdout
       +import sys
        
        from PyQt4.QtGui import *
        from PyQt4.QtCore import *
       t@@ -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,
       t@@ -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]))
        
       t@@ -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
        
 (DIR) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py
       t@@ -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
       t@@ -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]))
        
 (DIR) diff --git a/lib/plugins.py b/lib/plugins.py
       t@@ -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):
       t@@ -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,
       t@@ -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
 (DIR) diff --git a/lib/util.py b/lib/util.py
       t@@ -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):
 (DIR) diff --git a/lib/wizard.py b/lib/wizard.py
       t@@ -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')
 (DIR) diff --git a/plugins/hw_wallet/qt.py b/plugins/hw_wallet/qt.py
       t@@ -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__()
       t@@ -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
       t@@ -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)
        
       t@@ -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()
 (DIR) diff --git a/plugins/trezor/clientbase.py b/plugins/trezor/clientbase.py
       t@@ -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):
       t@@ -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):
 (DIR) diff --git a/plugins/trezor/plugin.py b/plugins/trezor/plugin.py
       t@@ -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)
 (DIR) diff --git a/plugins/trezor/qt_generic.py b/plugins/trezor/qt_generic.py
       t@@ -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 "
       t@@ -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):