Initial commit

This commit is contained in:
2026-10-09 02:48:24 +02:00
commit e05d973a8d
133 changed files with 101507 additions and 0 deletions

72
app/workers.py Normal file
View 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