Initial commit
This commit is contained in:
72
app/workers.py
Normal file
72
app/workers.py
Normal file
@@ -0,0 +1,72 @@
|
||||
"""Run long operations off the GUI thread, with progress messages and cancel."""
|
||||
import traceback
|
||||
|
||||
from PySide6.QtCore import QObject, QRunnable, QThreadPool, Signal
|
||||
|
||||
|
||||
class Cancelled(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class _Signals(QObject):
|
||||
done = Signal(object)
|
||||
failed = Signal(object, str)
|
||||
progress = Signal(str)
|
||||
|
||||
|
||||
class Job(QRunnable):
|
||||
"""fn(job) -> result. Inside fn call job.report(msg) and job.check() (raises Cancelled)."""
|
||||
|
||||
def __init__(self, fn):
|
||||
super().__init__()
|
||||
self.fn = fn
|
||||
self.signals = _Signals()
|
||||
self.cancelled = False
|
||||
|
||||
def report(self, msg):
|
||||
self.check()
|
||||
self.signals.progress.emit(str(msg))
|
||||
|
||||
def check(self):
|
||||
if self.cancelled:
|
||||
raise Cancelled()
|
||||
|
||||
def cancel(self):
|
||||
self.cancelled = True
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
r = self.fn(self)
|
||||
except Cancelled:
|
||||
self.signals.failed.emit(Cancelled(), "cancelled")
|
||||
except Exception as e: # noqa: BLE001 - shown to the user
|
||||
self.signals.failed.emit(e, traceback.format_exc())
|
||||
else:
|
||||
self.signals.done.emit(r)
|
||||
|
||||
|
||||
class Runner:
|
||||
"""Starts jobs on a thread pool (or inline when `sync`, used by the GUI tests)."""
|
||||
|
||||
def __init__(self, sync=False):
|
||||
self.sync = sync
|
||||
self.pool = QThreadPool.globalInstance()
|
||||
self.active = set() # keep Python references until the queued result signal is delivered
|
||||
|
||||
def start(self, fn, on_done=None, on_failed=None, on_progress=None):
|
||||
job = Job(fn)
|
||||
job.setAutoDelete(False)
|
||||
self.active.add(job)
|
||||
job.signals.done.connect(lambda *_: self.active.discard(job))
|
||||
job.signals.failed.connect(lambda *_: self.active.discard(job))
|
||||
if on_done:
|
||||
job.signals.done.connect(on_done)
|
||||
if on_failed:
|
||||
job.signals.failed.connect(on_failed)
|
||||
if on_progress:
|
||||
job.signals.progress.connect(on_progress)
|
||||
if self.sync:
|
||||
job.run()
|
||||
else:
|
||||
self.pool.start(job)
|
||||
return job
|
||||
Reference in New Issue
Block a user