From 0228169852b50728e6bd8815bc0e0f6d68a6b9e5 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Thu, 7 Jul 2022 19:10:20 +0200 Subject: [PATCH] refactor to new event listener framework --- electrum/gui/qml/qechanneldetails.py | 17 ++- electrum/gui/qml/qechannellistmodel.py | 41 +++---- electrum/gui/qml/qedaemon.py | 4 +- electrum/gui/qml/qefx.py | 18 ++-- electrum/gui/qml/qeinvoice.py | 4 +- electrum/gui/qml/qenetwork.py | 30 +++--- electrum/gui/qml/qewallet.py | 143 +++++++++++++++---------- electrum/gui/qml/util.py | 31 ++++++ 8 files changed, 175 insertions(+), 113 deletions(-) create mode 100644 electrum/gui/qml/util.py diff --git a/electrum/gui/qml/qechanneldetails.py b/electrum/gui/qml/qechanneldetails.py index 527083fb4..020a17568 100644 --- a/electrum/gui/qml/qechanneldetails.py +++ b/electrum/gui/qml/qechanneldetails.py @@ -5,14 +5,14 @@ from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, Q_ENUMS from electrum.i18n import _ from electrum.gui import messages from electrum.logging import get_logger -from electrum.util import register_callback, unregister_callback from electrum.lnutil import LOCAL, REMOTE from electrum.lnchannel import ChanCloseOption from .qewallet import QEWallet from .qetypes import QEAmount +from .util import QtEventListener, qt_event_listener -class QEChannelDetails(QObject): +class QEChannelDetails(QObject, QtEventListener): _logger = get_logger(__name__) _wallet = None @@ -25,17 +25,16 @@ class QEChannelDetails(QObject): def __init__(self, parent=None): super().__init__(parent) - register_callback(self.on_network, ['channel']) + self.register_callbacks() self.destroyed.connect(lambda: self.on_destroy()) - def on_network(self, event, *args): - if event == 'channel': - wallet, channel = args - if wallet == self._wallet.wallet and self._channelid == channel.channel_id.hex(): - self.channelChanged.emit() + @qt_event_listener + def on_event_channel(self, wallet, channel): + if wallet == self._wallet.wallet and self._channelid == channel.channel_id.hex(): + self.channelChanged.emit() def on_destroy(self): - unregister_callback(self.on_network) + self.unregister_callbacks() walletChanged = pyqtSignal() @pyqtProperty(QEWallet, notify=walletChanged) diff --git a/electrum/gui/qml/qechannellistmodel.py b/electrum/gui/qml/qechannellistmodel.py index 09a4f15f0..94bfcbb46 100644 --- a/electrum/gui/qml/qechannellistmodel.py +++ b/electrum/gui/qml/qechannellistmodel.py @@ -4,13 +4,14 @@ from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject from PyQt5.QtCore import Qt, QAbstractListModel, QModelIndex from electrum.logging import get_logger -from electrum.util import Satoshis, register_callback, unregister_callback +from electrum.util import Satoshis from electrum.lnutil import LOCAL, REMOTE from electrum.lnchannel import ChannelState from .qetypes import QEAmount +from .util import QtEventListener, qt_event_listener -class QEChannelListModel(QAbstractListModel): +class QEChannelListModel(QAbstractListModel, QtEventListener): _logger = get_logger(__name__) # define listmodel rolemap @@ -28,38 +29,26 @@ class QEChannelListModel(QAbstractListModel): self.wallet = wallet self.init_model() - self._network_signal.connect(self.on_network_qt) - interests = ['channel', 'channels_updated', 'gossip_peers', - 'ln_gossip_sync_progress', 'unknown_channels', - 'channel_db', 'gossip_db_loaded'] # To avoid leaking references to "self" that prevent the # window from being GC-ed when closed, callbacks should be # methods of this class only, and specifically not be # partials, lambdas or methods of subobjects. Hence... - register_callback(self.on_network, interests) + self.register_callbacks() self.destroyed.connect(lambda: self.on_destroy()) - def on_network(self, event, *args): - if event in ['channel','channels_updated']: - # Handle in GUI thread (_network_signal -> on_network_qt) - self._network_signal.emit(event, args) - else: - self.on_network_qt(event, args) - - def on_network_qt(self, event, args=None): - if event == 'channel': - wallet, channel = args - if wallet == self.wallet: - self.on_channel_updated(channel) - elif event == 'channels_updated': - wallet, = args - if wallet == self.wallet: - self.init_model() # TODO: remove/add less crude than full re-init - else: - self._logger.debug('unhandled event %s: %s' % (event, repr(args))) + @qt_event_listener + def on_event_channel(self, wallet, channel): + if wallet == self.wallet: + self.on_channel_updated(channel) + + # elif event == 'channels_updated': + @qt_event_listener + def on_event_channels_updated(self, wallet): + if wallet == self.wallet: + self.init_model() # TODO: remove/add less crude than full re-init def on_destroy(self): - unregister_callback(self.on_network) + self.unregister_callbacks() def rowCount(self, index): return len(self.channels) diff --git a/electrum/gui/qml/qedaemon.py b/electrum/gui/qml/qedaemon.py index 683cee94a..83c952710 100644 --- a/electrum/gui/qml/qedaemon.py +++ b/electrum/gui/qml/qedaemon.py @@ -6,7 +6,7 @@ from PyQt5.QtCore import Qt, QAbstractListModel, QModelIndex from electrum.util import register_callback, get_new_wallet_name, WalletFileException from electrum.logging import get_logger -from electrum.wallet import Wallet, Abstract_Wallet, update_password_for_directory +from electrum.wallet import Wallet, Abstract_Wallet from electrum.storage import WalletStorage, StorageReadWriteError from electrum.wallet_db import WalletDB @@ -147,7 +147,7 @@ class QEDaemon(AuthMixin, QObject): self.walletLoaded.emit() if self.daemon.config.get('single_password'): - self._use_single_password = update_password_for_directory(self.daemon.config, password, password) + self._use_single_password = self.daemon.update_password_for_directory(old_password=password, new_password=password) self._password = password self._logger.info(f'use single password: {self._use_single_password}') diff --git a/electrum/gui/qml/qefx.py b/electrum/gui/qml/qefx.py index 2de44e567..580910025 100644 --- a/electrum/gui/qml/qefx.py +++ b/electrum/gui/qml/qefx.py @@ -6,28 +6,34 @@ from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject from electrum.logging import get_logger from electrum.exchange_rate import FxThread from electrum.simple_config import SimpleConfig -from electrum.util import register_callback from electrum.bitcoin import COIN from .qetypes import QEAmount +from .util import QtEventListener, qt_event_listener -class QEFX(QObject): +class QEFX(QObject, QtEventListener): def __init__(self, fxthread: FxThread, config: SimpleConfig, parent=None): super().__init__(parent) self.fx = fxthread self.config = config - register_callback(self.on_quotes, ['on_quotes']) - register_callback(self.on_history, ['on_history']) + self.register_callbacks() + self.destroyed.connect(lambda: self.on_destroy()) _logger = get_logger(__name__) quotesUpdated = pyqtSignal() - def on_quotes(self, event, *args): + + def on_destroy(self): + self.unregister_callbacks() + + @qt_event_listener + def on_event_on_quotes(self, *args): self._logger.debug('new quotes') self.quotesUpdated.emit() historyUpdated = pyqtSignal() - def on_history(self, event, *args): + @qt_event_listener + def on_event_on_history(self, *args): self._logger.debug('new history') self.historyUpdated.emit() diff --git a/electrum/gui/qml/qeinvoice.py b/electrum/gui/qml/qeinvoice.py index 6de0b5715..63dbdec51 100644 --- a/electrum/gui/qml/qeinvoice.py +++ b/electrum/gui/qml/qeinvoice.py @@ -6,7 +6,7 @@ from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, Q_ENUMS from electrum.logging import get_logger from electrum.i18n import _ from electrum.util import (parse_URI, create_bip21_uri, InvalidBitcoinURI, InvoiceError, - maybe_extract_bolt11_invoice) + maybe_extract_lightning_payment_identifier) from electrum.invoices import Invoice from electrum.invoices import (PR_UNPAID,PR_EXPIRED,PR_UNKNOWN,PR_PAID,PR_INFLIGHT, PR_FAILED,PR_ROUTING,PR_UNCONFIRMED,LN_EXPIRY_NEVER) @@ -335,7 +335,7 @@ class QEInvoiceParser(QEInvoice): lninvoice = None try: - maybe_lightning_invoice = maybe_extract_bolt11_invoice(maybe_lightning_invoice) + maybe_lightning_invoice = maybe_extract_lightning_payment_identifier(maybe_lightning_invoice) lninvoice = Invoice.from_bech32(maybe_lightning_invoice) except InvoiceError as e: pass diff --git a/electrum/gui/qml/qenetwork.py b/electrum/gui/qml/qenetwork.py index 73bad2856..190efc6a5 100644 --- a/electrum/gui/qml/qenetwork.py +++ b/electrum/gui/qml/qenetwork.py @@ -1,20 +1,16 @@ from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject -from electrum.util import register_callback from electrum.logging import get_logger from electrum import constants from electrum.interface import ServerAddr -class QENetwork(QObject): +from .util import QtEventListener, qt_event_listener + +class QENetwork(QObject, QtEventListener): def __init__(self, network, parent=None): super().__init__(parent) self.network = network - register_callback(self.on_network_updated, ['network_updated']) - register_callback(self.on_blockchain_updated, ['blockchain_updated']) - register_callback(self.on_default_server_changed, ['default_server_changed']) - register_callback(self.on_proxy_set, ['proxy_set']) - register_callback(self.on_status, ['status']) - register_callback(self.on_fee_histogram, ['fee_histogram']) + self.register_callbacks() _logger = get_logger(__name__) @@ -33,30 +29,36 @@ class QENetwork(QObject): _height = 0 _status = "" - def on_network_updated(self, event, *args): + @qt_event_listener + def on_event_network_updated(self, *args): self.networkUpdated.emit() - def on_blockchain_updated(self, event, *args): + @qt_event_listener + def on_event_blockchain_updated(self, *args): if self._height != self.network.get_local_height(): self._height = self.network.get_local_height() self._logger.debug('new height: %d' % self._height) self.heightChanged.emit(self._height) self.blockchainUpdated.emit() - def on_default_server_changed(self, event, *args): + @qt_event_listener + def on_event_default_server_changed(self, *args): self.defaultServerChanged.emit() - def on_proxy_set(self, event, *args): + @qt_event_listener + def on_event_proxy_set(self, *args): self._logger.debug('proxy set') self.proxySet.emit() - def on_status(self, event, *args): + @qt_event_listener + def on_event_status(self, *args): self._logger.debug('status updated: %s' % self.network.connection_status) if self._status != self.network.connection_status: self._status = self.network.connection_status self.statusChanged.emit() - def on_fee_histogram(self, event, *args): + @qt_event_listener + def on_event_fee_histogram(self, *args): self._logger.debug('fee histogram updated') self.feeHistogramUpdated.emit() diff --git a/electrum/gui/qml/qewallet.py b/electrum/gui/qml/qewallet.py index 8e7e321ae..cafa5ce86 100644 --- a/electrum/gui/qml/qewallet.py +++ b/electrum/gui/qml/qewallet.py @@ -7,8 +7,8 @@ import threading from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl, QTimer from electrum.i18n import _ -from electrum.util import (register_callback, unregister_callback, - Satoshis, format_time, parse_max_spend, InvalidPassword) +from electrum.util import (Satoshis, format_time, parse_max_spend, InvalidPassword, + event_listener) from electrum.logging import get_logger from electrum.wallet import Wallet, Abstract_Wallet from electrum.storage import StorageEncryptionVersion @@ -24,8 +24,9 @@ from .qeaddresslistmodel import QEAddressListModel from .qechannellistmodel import QEChannelListModel from .qetypes import QEAmount from .auth import AuthMixin, auth_protect +from .util import QtEventListener, qt_event_listener -class QEWallet(AuthMixin, QObject): +class QEWallet(AuthMixin, QObject, QtEventListener): __instances = [] # this factory method should be used to instantiate QEWallet @@ -79,7 +80,7 @@ class QEWallet(AuthMixin, QObject): self.notification_timer.setInterval(500) # msec self.notification_timer.timeout.connect(self.notify_transactions) - self._network_signal.connect(self.on_network_qt) + #self._network_signal.connect(self.on_network_qt) interests = ['wallet_updated', 'new_transaction', 'status', 'verified', 'on_history', 'channel', 'channels_updated', 'payment_failed', 'payment_succeeded', 'invoice_status', 'request_status'] @@ -87,7 +88,8 @@ class QEWallet(AuthMixin, QObject): # window from being GC-ed when closed, callbacks should be # methods of this class only, and specifically not be # partials, lambdas or methods of subobjects. Hence... - register_callback(self.on_network, interests) + #register_callback(self.on_network, interests) + self.register_callbacks() self.destroyed.connect(lambda: self.on_destroy()) @pyqtProperty(bool, notify=isUptodateChanged) @@ -108,60 +110,93 @@ class QEWallet(AuthMixin, QObject): wallet = args[0] if wallet == self.wallet: self._logger.debug('event %s' % event) - if event == 'status': + + @event_listener + def on_event_status(self, *args, **kwargs): + #if event == 'status': self.isUptodateChanged.emit() - elif event == 'request_status': - wallet, key, status = args - if wallet == self.wallet: - self._logger.debug('request status %d for key %s' % (status, key)) - self.requestStatusChanged.emit(key, status) - elif event == 'invoice_status': - wallet, key = args - if wallet == self.wallet: - self._logger.debug('invoice status update for key %s' % key) - # FIXME event doesn't pass the new status, so we need to retrieve - invoice = self.wallet.get_invoice(key) - if invoice: - status = self.wallet.get_invoice_status(invoice) - self.invoiceStatusChanged.emit(key, status) - else: - self._logger.debug(f'No invoice found for key {key}') - elif event == 'new_transaction': - wallet, tx = args - if wallet == self.wallet: - self.add_tx_notification(tx) - self.historyModel.init_model() # TODO: be less dramatic - elif event == 'verified': - wallet, txid, info = args - if wallet == self.wallet: - self.historyModel.update_tx(txid, info) - elif event == 'wallet_updated': - wallet, = args - if wallet == self.wallet: - self._logger.debug('wallet %s updated' % str(wallet)) - self.balanceChanged.emit() - elif event == 'channel': - wallet, channel = args - if wallet == self.wallet: - self.balanceChanged.emit() - elif event == 'channels_updated': - wallet, = args + + + # elif event == 'request_status': + @event_listener + def on_event_request_status(self, wallet, key, status): + #wallet, key, status = args + if wallet == self.wallet: + self._logger.debug('request status %d for key %s' % (status, key)) + self.requestStatusChanged.emit(key, status) + # elif event == 'invoice_status': + @event_listener + def on_event_invoice_status(self, wallet, key): + #wallet, key = args + if wallet == self.wallet: + self._logger.debug('invoice status update for key %s' % key) + # FIXME event doesn't pass the new status, so we need to retrieve + invoice = self.wallet.get_invoice(key) + if invoice: + status = self.wallet.get_invoice_status(invoice) + self.invoiceStatusChanged.emit(key, status) + else: + self._logger.debug(f'No invoice found for key {key}') + + #elif event == 'new_transaction': + @qt_event_listener + def on_event_new_transaction(self, *args): + wallet, tx = args + if wallet == self.wallet: + self.add_tx_notification(tx) + self.historyModel.init_model() # TODO: be less dramatic + + + # elif event == 'verified': + @qt_event_listener + def on_event_verified(self, wallet, txid, info): + #wallet, txid, info = args + if wallet == self.wallet: + self.historyModel.update_tx(txid, info) + + + # elif event == 'wallet_updated': + @event_listener + def on_event_wallet_updated(self, wallet): + #wallet, = args + if wallet == self.wallet: + self._logger.debug('wallet %s updated' % str(wallet)) + self.balanceChanged.emit() + + # elif event == 'channel': + @event_listener + def on_event_channel(self, wallet, channel): + #wallet, channel = args if wallet == self.wallet: self.balanceChanged.emit() - elif event == 'payment_succeeded': - wallet, key = args - if wallet == self.wallet: - self.paymentSucceeded.emit(key) - self.historyModel.init_model() # TODO: be less dramatic - elif event == 'payment_failed': - wallet, key, reason = args - if wallet == self.wallet: - self.paymentFailed.emit(key, reason) - else: - self._logger.debug('unhandled event: %s %s' % (event, str(args))) + + # elif event == 'channels_updated': + @event_listener + def on_event_channels_updated(self, wallet): + #wallet, = args + if wallet == self.wallet: + self.balanceChanged.emit() + # elif event == 'payment_succeeded': + + @qt_event_listener + def on_event_payment_succeeded(self, wallet, key): + #wallet, key = args + if wallet == self.wallet: + self.paymentSucceeded.emit(key) + self.historyModel.init_model() # TODO: be less dramatic + + # elif event == 'payment_failed': + @event_listener + def on_event_payment_failed(self, wallet, key, reason): + #wallet, key, reason = args + if wallet == self.wallet: + self.paymentFailed.emit(key, reason) + #else: + #self._logger.debug('unhandled event: %s %s' % (event, str(args))) def on_destroy(self): - unregister_callback(self.on_network) + #unregister_callback(self.on_network) + self.unregister_callbacks() def add_tx_notification(self, tx): self._logger.debug('new transaction event') diff --git a/electrum/gui/qml/util.py b/electrum/gui/qml/util.py new file mode 100644 index 000000000..79aff327a --- /dev/null +++ b/electrum/gui/qml/util.py @@ -0,0 +1,31 @@ +from functools import wraps + +from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject + +from electrum.logging import get_logger +from electrum.i18n import _ +from electrum.util import EventListener, event_listener + +class QtEventListener(EventListener): + + qt_callback_signal = pyqtSignal(tuple) + + def register_callbacks(self): + self.qt_callback_signal.connect(self.on_qt_callback_signal) + EventListener.register_callbacks(self) + + def unregister_callbacks(self): + #self.qt_callback_signal.disconnect() + EventListener.unregister_callbacks(self) + + def on_qt_callback_signal(self, args): + func = args[0] + return func(self, *args[1:]) + +# decorator for members of the QtEventListener class +def qt_event_listener(func): + func = event_listener(func) + @wraps(func) + def decorator(self, *args): + self.qt_callback_signal.emit( (func,) + args) + return decorator