fixes https://github.com/spesmilo/electrum/issues/7750 Each task we schedule on `TaskThread` can provide an optional `cancel` method. When stopping `TaskThread`, we call this `cancel` method on all tasks in the queue. If the currently running task does not implement `cancel`, `TaskThread.stop` will block until that task finishes. Note that there is a significant change in behaviour here: `ElectrumWindow.run_coroutine_from_thread` and `ElectrumWindow.pay_lightning_invoice` previously serialised the execution of their coroutines via wallet.thread. This is no longer the case: they will now schedule coroutines immediately. So for example, the GUI now allows trying to pay multiple LN invoices "concurrently".
90 lines
3.3 KiB
Python
90 lines
3.3 KiB
Python
# 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 .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, network.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()
|
|
self.content.addWidget(QLabel(_('Error: Account discovery failed.')))
|
|
_logger.error(f"recovery error", exc_info=exc_info)
|
|
|
|
def clear_content(self):
|
|
for i in reversed(range(self.content.count())):
|
|
self.content.itemAt(i).widget().setParent(None)
|