# Copyright (C) 2020 The Electrum developers # Distributed under the MIT software license, see the accompanying # file LICENCE or http://www.opensource.org/licenses/mit-license.php import asyncio import concurrent.futures from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QWidget, QVBoxLayout, QGridLayout, QLabel, QListWidget, QListWidgetItem from electrum.i18n import _ from electrum.network import Network from electrum.bip39_recovery import account_discovery from electrum.logging import get_logger from electrum.util import get_asyncio_loop, UserFacingException from .util import WindowModalDialog, MessageBoxMixin, TaskThread, Buttons, CancelButton, OkButton _logger = get_logger(__name__) class Bip39RecoveryDialog(WindowModalDialog): ROLE_ACCOUNT = Qt.UserRole def __init__(self, parent: QWidget, get_account_xpub, on_account_select): self.get_account_xpub = get_account_xpub self.on_account_select = on_account_select WindowModalDialog.__init__(self, parent, _('BIP39 Recovery')) self.setMinimumWidth(400) vbox = QVBoxLayout(self) self.content = QVBoxLayout() self.content.addWidget(QLabel(_('Scanning common paths for existing accounts...'))) vbox.addLayout(self.content) self.thread = TaskThread(self) self.thread.finished.connect(self.deleteLater) # see #3956 network = Network.get_instance() coro = account_discovery(network, self.get_account_xpub) fut = asyncio.run_coroutine_threadsafe(coro, get_asyncio_loop()) self.thread.add( fut.result, on_success=self.on_recovery_success, on_error=self.on_recovery_error, cancel=fut.cancel, ) self.ok_button = OkButton(self) self.ok_button.clicked.connect(self.on_ok_button_click) self.ok_button.setEnabled(False) cancel_button = CancelButton(self) cancel_button.clicked.connect(fut.cancel) vbox.addLayout(Buttons(cancel_button, self.ok_button)) self.finished.connect(self.on_finished) self.show() def on_finished(self): self.thread.stop() def on_ok_button_click(self): item = self.list.currentItem() account = item.data(self.ROLE_ACCOUNT) self.on_account_select(account) def on_recovery_success(self, accounts): self.clear_content() if len(accounts) == 0: self.content.addWidget(QLabel(_('No existing accounts found.'))) return self.content.addWidget(QLabel(_('Choose an account to restore.'))) self.list = QListWidget() for account in accounts: item = QListWidgetItem(account['description']) item.setData(self.ROLE_ACCOUNT, account) self.list.addItem(item) self.list.clicked.connect(lambda: self.ok_button.setEnabled(True)) self.content.addWidget(self.list) def on_recovery_error(self, exc_info): e = exc_info[1] if isinstance(e, concurrent.futures.CancelledError): return self.clear_content() msg = _('Error: Account discovery failed.') if isinstance(e, UserFacingException): msg += f"\n{e}" else: _logger.error(f"recovery error", exc_info=exc_info) self.content.addWidget(QLabel(msg)) def clear_content(self): for i in reversed(range(self.content.count())): self.content.itemAt(i).widget().setParent(None)