<commit_before>import asyncio
import queue
import threading
import time
from typing import Optional, TYPE_CHECKING

from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QTimer

from electrum import bitcoin
from electrum.i18n import _
from electrum.invoices import InvoiceError, PR_DEFAULT_EXPIRATION_WHEN_CREATING
from electrum.logging import get_logger
from electrum.network import TxBroadcastError, BestEffortRequestFailed
from electrum.transaction import PartialTxOutput
from electrum.util import (parse_max_spend, InvalidPassword, event_listener)

from .auth import AuthMixin, auth_protect
from .qeaddresslistmodel import QEAddressListModel
from .qechannellistmodel import QEChannelListModel
from .qeinvoicelistmodel import QEInvoiceListModel, QERequestListModel
from .qetransactionlistmodel import QETransactionListModel
from .qetypes import QEAmount
from .util import QtEventListener, qt_event_listener

if TYPE_CHECKING:
    from electrum.wallet import Abstract_Wallet


class QEWallet(AuthMixin, QObject, QtEventListener):
    __instances = []

    # this factory method should be used to instantiate QEWallet
    # so we have only one QEWallet for each electrum.wallet
    @classmethod
    def getInstanceFor(cls, wallet):
        for i in cls.__instances:
            if i.wallet == wallet:
                return i
        i = QEWallet(wallet)
        cls.__instances.append(i)
        return i

    _logger = get_logger(__name__)

    # emitted when wallet wants to display a user notification
    # actual presentation should be handled on app or window level
    userNotify = pyqtSignal(object, object)

    # shared signal for many static wallet properties
    dataChanged = pyqtSignal()

    isUptodateChanged = pyqtSignal()
    requestStatusChanged = pyqtSignal([str,int], arguments=['key','status'])
    requestCreateSuccess = pyqtSignal([str], arguments=['key'])
    requestCreateError = pyqtSignal([str,str], arguments=['code','error'])
    invoiceStatusChanged = pyqtSignal([str,int], arguments=['key','status'])
    invoiceCreateSuccess = pyqtSignal()
    invoiceCreateError = pyqtSignal([str,str], arguments=['code','error'])
    paymentSucceeded = pyqtSignal([str], arguments=['key'])
    paymentFailed = pyqtSignal([str,str], arguments=['key','reason'])
    requestNewPassword = pyqtSignal()
    transactionSigned = pyqtSignal([str], arguments=['txid'])
    #broadcastSucceeded = pyqtSignal([str], arguments=['txid'])
    broadcastFailed = pyqtSignal([str,str,str], arguments=['txid','code','reason'])
    labelsUpdated = pyqtSignal()

    _network_signal = pyqtSignal(str, object)

    _isUpToDate = False
    _synchronizing = False
    _synchronizing_progress = ''

    def __init__(self, wallet: 'Abstract_Wallet', parent=None):
        super().__init__(parent)
        self.wallet = wallet

        self._historyModel = None
        self._addressModel = None
        self._requestModel = None
        self._invoiceModel = None
        self._channelModel = None

        self.tx_notification_queue = queue.Queue()
        self.tx_notification_last_time = 0

        self.notification_timer = QTimer(self)
        self.notification_timer.setSingleShot(False)
        self.notification_timer.setInterval(500)  # msec
        self.notification_timer.timeout.connect(self.notify_transactions)

        # 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...

        self.register_callbacks()
        self.destroyed.connect(lambda: self.on_destroy())

    @pyqtProperty(bool, notify=isUptodateChanged)
    def isUptodate(self):
        return self._isUpToDate

    synchronizingChanged = pyqtSignal()
    @pyqtProperty(bool, notify=synchronizingChanged)
    def synchronizing(self):
        return self._synchronizing

    @synchronizing.setter
    def synchronizing(self, synchronizing):
        if self._synchronizing != synchronizing:
            self._synchronizing = synchronizing
            self.synchronizingChanged.emit()

    synchronizingProgressChanged = pyqtSignal()
    @pyqtProperty(str, notify=synchronizingProgressChanged)
    def synchronizing_progress(self):
        return self._synchronizing_progress

    @synchronizing_progress.setter
    def synchronizing_progress(self, progress):
        if self._synchronizing_progress != progress:
            self._synchronizing_progress = progress
            self.synchronizingProgressChanged.emit()

    @event_listener
    def on_event_status(self):
        self._logger.debug('status')
        uptodate = self.wallet.is_up_to_date()
        if self._isUpToDate != uptodate:
            self._isUpToDate = uptodate
            self.isUptodateChanged.emit()

        if self.wallet.network.is_connected():
            server_height = self.wallet.network.get_server_height()
            server_lag = self.wallet.network.get_local_height() - server_height
            # Server height can be 0 after switching to a new server
            # until we get a headers subscription request response.
            # Display the synchronizing message in that case.
            if not self._isUpToDate or server_height == 0:
                num_sent, num_answered = self.wallet.adb.get_history_sync_state_details()
                self.synchronizing_progress = ("{} ({}/{})"
                                .format(_("Synchronizing..."), num_answered, num_sent))
                self.synchronizing = True
            else:
                self.synchronizing_progress = ''
                self.synchronizing = False

    @event_listener
    def on_event_request_status(self, wallet, key, status):
        if wallet == self.wallet:
            self._logger.debug('request status %d for key %s' % (status, key))
            self.requestStatusChanged.emit(key, status)

    @event_listener
    def on_event_invoice_status(self, wallet, key):
        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}')

    @qt_event_listener
    def on_event_new_transaction(self, wallet, tx):
        if wallet == self.wallet:
            self.add_tx_notification(tx)
            self.historyModel.init_model() # TODO: be less dramatic

    @qt_event_listener
    def on_event_verified(self, wallet, txid, info):<commit_msg>qml: update history when LN payment arrives<commit_after>import asyncio
