From dbf055de9a609777dc1e487966154c2b6902e90e Mon Sep 17 00:00:00 2001 From: ThomasV Date: Thu, 16 Jun 2022 12:05:05 +0200 Subject: [PATCH 1/2] EventListener class to handle callbacks and QtEventListener for Qt --- electrum/address_synchronizer.py | 10 +- electrum/gui/kivy/main_window.py | 105 +++++++++-------- electrum/gui/qt/__init__.py | 10 +- electrum/gui/qt/channel_details.py | 40 +++---- electrum/gui/qt/lightning_dialog.py | 22 ++-- electrum/gui/qt/main_window.py | 175 ++++++++++++++-------------- electrum/gui/qt/network_dialog.py | 21 ++-- electrum/gui/qt/util.py | 30 ++++- electrum/gui/text.py | 15 ++- electrum/lnwatcher.py | 33 +++--- electrum/lnworker.py | 10 +- electrum/tests/test_lnpeer.py | 6 +- electrum/util.py | 42 ++++++- electrum/wallet.py | 22 ++-- 14 files changed, 293 insertions(+), 248 deletions(-) diff --git a/electrum/address_synchronizer.py b/electrum/address_synchronizer.py index b23dcef21..33f1e226e 100644 --- a/electrum/address_synchronizer.py +++ b/electrum/address_synchronizer.py @@ -37,6 +37,7 @@ from .verifier import SPV from .blockchain import hash_header, Blockchain from .i18n import _ from .logging import Logger +from .util import EventListener, event_listener if TYPE_CHECKING: from .network import Network @@ -57,7 +58,7 @@ class HistoryItem(NamedTuple): balance: int -class AddressSynchronizer(Logger): +class AddressSynchronizer(Logger, EventListener): """ address database """ network: Optional['Network'] @@ -181,9 +182,10 @@ class AddressSynchronizer(Logger): self.synchronizer = Synchronizer(self) self.verifier = SPV(self.network, self) self.asyncio_loop = network.asyncio_loop - util.register_callback(self.on_blockchain_updated, ['blockchain_updated']) + self.register_callbacks() - def on_blockchain_updated(self, event, *args): + @event_listener + def on_event_blockchain_updated(self, *args): self._get_balance_cache = {} # invalidate cache async def stop(self): @@ -197,7 +199,7 @@ class AddressSynchronizer(Logger): finally: # even if we get cancelled self.synchronizer = None self.verifier = None - util.unregister_callback(self.on_blockchain_updated) + self.unregister_callbacks() self.db.put('stored_height', self.get_local_height()) def add_address(self, address): diff --git a/electrum/gui/kivy/main_window.py b/electrum/gui/kivy/main_window.py index b97a07ed1..ce9fe054c 100644 --- a/electrum/gui/kivy/main_window.py +++ b/electrum/gui/kivy/main_window.py @@ -19,6 +19,7 @@ from electrum import util from electrum.util import (profiler, InvalidPassword, send_exception_to_crash_reporter, format_satoshis, format_satoshis_plain, format_fee_satoshis, maybe_extract_bolt11_invoice, parse_max_spend) +from electrum.util import EventListener, event_listener from electrum.invoices import PR_PAID, PR_FAILED, Invoice from electrum import blockchain from electrum.network import Network, TxBroadcastError, BestEffortRequestFailed @@ -99,7 +100,7 @@ if TYPE_CHECKING: from electrum.paymentrequest import PaymentRequest -class ElectrumWindow(App, Logger): +class ElectrumWindow(App, Logger, EventListener): electrum_config = ObjectProperty(None) language = StringProperty('en') @@ -252,21 +253,25 @@ class ElectrumWindow(App, Logger): if self.history_screen: self.history_screen.update() - def on_quotes(self, d): + @event_listener + def on_event_on_quotes(self): self.logger.info("on_quotes") self._trigger_update_status() self._trigger_update_history() - def on_history(self, d): + @event_listener + def on_event_on_history(self): self.logger.info("on_history") if self.wallet: self.wallet.clear_coin_price_cache() self._trigger_update_history() - def on_fee_histogram(self, *args): + @event_listener + def on_event_fee_histogram(self, *args): self._trigger_update_history() - def on_request_status(self, event, wallet, key, status): + @event_listener + def on_event_request_status(self, wallet, key, status): if wallet != self.wallet: return req = self.wallet.receive_requests.get(key) @@ -283,7 +288,8 @@ class ElectrumWindow(App, Logger): self.show_info(_('Payment Received') + '\n' + key) self._trigger_update_history() - def on_invoice_status(self, event, wallet, key): + @event_listener + def on_event_invoice_status(self, wallet, key): if wallet != self.wallet: return req = self.wallet.get_invoice(key) @@ -299,14 +305,16 @@ class ElectrumWindow(App, Logger): if self.invoice_popup and self.invoice_popup.key == key: self.invoice_popup.update_status() - def on_payment_succeeded(self, event, wallet, key): + @event_listener + def on_event_payment_succeeded(self, wallet, key): if wallet != self.wallet: return description = self.wallet.get_label(key) self.show_info(_('Payment succeeded') + '\n\n' + description) self._trigger_update_history() - def on_payment_failed(self, event, wallet, key, reason): + @event_listener + def on_event_payment_failed(self, wallet, key, reason): if wallet != self.wallet: return self.show_info(_('Payment failed') + '\n\n' + reason) @@ -647,25 +655,7 @@ class ElectrumWindow(App, Logger): mactivity = PythonActivity.mActivity self.on_new_intent(mactivity.getIntent()) activity.bind(on_new_intent=self.on_new_intent) - # connect callbacks - if self.network: - interests = ['wallet_updated', 'network_updated', 'blockchain_updated', - 'status', 'new_transaction', 'verified'] - util.register_callback(self.on_network_event, interests) - util.register_callback(self.on_fee, ['fee']) - util.register_callback(self.on_fee_histogram, ['fee_histogram']) - util.register_callback(self.on_quotes, ['on_quotes']) - util.register_callback(self.on_history, ['on_history']) - util.register_callback(self.on_channels, ['channels_updated']) - util.register_callback(self.on_channel, ['channel']) - util.register_callback(self.on_invoice_status, ['invoice_status']) - util.register_callback(self.on_request_status, ['request_status']) - util.register_callback(self.on_payment_failed, ['payment_failed']) - util.register_callback(self.on_payment_succeeded, ['payment_succeeded']) - util.register_callback(self.on_channel_db, ['channel_db']) - util.register_callback(self.set_num_peers, ['gossip_peers']) - util.register_callback(self.set_unknown_channels, ['unknown_channels']) - + self.register_callbacks() if self.network and self.electrum_config.get('auto_connect') is None: self.popup_dialog("first_screen") # load_wallet_on_start will be called later, after initial network setup is completed @@ -677,14 +667,17 @@ class ElectrumWindow(App, Logger): if uri: self.set_URI(uri) - def on_channel_db(self, event, num_nodes, num_channels, num_policies): + @event_listener + def on_event_channel_db(self, num_nodes, num_channels, num_policies): self.lightning_gossip_num_nodes = num_nodes self.lightning_gossip_num_channels = num_channels - def set_num_peers(self, event, num_peers): + @event_listener + def on_event_gossip_peers(self, num_peers): self.lightning_gossip_num_peers = num_peers - def set_unknown_channels(self, event, unknown): + @event_listener + def on_event_unknown_channels(self, unknown): self.lightning_gossip_num_queries = unknown def get_wallet_path(self): @@ -818,11 +811,13 @@ class ElectrumWindow(App, Logger): delete_gossip) d.open() - def on_channel(self, evt, wallet, chan): + @event_listener + def on_event_channel(self, wallet, chan): if self._channels_dialog: Clock.schedule_once(lambda dt: self._channels_dialog.update()) - def on_channels(self, evt, wallet): + @event_listener + def on_event_channels(self, wallet): if self._channels_dialog: Clock.schedule_once(lambda dt: self._channels_dialog.update()) @@ -904,23 +899,32 @@ class ElectrumWindow(App, Logger): self.proxy_config = net_params.proxy or {} self.update_proxy_str(self.proxy_config) - def on_network_event(self, event, *args): - self.logger.info('network event: '+ event) - if event == 'network_updated': - self._trigger_update_interfaces() - self._trigger_update_status() - elif event == 'wallet_updated': - self._trigger_update_wallet() - self._trigger_update_status() - elif event == 'blockchain_updated': - # to update number of confirmations in history - self._trigger_update_wallet() - elif event == 'status': - self._trigger_update_status() - elif event == 'new_transaction': - self._trigger_update_wallet() - elif event == 'verified': - self._trigger_update_wallet() + @event_listener + def on_event_network_updated(self): + self._trigger_update_interfaces() + self._trigger_update_status() + + @event_listener + def on_event_wallet_updated(self, *args): + self._trigger_update_wallet() + self._trigger_update_status() + + @event_listener + def on_event_blockchain_updated(self, *args): + # to update number of confirmations in history + self._trigger_update_wallet() + + @event_listener + def on_event_status(self, *args): + self._trigger_update_status() + + @event_listener + def on_event_new_transaction(self, *args): + self._trigger_update_wallet() + + @event_listener + def on_event_verified(self, *args): + self._trigger_update_wallet() @profiler def load_wallet(self, wallet: 'Abstract_Wallet'): @@ -1265,7 +1269,8 @@ class ElectrumWindow(App, Logger): target, tooltip, dyn = self.electrum_config.get_fee_target() self.fee_status = target - def on_fee(self, event, *arg): + @event_listener + def on_event_fee(self, *arg): self.set_fee_status() def protected(self, msg, f, args): diff --git a/electrum/gui/qt/__init__.py b/electrum/gui/qt/__init__.py index 817752e2c..c5bb05ce4 100644 --- a/electrum/gui/qt/__init__.py +++ b/electrum/gui/qt/__init__.py @@ -102,9 +102,6 @@ class QElectrumApplication(QApplication): alias_received_signal = pyqtSignal() -class QNetworkUpdatedSignalObject(QObject): - network_updated_signal = pyqtSignal(str, object) - class ElectrumGui(BaseElectrumGui, Logger): @@ -142,7 +139,6 @@ class ElectrumGui(BaseElectrumGui, Logger): self.network_dialog = None self.lightning_dialog = None self.watchtower_dialog = None - self.network_updated_signal_obj = QNetworkUpdatedSignalObject() self._num_wizards_in_progress = 0 self._num_wizards_lock = threading.Lock() self.dark_icon = self.config.get("dark_icon", False) @@ -251,7 +247,6 @@ class ElectrumGui(BaseElectrumGui, Logger): self.network_dialog.close() self.network_dialog.clean_up() self.network_dialog = None - self.network_updated_signal_obj = None if self.lightning_dialog: self.lightning_dialog.close() self.lightning_dialog = None @@ -298,14 +293,13 @@ class ElectrumGui(BaseElectrumGui, Logger): def show_network_dialog(self): if self.network_dialog: - self.network_dialog.on_update() + self.network_dialog.on_event_network_updated() self.network_dialog.show() self.network_dialog.raise_() return self.network_dialog = NetworkDialog( network=self.daemon.network, - config=self.config, - network_updated_signal_obj=self.network_updated_signal_obj) + config=self.config) self.network_dialog.show() def _create_window_for_wallet(self, wallet): diff --git a/electrum/gui/qt/channel_details.py b/electrum/gui/qt/channel_details.py index 7eb6d8ec6..6a783b556 100644 --- a/electrum/gui/qt/channel_details.py +++ b/electrum/gui/qt/channel_details.py @@ -5,7 +5,7 @@ import PyQt5.QtWidgets as QtWidgets import PyQt5.QtCore as QtCore from PyQt5.QtWidgets import QLabel, QLineEdit, QHBoxLayout -from electrum import util +from electrum.util import EventListener from electrum.i18n import _ from electrum.util import bh2u, format_time from electrum.lnutil import format_short_channel_id, LOCAL, REMOTE, UpdateAddHtlc, Direction @@ -15,6 +15,7 @@ from electrum.bitcoin import COIN from electrum.wallet import Abstract_Wallet from .util import Buttons, CloseButton, ButtonsLineEdit, MessageBoxMixin, WWLabel +from .util import QtEventListener, qt_event_listener if TYPE_CHECKING: from .main_window import ElectrumWindow @@ -35,7 +36,8 @@ class LinkedLabel(QtWidgets.QLabel): self.linkActivated.connect(on_clicked) -class ChannelDetailsDialog(QtWidgets.QDialog, MessageBoxMixin): +class ChannelDetailsDialog(QtWidgets.QDialog, MessageBoxMixin, QtEventListener): + def make_htlc_item(self, i: UpdateAddHtlc, direction: Direction) -> HTLCItem: it = HTLCItem(_('Sent HTLC with ID {}' if Direction.SENT == direction else 'Received HTLC with ID {}').format(i.htlc_id)) it.appendRow([HTLCItem(_('Amount')),HTLCItem(self.format_msat(i.amount_msat))]) @@ -83,35 +85,28 @@ class ChannelDetailsDialog(QtWidgets.QDialog, MessageBoxMixin): dest_mapping = self.keyname_rows[to] dest_mapping[payment_hash] = len(dest_mapping) - htlc_fulfilled = QtCore.pyqtSignal(str, bytes, Channel, int) - htlc_failed = QtCore.pyqtSignal(str, bytes, Channel, int) - htlc_added = QtCore.pyqtSignal(str, Channel, UpdateAddHtlc, Direction) - state_changed = QtCore.pyqtSignal(str, Abstract_Wallet, AbstractChannel) - - @QtCore.pyqtSlot(str, Abstract_Wallet, AbstractChannel) - def do_state_changed(self, wallet, chan): - if wallet != self.wallet: - return + @qt_event_listener + def on_event_channel(self, wallet, chan): if chan == self.chan: self.update() - @QtCore.pyqtSlot(str, Channel, UpdateAddHtlc, Direction) - def on_htlc_added(self, evtname, chan, htlc, direction): + @qt_event_listener + def on_event_htlc_added(self, chan, htlc, direction): if chan != self.chan: return mapping = self.keyname_rows['inflight'] mapping[htlc.payment_hash] = len(mapping) self.folders['inflight'].appendRow(self.make_htlc_item(htlc, direction)) - @QtCore.pyqtSlot(str, bytes, Channel, int) - def on_htlc_fulfilled(self, evtname, payment_hash, chan, htlc_id): + @qt_event_listener + def on_event_htlc_fulfilled(self, payment_hash, chan, htlc_id): if chan.channel_id != self.chan.channel_id: return self.move('inflight', 'settled', payment_hash) self.update() - @QtCore.pyqtSlot(str, bytes, Channel, int) - def on_htlc_failed(self, evtname, payment_hash, chan, htlc_id): + @qt_event_listener + def on_event_htlc_failed(self, payment_hash, chan, htlc_id): if chan.channel_id != self.chan.channel_id: return self.move('inflight', 'failed', payment_hash) @@ -143,17 +138,8 @@ class ChannelDetailsDialog(QtWidgets.QDialog, MessageBoxMixin): self.format_msat = lambda msat: window.format_amount_and_units(msat / 1000) self.format_sat = lambda sat: window.format_amount_and_units(sat) - # connect signals with slots - self.htlc_fulfilled.connect(self.on_htlc_fulfilled) - self.htlc_failed.connect(self.on_htlc_failed) - self.state_changed.connect(self.do_state_changed) - self.htlc_added.connect(self.on_htlc_added) - # register callbacks for updating - util.register_callback(self.htlc_fulfilled.emit, ['htlc_fulfilled']) - util.register_callback(self.htlc_failed.emit, ['htlc_failed']) - util.register_callback(self.htlc_added.emit, ['htlc_added']) - util.register_callback(self.state_changed.emit, ['channel']) + self.register_callbacks() # set attributes of QDialog self.setWindowTitle(_('Channel Details')) diff --git a/electrum/gui/qt/lightning_dialog.py b/electrum/gui/qt/lightning_dialog.py index 44a995006..ee90a02c6 100644 --- a/electrum/gui/qt/lightning_dialog.py +++ b/electrum/gui/qt/lightning_dialog.py @@ -27,16 +27,16 @@ from typing import TYPE_CHECKING from PyQt5.QtWidgets import (QDialog, QLabel, QVBoxLayout, QPushButton) -from electrum import util from electrum.i18n import _ from .util import Buttons +from .util import QtEventListener, qt_event_listener if TYPE_CHECKING: from . import ElectrumGui -class LightningDialog(QDialog): +class LightningDialog(QDialog, QtEventListener): def __init__(self, gui_object: 'ElectrumGui'): QDialog.__init__(self) @@ -59,24 +59,25 @@ class LightningDialog(QDialog): b = QPushButton(_('Close')) b.clicked.connect(self.close) vbox.addLayout(Buttons(b)) - util.register_callback(self.on_channel_db, ['channel_db']) - util.register_callback(self.set_num_peers, ['gossip_peers']) - util.register_callback(self.set_unknown_channels, ['unknown_channels']) + self.register_callbacks() self.network.channel_db.update_counts() # trigger callback if self.network.lngossip: - self.set_num_peers('', self.network.lngossip.num_peers()) - self.set_unknown_channels('', len(self.network.lngossip.unknown_ids)) + self.on_event_gossip_peers(self.network.lngossip.num_peers()) + self.on_event_unknown_channels(len(self.network.lngossip.unknown_ids)) else: self.num_peers.setText(_('Lightning gossip not active.')) - def on_channel_db(self, event, num_nodes, num_channels, num_policies): + @qt_event_listener + def on_event_channel_db(self, num_nodes, num_channels, num_policies): self.num_nodes.setText(_('{} nodes').format(num_nodes)) self.num_channels.setText(_('{} channels').format(num_channels)) - def set_num_peers(self, event, num_peers): + @qt_event_listener + def on_event_gossip_peers(self, num_peers): self.num_peers.setText(_('Connected to {} peers').format(num_peers)) - def set_unknown_channels(self, event, unknown): + @qt_event_listener + def on_event_unknown_channels(self, unknown): self.status.setText(_('Requesting {} channels...').format(unknown) if unknown else '') def is_hidden(self): @@ -93,5 +94,6 @@ class LightningDialog(QDialog): self.raise_() def closeEvent(self, event): + self.unregister_callbacks() self.gui_object.lightning_dialog = None event.accept() diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py index edad8ba83..e4f561352 100644 --- a/electrum/gui/qt/main_window.py +++ b/electrum/gui/qt/main_window.py @@ -193,12 +193,12 @@ def protected(func): return func(self, *args, **kwargs) return request_password +from .util import QtEventListener, qt_event_listener, event_listener -class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): +class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): payment_request_ok_signal = pyqtSignal() payment_request_error_signal = pyqtSignal() - network_signal = pyqtSignal(str, object) #ln_payment_attempt_signal = pyqtSignal(str) computing_privkeys_signal = pyqtSignal() show_privkeys_signal = pyqtSignal() @@ -208,7 +208,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): def __init__(self, gui_object: 'ElectrumGui', wallet: Abstract_Wallet): QMainWindow.__init__(self) - self.gui_object = gui_object self.config = config = gui_object.config # type: SimpleConfig self.gui_thread = gui_object.gui_thread @@ -314,21 +313,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): self.history_list.setFocus(True) # network callbacks - if self.network: - self.network_signal.connect(self.on_network_qt) - interests = ['wallet_updated', 'network_updated', 'blockchain_updated', - 'new_transaction', 'status', - 'banner', 'verified', 'fee', 'fee_histogram', 'on_quotes', - 'on_history', 'channel', 'channels_updated', - 'payment_failed', 'payment_succeeded', - 'invoice_status', 'request_status', 'ln_gossip_sync_progress', - 'cert_mismatch', '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... - util.register_callback(self.on_network, interests) - # set initial message + self.register_callbacks() + # banner may already be there + if self.network and self.network.banner: self.console.showMessage(self.network.banner) # update fee slider in case we missed the callback @@ -463,74 +450,75 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): pass # see #4418 self.show_error(repr(e)) - def on_network(self, event, *args): - # Handle in GUI thread - self.network_signal.emit(event, args) + @event_listener + def on_event_wallet_updated(self, wallet): + if wallet == self.wallet: + self.need_update.set() + + @event_listener + def on_event_new_transaction(self, wallet, tx): + if wallet == self.wallet: + self.tx_notification_queue.put(tx) - def on_network_qt(self, event, args=None): - # Handle a network message in the GUI thread - # note: all windows get events from all wallets! - if event == 'wallet_updated': - wallet = args[0] - if wallet == self.wallet: - self.need_update.set() - elif event == 'network_updated': - self.gui_object.network_updated_signal_obj.network_updated_signal \ - .emit(event, args) - self.network_signal.emit('status', None) - elif event == 'blockchain_updated': - # to update number of confirmations in history - self.refresh_tabs() - elif event == 'new_transaction': - wallet, tx = args - if wallet == self.wallet: - self.tx_notification_queue.put(tx) - elif event == 'on_quotes': - self.on_fx_quotes() - elif event == 'on_history': - self.on_fx_history() - elif event == 'gossip_db_loaded': - self.channels_list.gossip_db_loaded.emit(*args) - elif event == 'channels_updated': - wallet = args[0] - if wallet == self.wallet: - self.channels_list.update_rows.emit(*args) - elif event == 'channel': - wallet = args[0] - if wallet == self.wallet: - self.channels_list.update_single_row.emit(*args) - self.update_status() - elif event == 'request_status': - self.on_request_status(*args) - elif event == 'invoice_status': - self.on_invoice_status(*args) - elif event == 'payment_succeeded': - # sent by lnworker, redundant with invoice_status - wallet = args[0] - if wallet == self.wallet: - self.on_payment_succeeded(*args) - elif event == 'payment_failed': - wallet = args[0] - if wallet == self.wallet: - self.on_payment_failed(*args) - elif event == 'status': + @qt_event_listener + def on_event_status(self): + self.update_status() + + @qt_event_listener + def on_event_network_updated(self, *args): + self.update_status() + + @qt_event_listener + def on_event_blockchain_updated(self, *args): + # update the number of confirmations in history + self.refresh_tabs() + + @qt_event_listener + def on_event_on_quotes(self, *args): + self.on_fx_quotes() + + @qt_event_listener + def on_event_on_history(self, *args): + self.on_fx_history() + + @qt_event_listener + def on_event_gossip_db_loaded(self, *args): + self.channels_list.gossip_db_loaded.emit(*args) + + @qt_event_listener + def on_event_channels_updated(self, *args): + wallet = args[0] + if wallet == self.wallet: + self.channels_list.update_rows.emit(*args) + + @qt_event_listener + def on_event_channel(self, *args): + wallet = args[0] + if wallet == self.wallet: + self.channels_list.update_single_row.emit(*args) self.update_status() - elif event == 'banner': - self.console.showMessage(args[0]) - elif event == 'verified': - wallet, tx_hash, tx_mined_status = args - if wallet == self.wallet: - self.history_model.update_tx_mined_status(tx_hash, tx_mined_status) - elif event == 'fee': - pass - elif event == 'fee_histogram': - self.history_model.on_fee_histogram() - elif event == 'ln_gossip_sync_progress': - self.update_lightning_icon() - elif event == 'cert_mismatch': - self.show_cert_mismatch_error() - else: - self.logger.info(f"unexpected network event: {event} {args}") + + @qt_event_listener + def on_event_banner(self, *args): + self.console.showMessage(args[0]) + + @qt_event_listener + def on_event_verified(self, *args): + wallet, tx_hash, tx_mined_status = args + if wallet == self.wallet: + self.history_model.update_tx_mined_status(tx_hash, tx_mined_status) + + @qt_event_listener + def on_event_fee_histogram(self, *args): + self.history_model.on_fee_histogram() + + @qt_event_listener + def on_event_ln_gossip_sync_progress(self, *args): + self.update_lightning_icon() + + @qt_event_listener + def on_event_cert_mismatch(self, *args): + self.show_cert_mismatch_error() def close_wallet(self): if self.wallet: @@ -1821,7 +1809,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): d = SwapDialog(self, is_reverse=is_reverse, recv_amount_sat=recv_amount_sat, channels=channels) return d.run() - def on_request_status(self, wallet, key, status): + @qt_event_listener + def on_event_request_status(self, wallet, key, status): if wallet != self.wallet: return req = self.wallet.receive_requests.get(key) @@ -1840,7 +1829,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): else: self.request_list.refresh_item(key) - def on_invoice_status(self, wallet, key): + @qt_event_listener + def on_event_invoice_status(self, wallet, key): if wallet != self.wallet: return invoice = self.wallet.get_invoice(key) @@ -1852,12 +1842,19 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): else: self.invoice_list.refresh_item(key) - def on_payment_succeeded(self, wallet, key): + @qt_event_listener + def on_event_payment_succeeded(self, wallet, key): + # sent by lnworker, redundant with invoice_status + if wallet != self.wallet: + return description = self.wallet.get_label(key) self.notify(_('Payment sent') + '\n\n' + description) self.need_update.set() - def on_payment_failed(self, wallet, key, reason): + @qt_event_listener + def on_event_payment_failed(self, wallet, key, reason): + if wallet != self.wallet: + return invoice = self.wallet.get_invoice(key) if invoice and invoice.is_lightning() and invoice.get_address(): if self.question(_('Payment failed') + '\n\n' + reason + '\n\n'+ 'Fallback to onchain payment?'): @@ -3497,7 +3494,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): self.thread = None for fut in self._coroutines_scheduled.keys(): fut.cancel() - util.unregister_callback(self.on_network) + self.unregister_callbacks() self.config.set_key("is_maximized", self.isMaximized()) if not self.isMaximized(): g = self.geometry() diff --git a/electrum/gui/qt/network_dialog.py b/electrum/gui/qt/network_dialog.py index 3f2387f04..cb2d4297c 100644 --- a/electrum/gui/qt/network_dialog.py +++ b/electrum/gui/qt/network_dialog.py @@ -43,6 +43,7 @@ from electrum.logging import get_logger from .util import (Buttons, CloseButton, HelpButton, read_QIcon, char_width_in_lineedit, PasswordLineEdit) +from .util import QtEventListener, qt_event_listener if TYPE_CHECKING: from electrum.simple_config import SimpleConfig @@ -53,27 +54,20 @@ _logger = get_logger(__name__) protocol_names = ['TCP', 'SSL'] protocol_letters = 'ts' -class NetworkDialog(QDialog): - def __init__(self, *, network: Network, config: 'SimpleConfig', network_updated_signal_obj): +class NetworkDialog(QDialog, QtEventListener): + def __init__(self, *, network: Network, config: 'SimpleConfig'): QDialog.__init__(self) self.setWindowTitle(_('Network')) self.setMinimumSize(500, 500) self.nlayout = NetworkChoiceLayout(network, config) - self.network_updated_signal_obj = network_updated_signal_obj vbox = QVBoxLayout(self) vbox.addLayout(self.nlayout.layout()) vbox.addLayout(Buttons(CloseButton(self))) - self.network_updated_signal_obj.network_updated_signal.connect( - self.on_update) - util.register_callback(self.on_network, ['network_updated']) + self.register_callbacks() self._cleaned_up = False - def on_network(self, event, *args): - signal_obj = self.network_updated_signal_obj - if signal_obj: - signal_obj.network_updated_signal.emit(event, args) - - def on_update(self): + @qt_event_listener + def on_event_network_updated(self): self.nlayout.update() def clean_up(self): @@ -81,8 +75,7 @@ class NetworkDialog(QDialog): return self._cleaned_up = True self.nlayout.clean_up() - self.network_updated_signal_obj.network_updated_signal.disconnect() - self.network_updated_signal_obj = None + self.unregister_callbacks() class NodesListWidget(QTreeWidget): diff --git a/electrum/gui/qt/util.py b/electrum/gui/qt/util.py index c7d89f30b..b2a766658 100644 --- a/electrum/gui/qt/util.py +++ b/electrum/gui/qt/util.py @@ -8,7 +8,7 @@ import traceback import os import webbrowser from decimal import Decimal -from functools import partial, lru_cache +from functools import partial, lru_cache, wraps from typing import (NamedTuple, Callable, Optional, TYPE_CHECKING, Union, List, Dict, Any, Sequence, Iterable) @@ -1503,6 +1503,34 @@ class VTabWidget(QtWidgets.QTabWidget): return super().resizeEvent(e) +from electrum.util import EventListener, event_listener + +class QtEventListener(EventListener): + + qt_callback_signal = QtCore.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): + assert func.__name__.startswith('on_event_') + func._is_event_listener = True + @wraps(func) + def decorator(self, *args): + self.qt_callback_signal.emit( (func,) + args) + return decorator + + if __name__ == "__main__": app = QApplication([]) t = WaitingDialog(None, 'testing ...', lambda: [time.sleep(1)], lambda x: QMessageBox.information(None, 'done', "done")) diff --git a/electrum/gui/text.py b/electrum/gui/text.py index aa9fd06ac..76bfecafd 100644 --- a/electrum/gui/text.py +++ b/electrum/gui/text.py @@ -12,6 +12,7 @@ import electrum from electrum.gui import BaseElectrumGui from electrum import util from electrum.util import format_satoshis +from electrum.util import EventListener, event_listener from electrum.bitcoin import is_address, COIN from electrum.transaction import PartialTxOutput from electrum.wallet import Wallet, Abstract_Wallet @@ -29,7 +30,7 @@ if TYPE_CHECKING: _ = lambda x:x # i18n -class ElectrumGui(BaseElectrumGui): +class ElectrumGui(BaseElectrumGui, EventListener): def __init__(self, *, config: 'SimpleConfig', daemon: 'Daemon', plugins: 'Plugins'): BaseElectrumGui.__init__(self, config=config, daemon=daemon, plugins=plugins) @@ -74,12 +75,20 @@ class ElectrumGui(BaseElectrumGui): self.history = None self.txid = [] - util.register_callback(self.update, ['wallet_updated', 'network_updated']) + self.register_callbacks() self.tab_names = [_("History"), _("Send"), _("Receive"), _("Addresses"), _("Contacts"), _("Banner")] self.num_tabs = len(self.tab_names) + @event_listener + def on_event_wallet_updated(self, wallet): + self.update() + + @event_listener + def on_event_network_updated(self): + self.update() + def set_cursor(self, x): try: curses.curs_set(x) @@ -101,7 +110,7 @@ class ElectrumGui(BaseElectrumGui): self.set_cursor(0) return s - def update(self, event, *args): + def update(self): self.update_history() if self.tab == 0: self.print_history() diff --git a/electrum/lnwatcher.py b/electrum/lnwatcher.py index 765854d27..358d0da0b 100644 --- a/electrum/lnwatcher.py +++ b/electrum/lnwatcher.py @@ -135,8 +135,9 @@ class SweepStore(SqlDB): return [(r[0], r[1]) for r in c.fetchall()] +from .util import EventListener, event_listener -class LNWatcher(Logger): +class LNWatcher(Logger, EventListener): LOGGING_SHORTCUT = 'W' @@ -147,23 +148,12 @@ class LNWatcher(Logger): self.config = network.config self.callbacks = {} # address -> lambda: coroutine self.network = network - - util.register_callback(self.on_fee, ['fee']) - util.register_callback(self.on_blockchain_updated, ['blockchain_updated']) - util.register_callback(self.on_network_updated, ['network_updated']) - util.register_callback(self.on_adb_added_verified_tx, ['adb_added_verified_tx']) - util.register_callback(self.on_adb_set_up_to_date, ['adb_set_up_to_date']) - + self.register_callbacks() # status gets populated when we run self.channel_status = {} - async def stop(self): - util.unregister_callback(self.on_fee) - util.unregister_callback(self.on_blockchain_updated) - util.unregister_callback(self.on_network_updated) - util.unregister_callback(self.on_adb_added_verified_tx) - util.unregister_callback(self.on_adb_set_up_to_date) + self.unregister_callbacks() def get_channel_status(self, outpoint): return self.channel_status.get(outpoint, 'unknown') @@ -185,21 +175,26 @@ class LNWatcher(Logger): self.adb.add_address(address) self.callbacks[address] = callback - async def on_fee(self, event, *args): + @event_listener + async def on_event_fee(self, *args): await self.trigger_callbacks() - async def on_network_updated(self, event, *args): + @event_listener + async def on_event_network_updated(self, *args): await self.trigger_callbacks() - async def on_blockchain_updated(self, event, *args): + @event_listener + async def on_event_blockchain_updated(self, *args): await self.trigger_callbacks() - async def on_adb_added_verified_tx(self, event, adb, tx_hash): + @event_listener + async def on_event_adb_added_verified_tx(self, adb, tx_hash): if adb != self.adb: return await self.trigger_callbacks() - async def on_adb_set_up_to_date(self, event, adb): + @event_listener + async def on_event_adb_set_up_to_date(self, adb): if adb != self.adb: return await self.trigger_callbacks() diff --git a/electrum/lnworker.py b/electrum/lnworker.py index c7a9b5cd4..d00455266 100644 --- a/electrum/lnworker.py +++ b/electrum/lnworker.py @@ -31,6 +31,7 @@ from . import keystore from .util import profiler, chunks, OldTaskGroup from .invoices import Invoice, PR_UNPAID, PR_EXPIRED, PR_PAID, PR_INFLIGHT, PR_FAILED, PR_ROUTING, LN_EXPIRY_NEVER from .util import NetworkRetryManager, JsonRPCClient, NotEnoughFunds +from .util import EventListener, event_listener from .lnutil import LN_MAX_FUNDING_SAT from .keystore import BIP32_KeyStore from .bitcoin import COIN @@ -192,7 +193,7 @@ LNGOSSIP_FEATURES = BASE_FEATURES\ | LnFeatures.GOSSIP_QUERIES_REQ\ -class LNWorker(Logger, NetworkRetryManager[LNPeerAddr]): +class LNWorker(Logger, EventListener, NetworkRetryManager[LNPeerAddr]): INITIAL_TRAMPOLINE_FEE_LEVEL = 1 # only used for trampoline payments. set to 0 in tests. @@ -216,7 +217,7 @@ class LNWorker(Logger, NetworkRetryManager[LNPeerAddr]): self.config = None # type: Optional[SimpleConfig] self.stopping_soon = False # whether we are being shut down - util.register_callback(self.on_proxy_changed, ['proxy_set']) + self.register_callbacks() @property def channel_db(self): @@ -340,7 +341,7 @@ class LNWorker(Logger, NetworkRetryManager[LNPeerAddr]): async def stop(self): if self.listen_server: self.listen_server.close() - util.unregister_callback(self.on_proxy_changed) + self.unregister_callbacks() await self.taskgroup.cancel_remaining() def _add_peers_from_config(self): @@ -477,7 +478,8 @@ class LNWorker(Logger, NetworkRetryManager[LNPeerAddr]): choice = random.choice(addr_list) return choice - def on_proxy_changed(self, event, *args): + @event_listener + def on_event_proxy_set(self, *args): for peer in self.peers.values(): peer.close_and_cleanup() self._clear_addr_retry_times() diff --git a/electrum/tests/test_lnpeer.py b/electrum/tests/test_lnpeer.py index 2a8a9be54..67577c579 100644 --- a/electrum/tests/test_lnpeer.py +++ b/electrum/tests/test_lnpeer.py @@ -22,7 +22,7 @@ from electrum.ecc import ECPrivkey from electrum import simple_config, lnutil from electrum.lnaddr import lnencode, LnAddr, lndecode from electrum.bitcoin import COIN, sha256 -from electrum.util import bh2u, NetworkRetryManager, bfh, OldTaskGroup +from electrum.util import bh2u, NetworkRetryManager, bfh, OldTaskGroup, EventListener from electrum.lnpeer import Peer from electrum.lnutil import LNPeerAddr, Keypair, privkey_to_pubkey from electrum.lnutil import PaymentFailure, LnFeatures, HTLCOwner @@ -124,7 +124,7 @@ class MockWallet: return True -class MockLNWallet(Logger, NetworkRetryManager[LNPeerAddr]): +class MockLNWallet(Logger, EventListener, NetworkRetryManager[LNPeerAddr]): MPP_EXPIRY = 2 # HTLC timestamps are cast to int, so this cannot be 1 PAYMENT_TIMEOUT = 120 TIMEOUT_SHUTDOWN_FAIL_PENDING_HTLCS = 0 @@ -255,7 +255,7 @@ class MockLNWallet(Logger, NetworkRetryManager[LNPeerAddr]): handle_error_code_from_failed_htlc = LNWallet.handle_error_code_from_failed_htlc is_trampoline_peer = LNWallet.is_trampoline_peer wait_for_received_pending_htlcs_to_get_removed = LNWallet.wait_for_received_pending_htlcs_to_get_removed - on_proxy_changed = LNWallet.on_proxy_changed + #on_event_proxy_set = LNWallet.on_event_proxy_set _decode_channel_update_msg = LNWallet._decode_channel_update_msg _handle_chanupd_from_failed_htlc = LNWallet._handle_chanupd_from_failed_htlc _on_maybe_forwarded_htlc_resolved = LNWallet._on_maybe_forwarded_htlc_resolved diff --git a/electrum/util.py b/electrum/util.py index 6f170fab6..9ad3bfefd 100644 --- a/electrum/util.py +++ b/electrum/util.py @@ -1594,10 +1594,10 @@ class CallbackManager: self.callbacks = defaultdict(list) # note: needs self.callback_lock self.asyncio_loop = None - def register_callback(self, callback, events): + def register_callback(self, func, events): with self.callback_lock: for event in events: - self.callbacks[event].append(callback) + self.callbacks[event].append(func) def unregister_callback(self, callback): with self.callback_lock: @@ -1618,13 +1618,13 @@ class CallbackManager: for callback in callbacks: # FIXME: if callback throws, we will lose the traceback if asyncio.iscoroutinefunction(callback): - asyncio.run_coroutine_threadsafe(callback(event, *args), self.asyncio_loop) + asyncio.run_coroutine_threadsafe(callback(*args), self.asyncio_loop) elif get_running_loop() == self.asyncio_loop: # run callback immediately, so that it is guaranteed # to have been executed when this method returns - callback(event, *args) + callback(*args) else: - self.asyncio_loop.call_soon_threadsafe(callback, event, *args) + self.asyncio_loop.call_soon_threadsafe(callback, *args) callback_mgr = CallbackManager() @@ -1633,6 +1633,38 @@ register_callback = callback_mgr.register_callback unregister_callback = callback_mgr.unregister_callback + +class EventListener: + + def _list_callbacks(self): + for method_name in dir(self): + # Fixme: getattr executes the code of methods decorated by @property. + # This if why we shield with startswith + if not method_name.startswith('on_event_'): + continue + method = getattr(self, method_name) + if not getattr(method, '_is_event_listener', False): + continue + assert callable(method) + yield method_name[len('on_event_'):], method + + def register_callbacks(self): + for name, method in self._list_callbacks(): + _logger.info(f'registering callback {method}') + register_callback(method, [name]) + + def unregister_callbacks(self): + for name, method in self._list_callbacks(): + _logger.info(f'unregistering callback {method}') + unregister_callback(method) + + +def event_listener(func): + assert func.__name__.startswith('on_event_') + func._is_event_listener = True + return func + + _NetAddrType = TypeVar("_NetAddrType") diff --git a/electrum/wallet.py b/electrum/wallet.py index 8740fd463..bfbd2a6c1 100644 --- a/electrum/wallet.py +++ b/electrum/wallet.py @@ -83,6 +83,7 @@ from .logging import get_logger, Logger from .lnworker import LNWallet from .paymentrequest import PaymentRequest from .util import read_json_file, write_json_file, UserFacingException +from .util import EventListener, event_listener if TYPE_CHECKING: from .network import Network @@ -267,7 +268,7 @@ class TxWalletDetails(NamedTuple): is_lightning_funding_tx: bool -class Abstract_Wallet(ABC, Logger): +class Abstract_Wallet(ABC, Logger, EventListener): """ Wallet classes are created to handle various address generation methods. Completion states (watching-only, single account, no seed, etc) are handled inside classes. @@ -282,6 +283,7 @@ class Abstract_Wallet(ABC, Logger): lnworker: Optional['LNWallet'] def __init__(self, db: WalletDB, storage: Optional[WalletStorage], *, config: SimpleConfig): + if not db.is_ready_to_be_used_by_wallet(): raise Exception("storage not ready to be used by Abstract_Wallet") @@ -332,10 +334,7 @@ class Abstract_Wallet(ABC, Logger): self.lnworker = None self.load_keystore() self.test_addresses_sanity() - # callbacks - util.register_callback(self.on_adb_added_tx, ['adb_added_tx']) - util.register_callback(self.on_adb_added_verified_tx, ['adb_added_verified_tx']) - util.register_callback(self.on_adb_removed_verified_tx, ['adb_removed_verified_tx']) + self.register_callbacks() @ignore_exceptions # don't kill outer taskgroup async def main_loop(self): @@ -422,9 +421,7 @@ class Abstract_Wallet(ABC, Logger): async def stop(self): """Stop all networking and save DB to disk.""" - util.unregister_callback(self.on_adb_added_tx) - util.unregister_callback(self.on_adb_added_verified_tx) - util.unregister_callback(self.on_adb_removed_verified_tx) + self.unregister_callbacks() try: async with ignore_after(5): if self.network: @@ -454,7 +451,8 @@ class Abstract_Wallet(ABC, Logger): if status_changed: self.logger.info(f'set_up_to_date: {up_to_date}') - def on_adb_added_tx(self, event, adb, tx_hash, notify_GUI): + @event_listener + def on_event_adb_added_tx(self, adb, tx_hash, notify_GUI): if self.adb != adb: return tx = self.db.get_transaction(tx_hash) @@ -471,14 +469,16 @@ class Abstract_Wallet(ABC, Logger): if notify_GUI: util.trigger_callback('new_transaction', self, tx) - def on_adb_added_verified_tx(self, event, adb, tx_hash): + @event_listener + def on_event_adb_added_verified_tx(self, adb, tx_hash): if adb != self.adb: return self._update_request_statuses_touched_by_tx(tx_hash) tx_mined_status = self.adb.get_tx_height(tx_hash) util.trigger_callback('verified', self, tx_hash, tx_mined_status) - def on_adb_removed_verified_tx(self, event, adb, tx_hash): + @event_listener + def on_event_adb_removed_verified_tx(self, adb, tx_hash): if adb != self.adb: return self._update_request_statuses_touched_by_tx(tx_hash) From 5b000a871f319a0ec4ffd7d622b0824ec60fa431 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Wed, 22 Jun 2022 01:24:19 +0200 Subject: [PATCH 2/2] EventListener follow-ups: adapt left-out classes and minor clean-ups --- electrum/daemon.py | 8 +++++--- electrum/exchange_rate.py | 9 +++++---- electrum/gui/qt/settings_dialog.py | 16 ++++++++-------- electrum/gui/qt/util.py | 6 ++---- electrum/gui/stdio.py | 22 ++++++++++++++-------- electrum/util.py | 5 ++--- 6 files changed, 36 insertions(+), 30 deletions(-) diff --git a/electrum/daemon.py b/electrum/daemon.py index 9221066d5..a73b82062 100644 --- a/electrum/daemon.py +++ b/electrum/daemon.py @@ -44,6 +44,7 @@ from .network import Network from .util import (json_decode, to_bytes, to_string, profiler, standardize_path, constant_time_compare) from .invoices import PR_PAID, PR_EXPIRED from .util import log_exceptions, ignore_exceptions, randrange, OldTaskGroup +from .util import EventListener, event_listener from .wallet import Wallet, Abstract_Wallet from .storage import WalletStorage from .wallet_db import WalletDB @@ -356,7 +357,7 @@ class WatchTowerServer(AuthenticatedServer): return await self.lnwatcher.sweepstore.add_sweep_tx(*args) -class PayServer(Logger): +class PayServer(Logger, EventListener): def __init__(self, daemon: 'Daemon', netaddress): Logger.__init__(self) @@ -364,14 +365,15 @@ class PayServer(Logger): self.daemon = daemon self.config = daemon.config self.pending = defaultdict(asyncio.Event) - util.register_callback(self.on_payment, ['request_status']) + self.register_callbacks() @property def wallet(self): # FIXME specify wallet somehow? return list(self.daemon.get_wallets().values())[0] - async def on_payment(self, evt, wallet, key, status): + @event_listener + async def on_event_request_status(self, wallet, key, status): if status == PR_PAID: self.pending[key].set() diff --git a/electrum/exchange_rate.py b/electrum/exchange_rate.py index 5e033e4ea..e2c4add95 100644 --- a/electrum/exchange_rate.py +++ b/electrum/exchange_rate.py @@ -17,7 +17,7 @@ from . import util from .bitcoin import COIN from .i18n import _ from .util import (ThreadJob, make_dir, log_exceptions, OldTaskGroup, - make_aiohttp_session, resource_path) + make_aiohttp_session, resource_path, EventListener, event_listener) from .network import Network from .simple_config import SimpleConfig from .logging import Logger @@ -496,13 +496,13 @@ def get_exchanges_by_ccy(history=True): return dictinvert(d) -class FxThread(ThreadJob): +class FxThread(ThreadJob, EventListener): def __init__(self, config: SimpleConfig, network: Optional[Network]): ThreadJob.__init__(self) self.config = config self.network = network - util.register_callback(self.set_proxy, ['proxy_set']) + self.register_callbacks() self.ccy = self.get_currency() self.history_used_spot = False self.ccy_combo = None @@ -513,7 +513,8 @@ class FxThread(ThreadJob): self.set_exchange(self.config_exchange()) make_dir(self.cache_dir) - def set_proxy(self, trigger_name, *args): + @event_listener + def on_event_proxy_set(self, *args): self._trigger.set() @staticmethod diff --git a/electrum/gui/qt/settings_dialog.py b/electrum/gui/qt/settings_dialog.py index 0623643d6..e2225ddb0 100644 --- a/electrum/gui/qt/settings_dialog.py +++ b/electrum/gui/qt/settings_dialog.py @@ -34,12 +34,12 @@ from PyQt5.QtWidgets import (QComboBox, QTabWidget, QDialog, from electrum.i18n import _, languages from electrum import util, coinchooser, paymentrequest -from electrum.util import base_units_list +from electrum.util import base_units_list, event_listener from electrum.gui import messages from .util import (ColorScheme, WindowModalDialog, HelpLabel, Buttons, - CloseButton) + CloseButton, QtEventListener) if TYPE_CHECKING: @@ -47,7 +47,7 @@ if TYPE_CHECKING: from .main_window import ElectrumWindow -class SettingsDialog(QDialog): +class SettingsDialog(QDialog, QtEventListener): def __init__(self, window: 'ElectrumWindow', config: 'SimpleConfig'): QDialog.__init__(self) @@ -60,7 +60,7 @@ class SettingsDialog(QDialog): self.fx = window.fx self.wallet = window.wallet - util.register_callback(self.on_network_callback, ['alias_received']) + self.register_callbacks() self.app.alias_received_signal.connect(self.set_alias_color) vbox = QVBoxLayout() @@ -559,10 +559,10 @@ class SettingsDialog(QDialog): vbox.addStretch(1) vbox.addLayout(Buttons(CloseButton(self))) self.setLayout(vbox) - - def on_network_callback(self, cb): - if cb == 'alias_received': - self.app.alias_received_signal.emit() + + @event_listener + def on_event_alias_received(self): + self.app.alias_received_signal.emit() def set_alias_color(self): if not self.config.get('alias'): diff --git a/electrum/gui/qt/util.py b/electrum/gui/qt/util.py index b2a766658..5c6028d89 100644 --- a/electrum/gui/qt/util.py +++ b/electrum/gui/qt/util.py @@ -28,6 +28,7 @@ from PyQt5.QtWidgets import (QPushButton, QLabel, QMessageBox, QHBoxLayout, from electrum.i18n import _, languages from electrum.util import FileImportFailed, FileExportFailed, make_aiohttp_session, resource_path +from electrum.util import EventListener, event_listener from electrum.invoices import PR_UNPAID, PR_PAID, PR_EXPIRED, PR_INFLIGHT, PR_UNKNOWN, PR_FAILED, PR_ROUTING, PR_UNCONFIRMED from electrum.logging import Logger from electrum.qrreader import MissingQrDetectionLib @@ -1503,8 +1504,6 @@ class VTabWidget(QtWidgets.QTabWidget): return super().resizeEvent(e) -from electrum.util import EventListener, event_listener - class QtEventListener(EventListener): qt_callback_signal = QtCore.pyqtSignal(tuple) @@ -1523,8 +1522,7 @@ class QtEventListener(EventListener): # decorator for members of the QtEventListener class def qt_event_listener(func): - assert func.__name__.startswith('on_event_') - func._is_event_listener = True + func = event_listener(func) @wraps(func) def decorator(self, *args): self.qt_callback_signal.emit( (func,) + args) diff --git a/electrum/gui/stdio.py b/electrum/gui/stdio.py index ed5632efd..c693c586b 100644 --- a/electrum/gui/stdio.py +++ b/electrum/gui/stdio.py @@ -9,7 +9,7 @@ from electrum import util from electrum import WalletStorage, Wallet from electrum.wallet import Abstract_Wallet from electrum.wallet_db import WalletDB -from electrum.util import format_satoshis +from electrum.util import format_satoshis, EventListener, event_listener from electrum.bitcoin import is_address, COIN from electrum.transaction import PartialTxOutput from electrum.network import TxBroadcastError, BestEffortRequestFailed @@ -20,7 +20,7 @@ _ = lambda x:x # i18n # written by rofl0r, with some bits stolen from the text gui (ncurses) -class ElectrumGui(BaseElectrumGui): +class ElectrumGui(BaseElectrumGui, EventListener): def __init__(self, *, config, daemon, plugins): BaseElectrumGui.__init__(self, config=config, daemon=daemon, plugins=plugins) @@ -47,7 +47,7 @@ class ElectrumGui(BaseElectrumGui): self.wallet.start_network(self.network) self.contacts = self.wallet.contacts - util.register_callback(self.on_network, ['wallet_updated', 'network_updated', 'banner']) + self.register_callbacks() self.commands = [_("[h] - displays this help text"), \ _("[i] - display transaction history"), \ _("[o] - enter payment order"), \ @@ -59,11 +59,17 @@ class ElectrumGui(BaseElectrumGui): _("[q] - quit")] self.num_commands = len(self.commands) - def on_network(self, event, *args): - if event in ['wallet_updated', 'network_updated']: - self.updated() - elif event == 'banner': - self.print_banner() + @event_listener + def on_event_wallet_updated(self, wallet): + self.updated() + + @event_listener + def on_event_network_updated(self): + self.updated() + + @event_listener + def on_event_banner(self): + self.print_banner() def main_command(self): self.print_balance() diff --git a/electrum/util.py b/electrum/util.py index 9ad3bfefd..ec8c31b3e 100644 --- a/electrum/util.py +++ b/electrum/util.py @@ -1633,7 +1633,6 @@ register_callback = callback_mgr.register_callback unregister_callback = callback_mgr.unregister_callback - class EventListener: def _list_callbacks(self): @@ -1650,8 +1649,8 @@ class EventListener: def register_callbacks(self): for name, method in self._list_callbacks(): - _logger.info(f'registering callback {method}') - register_callback(method, [name]) + _logger.info(f'registering callback {method}') + register_callback(method, [name]) def unregister_callbacks(self): for name, method in self._list_callbacks():