Merge pull request #9759 from accumulator/common_taskthread
qt,qml: move TaskThread to common_qt
This commit is contained in:
@@ -1,16 +1,19 @@
|
||||
from typing import Optional
|
||||
import queue
|
||||
import sys
|
||||
from typing import Optional, NamedTuple, Callable
|
||||
import os.path
|
||||
|
||||
from PyQt6 import QtGui
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtCore import Qt, QThread, pyqtSignal
|
||||
from PyQt6.QtGui import QColor, QPen, QPaintDevice, QFontDatabase, QImage
|
||||
import qrcode
|
||||
|
||||
from electrum.i18n import _
|
||||
|
||||
from electrum.logging import Logger
|
||||
|
||||
_cached_font_ids: dict[str, int] = {}
|
||||
|
||||
|
||||
def get_font_id(filename: str) -> int:
|
||||
font_id = _cached_font_ids.get(filename)
|
||||
if font_id is not None:
|
||||
@@ -22,6 +25,7 @@ def get_font_id(filename: str) -> int:
|
||||
_cached_font_ids[filename] = font_id
|
||||
return font_id
|
||||
|
||||
|
||||
def draw_qr(
|
||||
*,
|
||||
qr: Optional[qrcode.main.QRCode],
|
||||
@@ -116,3 +120,73 @@ def paintQR(data) -> Optional[QImage]:
|
||||
return base_img
|
||||
|
||||
|
||||
class TaskThread(QThread, Logger):
|
||||
"""Thread that runs background tasks. Callbacks are guaranteed
|
||||
to happen in the context of its parent."""
|
||||
|
||||
class Task(NamedTuple):
|
||||
task: Callable
|
||||
cb_success: Optional[Callable]
|
||||
cb_done: Optional[Callable]
|
||||
cb_error: Optional[Callable]
|
||||
cancel: Optional[Callable] = None
|
||||
|
||||
doneSig = pyqtSignal(object, object, object)
|
||||
|
||||
def __init__(self, parent, on_error=None):
|
||||
QThread.__init__(self, parent)
|
||||
Logger.__init__(self)
|
||||
self.on_error = on_error
|
||||
self.tasks = queue.Queue()
|
||||
self._cur_task = None # type: Optional[TaskThread.Task]
|
||||
self._stopping = False
|
||||
self.doneSig.connect(self.on_done)
|
||||
self.start()
|
||||
|
||||
def add(self, task, on_success=None, on_done=None, on_error=None, *, cancel=None):
|
||||
if self._stopping:
|
||||
self.logger.warning(f"stopping or already stopped but tried to add new task.")
|
||||
return
|
||||
on_error = on_error or self.on_error
|
||||
task_ = TaskThread.Task(task, on_success, on_done, on_error, cancel=cancel)
|
||||
self.tasks.put(task_)
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
if self._stopping:
|
||||
break
|
||||
task = self.tasks.get() # type: TaskThread.Task
|
||||
self._cur_task = task
|
||||
if not task or self._stopping:
|
||||
break
|
||||
try:
|
||||
result = task.task()
|
||||
self.doneSig.emit(result, task.cb_done, task.cb_success)
|
||||
except BaseException:
|
||||
self.doneSig.emit(sys.exc_info(), task.cb_done, task.cb_error)
|
||||
|
||||
def on_done(self, result, cb_done, cb_result):
|
||||
# This runs in the parent's thread.
|
||||
if cb_done:
|
||||
cb_done()
|
||||
if cb_result:
|
||||
cb_result(result)
|
||||
|
||||
def stop(self):
|
||||
self._stopping = True
|
||||
# try to cancel currently running task now.
|
||||
# if the task does not implement "cancel", we will have to wait until it finishes.
|
||||
task = self._cur_task
|
||||
if task and task.cancel:
|
||||
task.cancel()
|
||||
# cancel the remaining tasks in the queue
|
||||
while True:
|
||||
try:
|
||||
task = self.tasks.get_nowait()
|
||||
except queue.Empty:
|
||||
break
|
||||
if task and task.cancel:
|
||||
task.cancel()
|
||||
self.tasks.put(None) # in case the thread is still waiting on the queue
|
||||
self.exit()
|
||||
self.wait()
|
||||
|
||||
@@ -11,7 +11,7 @@ from electrum.bip39_recovery import account_discovery
|
||||
from electrum.logging import get_logger
|
||||
from electrum.util import get_asyncio_loop
|
||||
|
||||
from .util import TaskThread
|
||||
from electrum.gui.common_qt.util import TaskThread
|
||||
|
||||
|
||||
class QEBip39RecoveryListModel(QAbstractListModel):
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import math
|
||||
import re
|
||||
import sys
|
||||
import queue
|
||||
|
||||
from functools import wraps
|
||||
from time import time
|
||||
from typing import Callable, Optional, NamedTuple, Tuple
|
||||
from typing import Tuple
|
||||
|
||||
from PyQt6.QtCore import pyqtSignal, QThread
|
||||
from PyQt6.QtCore import pyqtSignal
|
||||
|
||||
from electrum.i18n import _
|
||||
from electrum.logging import Logger
|
||||
from electrum.util import EventListener, event_listener
|
||||
|
||||
|
||||
@@ -73,76 +70,3 @@ def check_password_strength(password: str) -> Tuple[int, str]:
|
||||
score = len(password)*(n + caps + num + extra)/20
|
||||
password_strength = {0: _('Weak'), 1: _('Medium'), 2: _('Strong'), 3: _('Very Strong')}
|
||||
return min(3, int(score)), password_strength[min(3, int(score))]
|
||||
|
||||
|
||||
# TODO: copied from desktop client, this could be moved to a set of common code.
|
||||
class TaskThread(QThread, Logger):
|
||||
"""Thread that runs background tasks. Callbacks are guaranteed
|
||||
to happen in the context of its parent."""
|
||||
|
||||
class Task(NamedTuple):
|
||||
task: Callable
|
||||
cb_success: Optional[Callable]
|
||||
cb_done: Optional[Callable]
|
||||
cb_error: Optional[Callable]
|
||||
cancel: Optional[Callable] = None
|
||||
|
||||
doneSig = pyqtSignal(object, object, object)
|
||||
|
||||
def __init__(self, parent, on_error=None):
|
||||
QThread.__init__(self, parent)
|
||||
Logger.__init__(self)
|
||||
self.on_error = on_error
|
||||
self.tasks = queue.Queue()
|
||||
self._cur_task = None # type: Optional[TaskThread.Task]
|
||||
self._stopping = False
|
||||
self.doneSig.connect(self.on_done)
|
||||
self.start()
|
||||
|
||||
def add(self, task, on_success=None, on_done=None, on_error=None, *, cancel=None):
|
||||
if self._stopping:
|
||||
self.logger.warning(f"stopping or already stopped but tried to add new task.")
|
||||
return
|
||||
on_error = on_error or self.on_error
|
||||
task_ = TaskThread.Task(task, on_success, on_done, on_error, cancel=cancel)
|
||||
self.tasks.put(task_)
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
if self._stopping:
|
||||
break
|
||||
task = self.tasks.get() # type: TaskThread.Task
|
||||
self._cur_task = task
|
||||
if not task or self._stopping:
|
||||
break
|
||||
try:
|
||||
result = task.task()
|
||||
self.doneSig.emit(result, task.cb_done, task.cb_success)
|
||||
except BaseException:
|
||||
self.doneSig.emit(sys.exc_info(), task.cb_done, task.cb_error)
|
||||
|
||||
def on_done(self, result, cb_done, cb_result):
|
||||
# This runs in the parent's thread.
|
||||
if cb_done:
|
||||
cb_done()
|
||||
if cb_result:
|
||||
cb_result(result)
|
||||
|
||||
def stop(self):
|
||||
self._stopping = True
|
||||
# try to cancel currently running task now.
|
||||
# if the task does not implement "cancel", we will have to wait until it finishes.
|
||||
task = self._cur_task
|
||||
if task and task.cancel:
|
||||
task.cancel()
|
||||
# cancel the remaining tasks in the queue
|
||||
while True:
|
||||
try:
|
||||
task = self.tasks.get_nowait()
|
||||
except queue.Empty:
|
||||
break
|
||||
if task and task.cancel:
|
||||
task.cancel()
|
||||
self.tasks.put(None) # in case the thread is still waiting on the queue
|
||||
self.exit()
|
||||
self.wait()
|
||||
|
||||
@@ -14,8 +14,9 @@ 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, TaskThread, Buttons, CancelButton, OkButton
|
||||
from electrum.gui.common_qt.util import TaskThread
|
||||
|
||||
from .util import WindowModalDialog, Buttons, CancelButton, OkButton
|
||||
|
||||
_logger = get_logger(__name__)
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ from .transaction_dialog import show_transaction
|
||||
from .fee_slider import FeeSlider, FeeComboBox
|
||||
from .util import (read_QIcon, ColorScheme, text_dialog, icon_path, WaitingDialog,
|
||||
WindowModalDialog, HelpLabel, Buttons,
|
||||
OkButton, InfoButton, WWLabel, TaskThread, CancelButton,
|
||||
OkButton, InfoButton, WWLabel, CancelButton,
|
||||
CloseButton, MessageBoxMixin, EnterButton, import_meta_gui, export_meta_gui,
|
||||
filename_field, address_field, char_width_in_lineedit, webopen,
|
||||
TRANSACTION_FILE_EXTENSION_FILTER_ANY, MONOSPACE_FONT,
|
||||
@@ -100,6 +100,8 @@ from .swap_dialog import SwapDialog, InvalidSwapParameters
|
||||
from .balance_dialog import (BalanceToolButton, COLOR_FROZEN, COLOR_UNMATURED, COLOR_UNCONFIRMED, COLOR_CONFIRMED,
|
||||
COLOR_LIGHTNING, COLOR_FROZEN_LIGHTNING)
|
||||
|
||||
from electrum.gui.common_qt.util import TaskThread
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import ElectrumGui
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ from typing import (NamedTuple, Callable, Optional, TYPE_CHECKING, List, Any, Se
|
||||
from PyQt6 import QtCore
|
||||
from PyQt6.QtGui import (QFont, QColor, QCursor, QPixmap, QImage,
|
||||
QPalette, QIcon, QFontMetrics, QPainter, QContextMenuEvent, QMovie)
|
||||
from PyQt6.QtCore import (Qt, pyqtSignal, QCoreApplication, QThread, QSize, QRect, QPoint, QObject)
|
||||
from PyQt6.QtCore import (Qt, pyqtSignal, QCoreApplication, QSize, QRect, QPoint, QObject)
|
||||
from PyQt6.QtWidgets import (QPushButton, QLabel, QMessageBox, QHBoxLayout, QVBoxLayout, QLineEdit,
|
||||
QStyle, QDialog, QGroupBox, QButtonGroup, QRadioButton,
|
||||
QFileDialog, QWidget, QToolButton, QPlainTextEdit, QApplication, QToolTip,
|
||||
@@ -24,9 +24,10 @@ from electrum.util import (FileImportFailed, FileExportFailed, resource_path, Ev
|
||||
get_logger, UserCancelled, UserFacingException)
|
||||
from electrum.invoices import (PR_UNPAID, PR_PAID, PR_EXPIRED, PR_INFLIGHT, PR_UNKNOWN, PR_FAILED, PR_ROUTING,
|
||||
PR_UNCONFIRMED, PR_BROADCASTING, PR_BROADCAST)
|
||||
from electrum.logging import Logger
|
||||
from electrum.qrreader import MissingQrDetectionLib, QrCodeResult
|
||||
|
||||
from electrum.gui.common_qt.util import TaskThread
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .main_window import ElectrumWindow
|
||||
from .paytoedit import PayToEdit
|
||||
@@ -1079,78 +1080,6 @@ class PasswordLineEdit(QLineEdit):
|
||||
super().clear()
|
||||
|
||||
|
||||
class TaskThread(QThread, Logger):
|
||||
'''Thread that runs background tasks. Callbacks are guaranteed
|
||||
to happen in the context of its parent.'''
|
||||
|
||||
class Task(NamedTuple):
|
||||
task: Callable
|
||||
cb_success: Optional[Callable]
|
||||
cb_done: Optional[Callable]
|
||||
cb_error: Optional[Callable]
|
||||
cancel: Optional[Callable] = None
|
||||
|
||||
doneSig = pyqtSignal(object, object, object)
|
||||
|
||||
def __init__(self, parent, on_error=None):
|
||||
QThread.__init__(self, parent)
|
||||
Logger.__init__(self)
|
||||
self.on_error = on_error
|
||||
self.tasks = queue.Queue()
|
||||
self._cur_task = None # type: Optional[TaskThread.Task]
|
||||
self._stopping = False
|
||||
self.doneSig.connect(self.on_done)
|
||||
self.start()
|
||||
|
||||
def add(self, task, on_success=None, on_done=None, on_error=None, *, cancel=None):
|
||||
if self._stopping:
|
||||
self.logger.warning(f"stopping or already stopped but tried to add new task.")
|
||||
return
|
||||
on_error = on_error or self.on_error
|
||||
task_ = TaskThread.Task(task, on_success, on_done, on_error, cancel=cancel)
|
||||
self.tasks.put(task_)
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
if self._stopping:
|
||||
break
|
||||
task = self.tasks.get() # type: TaskThread.Task
|
||||
self._cur_task = task
|
||||
if not task or self._stopping:
|
||||
break
|
||||
try:
|
||||
result = task.task()
|
||||
self.doneSig.emit(result, task.cb_done, task.cb_success)
|
||||
except BaseException:
|
||||
self.doneSig.emit(sys.exc_info(), task.cb_done, task.cb_error)
|
||||
|
||||
def on_done(self, result, cb_done, cb_result):
|
||||
# This runs in the parent's thread.
|
||||
if cb_done:
|
||||
cb_done()
|
||||
if cb_result:
|
||||
cb_result(result)
|
||||
|
||||
def stop(self):
|
||||
self._stopping = True
|
||||
# try to cancel currently running task now.
|
||||
# if the task does not implement "cancel", we will have to wait until it finishes.
|
||||
task = self._cur_task
|
||||
if task and task.cancel:
|
||||
task.cancel()
|
||||
# cancel the remaining tasks in the queue
|
||||
while True:
|
||||
try:
|
||||
task = self.tasks.get_nowait()
|
||||
except queue.Empty:
|
||||
break
|
||||
if task and task.cancel:
|
||||
task.cancel()
|
||||
self.tasks.put(None) # in case the thread is still waiting on the queue
|
||||
self.exit()
|
||||
self.wait()
|
||||
|
||||
|
||||
class ColorSchemeItem:
|
||||
def __init__(self, fg_color, bg_color):
|
||||
self.colors = (fg_color, bg_color)
|
||||
|
||||
@@ -31,9 +31,10 @@ from typing import TYPE_CHECKING, Union, Optional, Sequence, Tuple
|
||||
from PyQt6.QtCore import QObject, pyqtSignal, Qt
|
||||
from PyQt6.QtWidgets import QVBoxLayout, QLineEdit, QHBoxLayout, QLabel
|
||||
|
||||
from electrum.gui.common_qt.util import TaskThread
|
||||
from electrum.gui.qt.password_dialog import PasswordLayout, PW_PASSPHRASE
|
||||
from electrum.gui.qt.util import (read_QIcon, WWLabel, OkButton, WindowModalDialog,
|
||||
Buttons, CancelButton, TaskThread, char_width_in_lineedit,
|
||||
Buttons, CancelButton, char_width_in_lineedit,
|
||||
PasswordLineEdit)
|
||||
from electrum.gui.qt.main_window import StatusBarButton
|
||||
from electrum.gui.qt.util import read_QIcon_from_bytes
|
||||
|
||||
@@ -54,14 +54,13 @@ from .plugin import run_hook
|
||||
from .logging import Logger
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .gui.qt.util import TaskThread
|
||||
from .plugins.hw_wallet import HW_PluginBase, HardwareClientBase, HardwareHandlerBase
|
||||
from .gui.common_qt.util import TaskThread
|
||||
from .hw_wallet import HW_PluginBase, HardwareClientBase, HardwareHandlerBase
|
||||
from .wallet_db import WalletDB
|
||||
from .plugin import Device
|
||||
|
||||
|
||||
class CannotDerivePubkey(Exception): pass
|
||||
|
||||
class ScriptTypeNotSupported(Exception): pass
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,9 @@ from PyQt6.QtCore import QObject, pyqtSignal
|
||||
|
||||
from electrum.plugin import hook
|
||||
from electrum.i18n import _
|
||||
from electrum.gui.qt.util import TaskThread, read_QIcon_from_bytes
|
||||
|
||||
from electrum.gui.common_qt.util import TaskThread
|
||||
from electrum.gui.qt.util import read_QIcon_from_bytes
|
||||
|
||||
from .labels import LabelsPlugin
|
||||
|
||||
|
||||
Reference in New Issue
Block a user