import queue
import threading
import time
from typing import Optional, TYPE_CHECKING

from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QTimer

from electrum import bitcoin
from electrum.i18n import _
from electrum.invoices import InvoiceError, PR_DEFAULT_EXPIRATION_WHEN_CREATING, PR_PAID
from electrum.logging import get_logger
from electrum.network import TxBroadcastError, BestEffortRequestFailed
from electrum.transaction import PartialTxOutput
from electrum.util import (parse_max_spend, InvalidPassword, event_listener)

from .auth import AuthMixin, auth_protect
from .qeaddresslistmodel import QEAddressListModel
from .qechannellistmodel import QEChannelListModel
from .qeinvoicelistmodel import QEInvoiceListModel, QERequestListModel
from .qetransactionlistmodel import QETransactionListModel
from .qetypes import QEAmount
from .util import QtEventListener, qt_event_listener

if TYPE_CHECKING:
    from electrum.wallet import Abstract_Wallet


class QEWallet(AuthMixin, QObject, QtEventListener):
    __instances = []

    # this factory method should be used to instantiate QEWallet
    # so we have only one QEWallet for each electrum.wallet
    @classmethod
    def getInstanceFor(cls, wallet):
        for i in cls.__instances:
            if i.wallet == wallet:
                return i
        i = QEWallet(wallet)
        cls.__instances.append(i)
        return i

    _logger = get_logger(__name__)

    # emitted when wallet wants to display a user notification
    # actual presentation should be handled on app or window level
    userNotify = pyqtSignal(object, object)

    # shared signal for many static wallet properties
    dataChanged = pyqtSignal()

    isUptodateChanged = pyqtSignal()
    requestStatusChanged = pyqtSignal([str,int], arguments=['key','status'])
    requestCreateSuccess = pyqtSignal([str], arguments=['key'])
    requestCreateError = pyqtSignal([str,str], arguments=['code','error'])
    invoiceStatusChanged = pyqtSignal([str,int], arguments=['key','status'])
    invoiceCreateSuccess = pyqtSignal()
    invoiceCreateError = pyqtSignal([str,str], arguments=['code','error'])
    paymentSucceeded = pyqtSignal([str], arguments=['key'])
    paymentFailed = pyqtSignal([str,str], arguments=['key','reason'])
    requestNewPassword = pyqtSignal()
    transactionSigned = pyqtSignal([str], arguments=['txid'])
    #broadcastSucceeded = pyqtSignal([str], arguments=['txid'])
    broadcastFailed = pyqtSignal([str,str,str], arguments=['txid','code','reason'])
    labelsUpdated = pyqtSignal()

    _network_signal = pyqtSignal(str, object)

    _isUpToDate = False
    _synchronizing = False
    _synchronizing_progress = ''

    def __init__(self, wallet: 'Abstract_Wallet', parent=None):
        super().__init__(parent)
        self.wallet = wallet

        self._historyModel = None
        self._addressModel = None
        self._requestModel = None
        self._invoiceModel = None
        self._channelModel = None

        self.tx_notification_queue = queue.Queue()
        self.tx_notification_last_time = 0

        self.notification_timer = QTimer(self)
        self.notification_timer.setSingleShot(False)
        self.notification_timer.setInterval(500)  # msec
        self.notification_timer.timeout.connect(self.notify_transactions)

        # 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...

        self.register_callbacks()
        self.destroyed.connect(lambda: self.on_destroy())

    @pyqtProperty(bool, notify=isUptodateChanged)
    def isUptodate(self):
        return self._isUpToDate

    synchronizingChanged = pyqtSignal()
    @pyqtProperty(bool, notify=synchronizingChanged)
    def synchronizing(self):
        return self._synchronizing

    @synchronizing.setter
    def synchronizing(self, synchronizing):
        if self._synchronizing != synchronizing:
            self._synchronizing = synchronizing
            self.synchronizingChanged.emit()

    synchronizingProgressChanged = pyqtSignal()
    @pyqtProperty(str, notify=synchronizingProgressChanged)
    def synchronizing_progress(self):
        return self._synchronizing_progress

    @synchronizing_progress.setter
    def synchronizing_progress(self, progress):
        if self._synchronizing_progress != progress:
            self._synchronizing_progress = progress
            self.synchronizingProgressChanged.emit()

    @event_listener
    def on_event_status(self):
        self._logger.debug('status')
        uptodate = self.wallet.is_up_to_date()
        if self._isUpToDate != uptodate:
            self._isUpToDate = uptodate
            self.isUptodateChanged.emit()

        if self.wallet.network.is_connected():
            server_height = self.wallet.network.get_server_height()
            server_lag = self.wallet.network.get_local_height() - server_height
            # Server height can be 0 after switching to a new server
            # until we get a headers subscription request response.
            # Display the synchronizing message in that case.
            if not self._isUpToDate or server_height == 0:
                num_sent, num_answered = self.wallet.adb.get_history_sync_state_details()
                self.synchronizing_progress = ("{} ({}/{})"
                                .format(_("Synchronizing..."), num_answered, num_sent))
                self.synchronizing = True
            else:
                self.synchronizing_progress = ''
                self.synchronizing = False

    @qt_event_listener
    def on_event_request_status(self, wallet, key, status):
        if wallet == self.wallet:
            self._logger.debug('request status %d for key %s' % (status, key))
            self.requestStatusChanged.emit(key, status)
            if status == PR_PAID:
                # might be new incoming LN payment, update history
                # TODO: only update if it was paid over lightning,
                # and even then, we can probably just add the payment instead
                # of recreating the whole history (expensive)
                self.historyModel.init_model()

    @event_listener
    def on_event_invoice_status(self, wallet, key):
        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}')

    @qt_event_listener
    def on_event_new_transaction(self, wallet, tx):
        if wallet == self.wallet:
            self.add_tx_notification(tx)
            self.historyModel.init_model() # TODO: be less dramatic

    @qt_event_listener
    def on_event_verified(self, wallet, txid, info):
{'fraction_non_alphanumeric': 0.0831726993646034, 'fraction_numerical': 0.001285071749839366, 'mean_word_length': 3.1163679106670585, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2946846', 'n_tokens_mistral': 3929, 'n_tokens_neox': 3850, 'n_words': 966}