forked from Support/sp-hydra-veil-gui
Thread Safety Tool for bottom left. Transitioned navigator's prints to local logs via core's logger tool. Rolled back previous commit with bottom left status observers doing emit tools directly
This commit is contained in:
parent
332a84f187
commit
50e9469178
5 changed files with 69 additions and 31 deletions
Binary file not shown.
|
|
@ -23,6 +23,9 @@ from core.errors.logger import logger as core_logger
|
||||||
|
|
||||||
core_logger.propagate = False
|
core_logger.propagate = False
|
||||||
|
|
||||||
|
from gui.v2.infrastructure.ThreadSafetyTool import ThreadSafetyTool
|
||||||
|
mainthread = ThreadSafetyTool()
|
||||||
|
|
||||||
from gui.v2.infrastructure import orm
|
from gui.v2.infrastructure import orm
|
||||||
from gui.v2.infrastructure.navigator import Navigator
|
from gui.v2.infrastructure.navigator import Navigator
|
||||||
from gui.v2.infrastructure.setup_observers import setup_observers
|
from gui.v2.infrastructure.setup_observers import setup_observers
|
||||||
|
|
@ -656,7 +659,21 @@ class CustomWindow(QMainWindow):
|
||||||
self.status_label.setStyleSheet(
|
self.status_label.setStyleSheet(
|
||||||
f"color: rgb(0, 255, 255); font-size: {font_size}px;")
|
f"color: rgb(0, 255, 255); font-size: {font_size}px;")
|
||||||
|
|
||||||
|
@mainthread # wrapper of ThreadSafetyTool
|
||||||
def update_status(self, text, clear=False):
|
def update_status(self, text, clear=False):
|
||||||
|
"""
|
||||||
|
Purpose:
|
||||||
|
Update the bottom left status bar
|
||||||
|
|
||||||
|
Features:
|
||||||
|
Forced on the main Thread by ThreadSafetyTool
|
||||||
|
|
||||||
|
Depends on:
|
||||||
|
v2.infrastructure.ThreadSafetyTool
|
||||||
|
|
||||||
|
Called by:
|
||||||
|
v2.infrastructure.setup_observers (outside this class)
|
||||||
|
"""
|
||||||
if text is None:
|
if text is None:
|
||||||
self._set_status_font_size(16)
|
self._set_status_font_size(16)
|
||||||
self.status_label.setText('Status:')
|
self.status_label.setText('Status:')
|
||||||
|
|
|
||||||
21
gui/v2/infrastructure/ThreadSafetyTool.py
Normal file
21
gui/v2/infrastructure/ThreadSafetyTool.py
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from PyQt6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QPushButton, QLabel
|
||||||
|
from PyQt6.QtCore import QObject, pyqtSignal, Qt
|
||||||
|
|
||||||
|
class ThreadSafetyTool(QObject):
|
||||||
|
"""Marshal function calls to the main thread."""
|
||||||
|
_signal = pyqtSignal(object, tuple, dict)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self._signal.connect(self._execute, Qt.ConnectionType.QueuedConnection)
|
||||||
|
|
||||||
|
def _execute(self, func, args, kwargs):
|
||||||
|
func(*args, **kwargs)
|
||||||
|
|
||||||
|
def __call__(self, func):
|
||||||
|
"""Decorator that forces a function to run on the main thread."""
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
self._signal.emit(func, args, kwargs)
|
||||||
|
return wrapper
|
||||||
|
|
@ -13,6 +13,7 @@ from gui.v2.infrastructure.page_registry import (
|
||||||
)
|
)
|
||||||
from gui.v2.workers.page_data_worker import PageDataWorker
|
from gui.v2.workers.page_data_worker import PageDataWorker
|
||||||
from gui.v2.actions.flags import is_fast_registration_enabled
|
from gui.v2.actions.flags import is_fast_registration_enabled
|
||||||
|
from core.errors.logger import logger
|
||||||
|
|
||||||
|
|
||||||
class Navigator(QObject):
|
class Navigator(QObject):
|
||||||
|
|
@ -79,26 +80,25 @@ class Navigator(QObject):
|
||||||
|
|
||||||
def _preload_one(self, name):
|
def _preload_one(self, name):
|
||||||
if name in self._instances:
|
if name in self._instances:
|
||||||
print(f"[preload] {name:30s} SKIP (already cached)", flush=True)
|
logger.debug(f"[preload] {name:30s} SKIP (already cached)")
|
||||||
return False
|
return False
|
||||||
if name not in PAGE_REGISTRY:
|
if name not in PAGE_REGISTRY:
|
||||||
print(f"[preload] {name:30s} SKIP (not in registry)", flush=True)
|
logger.debug(f"[preload] {name:30s} SKIP (not in registry)")
|
||||||
return False
|
return False
|
||||||
module_path, class_name = PAGE_REGISTRY[name]
|
module_path, class_name = PAGE_REGISTRY[name]
|
||||||
print(f"[preload] {name:30s} LOAD {module_path}.{class_name}", flush=True)
|
logger.debug(f"[preload] {name:30s} LOAD {module_path}.{class_name}")
|
||||||
t0 = time.perf_counter()
|
t0 = time.perf_counter()
|
||||||
try:
|
try:
|
||||||
page = self._instantiate(name)
|
page = self._instantiate(name)
|
||||||
self.register_instance(name, page)
|
self.register_instance(name, page)
|
||||||
dt_ms = (time.perf_counter() - t0) * 1000.0
|
dt_ms = (time.perf_counter() - t0) * 1000.0
|
||||||
print(f"[preload] {name:30s} OK ({dt_ms:6.1f} ms, "
|
logger.debug(f"[preload] {name:30s} OK ({dt_ms:6.1f} ms, "
|
||||||
f"cached={len(self._instances)}, stack_size={self.page_stack.count()})",
|
f"cached={len(self._instances)}, stack_size={self.page_stack.count()})")
|
||||||
flush=True)
|
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
dt_ms = (time.perf_counter() - t0) * 1000.0
|
dt_ms = (time.perf_counter() - t0) * 1000.0
|
||||||
print(f"[preload] {name:30s} FAIL ({dt_ms:6.1f} ms) "
|
logger.debug(f"[preload] {name:30s} FAIL ({dt_ms:6.1f} ms) "
|
||||||
f"{type(e).__name__}: {e}", flush=True)
|
f"{type(e).__name__}: {e}")
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
from core.errors.logger import logger as core_logger
|
from core.errors.logger import logger as core_logger
|
||||||
core_logger.warning(f"Background preload failed for '{name}': "
|
core_logger.warning(f"Background preload failed for '{name}': "
|
||||||
|
|
@ -121,29 +121,29 @@ class Navigator(QObject):
|
||||||
self._preload_inflight = 0
|
self._preload_inflight = 0
|
||||||
self._preload_summary_emitted = False
|
self._preload_summary_emitted = False
|
||||||
self._preload_t_start = time.perf_counter()
|
self._preload_t_start = time.perf_counter()
|
||||||
print(f"[preload] ============================================================", flush=True)
|
logger.debug(f"[preload] ============================================================")
|
||||||
print(f"[preload] starting background preload: {self._preload_total} pages queued", flush=True)
|
logger.debug(f"[preload] starting background preload: {self._preload_total} pages queued")
|
||||||
print(f"[preload] order : {', '.join(self._preload_queue)}", flush=True)
|
logger.debug(f"[preload] order : {', '.join(self._preload_queue)}")
|
||||||
print(f"[preload] skipped : {', '.join(sorted(PRELOAD_SKIP))} (unsafe init)", flush=True)
|
logger.debug(f"[preload] skipped : {', '.join(sorted(PRELOAD_SKIP))} (unsafe init)")
|
||||||
if fast_mode:
|
if fast_mode:
|
||||||
print(f"[preload] fastmode: ON -> skipping regular-flow pages: "
|
logger.debug(f"[preload] fastmode: ON -> skipping regular-flow pages: "
|
||||||
f"{', '.join(skipped_regular) if skipped_regular else '(none)'}", flush=True)
|
f"{', '.join(skipped_regular) if skipped_regular else '(none)'}")
|
||||||
print(f"[preload] gap : 250 ms between loads, 700 ms initial delay", flush=True)
|
logger.debug(f"[preload] gap : 250 ms between loads, 700 ms initial delay")
|
||||||
print(f"[preload] ============================================================", flush=True)
|
logger.debug(f"[preload] ============================================================")
|
||||||
self._preload_timer.start(700)
|
self._preload_timer.start(700)
|
||||||
|
|
||||||
def _preload_next(self):
|
def _preload_next(self):
|
||||||
while self._preload_queue and self._preload_queue[0] in self._instances:
|
while self._preload_queue and self._preload_queue[0] in self._instances:
|
||||||
skipped = self._preload_queue.pop(0)
|
skipped = self._preload_queue.pop(0)
|
||||||
self._preload_skipped_runtime += 1
|
self._preload_skipped_runtime += 1
|
||||||
print(f"[preload] {skipped:30s} SKIP (user navigated here first)", flush=True)
|
logger.debug(f"[preload] {skipped:30s} SKIP (user navigated here first)")
|
||||||
if not self._preload_queue:
|
if not self._preload_queue:
|
||||||
self._maybe_emit_preload_summary()
|
self._maybe_emit_preload_summary()
|
||||||
return
|
return
|
||||||
self._preload_dispatched += 1
|
self._preload_dispatched += 1
|
||||||
idx = self._preload_dispatched
|
idx = self._preload_dispatched
|
||||||
name = self._preload_queue.pop(0)
|
name = self._preload_queue.pop(0)
|
||||||
print(f"[preload] ---- {idx}/{self._preload_total} ----", flush=True)
|
logger.debug(f"[preload] ---- {idx}/{self._preload_total} ----")
|
||||||
if name in ASYNC_PREPARE:
|
if name in ASYNC_PREPARE:
|
||||||
self._dispatch_async_preload(name)
|
self._dispatch_async_preload(name)
|
||||||
else:
|
else:
|
||||||
|
|
@ -165,7 +165,7 @@ class Navigator(QObject):
|
||||||
|
|
||||||
def _dispatch_async_preload(self, name):
|
def _dispatch_async_preload(self, name):
|
||||||
module_path, class_name = PAGE_REGISTRY[name]
|
module_path, class_name = PAGE_REGISTRY[name]
|
||||||
print(f"[preload] {name:30s} ASYNC {module_path}.{class_name}", flush=True)
|
logger.debug(f"[preload] {name:30s} ASYNC {module_path}.{class_name}")
|
||||||
worker = PageDataWorker(name, self._prepare_page_data, name)
|
worker = PageDataWorker(name, self._prepare_page_data, name)
|
||||||
worker.data_ready.connect(self._on_preload_data_ready)
|
worker.data_ready.connect(self._on_preload_data_ready)
|
||||||
worker.failed.connect(self._on_preload_failed)
|
worker.failed.connect(self._on_preload_failed)
|
||||||
|
|
@ -177,22 +177,22 @@ class Navigator(QObject):
|
||||||
def _on_preload_data_ready(self, name, payload):
|
def _on_preload_data_ready(self, name, payload):
|
||||||
self._preload_inflight -= 1
|
self._preload_inflight -= 1
|
||||||
if name in self._instances:
|
if name in self._instances:
|
||||||
print(f"[preload] {name:30s} SKIP (already cached, async result dropped)", flush=True)
|
logger.debug(f"[preload] {name:30s} SKIP (already cached, async result dropped)")
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
page = self._instantiate(name, prepared=payload)
|
page = self._instantiate(name, prepared=payload)
|
||||||
self.register_instance(name, page)
|
self.register_instance(name, page)
|
||||||
self._preload_done += 1
|
self._preload_done += 1
|
||||||
print(f"[preload] {name:30s} OK (async, cached={len(self._instances)}, "
|
logger.debug(f"[preload] {name:30s} OK (async, cached={len(self._instances)}, "
|
||||||
f"stack_size={self.page_stack.count()})", flush=True)
|
f"stack_size={self.page_stack.count()})")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._preload_failed += 1
|
self._preload_failed += 1
|
||||||
print(f"[preload] {name:30s} FAIL (async build) {type(e).__name__}: {e}", flush=True)
|
logger.debug(f"[preload] {name:30s} FAIL (async build) {type(e).__name__}: {e}")
|
||||||
self._maybe_emit_preload_summary()
|
self._maybe_emit_preload_summary()
|
||||||
|
|
||||||
def _on_preload_failed(self, name, error):
|
def _on_preload_failed(self, name, error):
|
||||||
self._preload_inflight -= 1
|
self._preload_inflight -= 1
|
||||||
print(f"[preload] {name:30s} FAIL (async prepare) {error}", flush=True)
|
logger.debug(f"[preload] {name:30s} FAIL (async prepare) {error}")
|
||||||
if name not in self._instances:
|
if name not in self._instances:
|
||||||
ok = self._preload_one(name)
|
ok = self._preload_one(name)
|
||||||
if ok:
|
if ok:
|
||||||
|
|
@ -215,10 +215,10 @@ class Navigator(QObject):
|
||||||
|
|
||||||
def _emit_preload_summary(self):
|
def _emit_preload_summary(self):
|
||||||
total_s = time.perf_counter() - self._preload_t_start
|
total_s = time.perf_counter() - self._preload_t_start
|
||||||
print(f"[preload] ============================================================", flush=True)
|
logger.debug(f"[preload] ============================================================")
|
||||||
print(f"[preload] complete: {self._preload_done} loaded, "
|
logger.debug(f"[preload] complete: {self._preload_done} loaded, "
|
||||||
f"{self._preload_failed} failed, "
|
f"{self._preload_failed} failed, "
|
||||||
f"{self._preload_skipped_runtime} skipped (user beat us to it) "
|
f"{self._preload_skipped_runtime} skipped (user beat us to it) "
|
||||||
f"in {total_s:.2f}s", flush=True)
|
f"in {total_s:.2f}s")
|
||||||
print(f"[preload] cached pages now: {sorted(self._instances.keys())}", flush=True)
|
logger.debug(f"[preload] cached pages now: {sorted(self._instances.keys())}")
|
||||||
print(f"[preload] ============================================================", flush=True)
|
logger.debug(f"[preload] ============================================================")
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[project]
|
[project]
|
||||||
name = "sp-hydra-veil-gui"
|
name = "sp-hydra-veil-gui"
|
||||||
version = "2.4.6"
|
version = "2.4.7"
|
||||||
authors = [
|
authors = [
|
||||||
{ name = "Simplified Privacy" },
|
{ name = "Simplified Privacy" },
|
||||||
]
|
]
|
||||||
|
|
@ -12,7 +12,7 @@ classifiers = [
|
||||||
"Operating System :: POSIX :: Linux",
|
"Operating System :: POSIX :: Linux",
|
||||||
]
|
]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"sp-hydra-veil-core == 2.3.8",
|
"sp-hydra-veil-core == 2.3.9",
|
||||||
"pyperclip ~= 1.9.0",
|
"pyperclip ~= 1.9.0",
|
||||||
"pyqt6 ~= 6.7.1",
|
"pyqt6 ~= 6.7.1",
|
||||||
"qrcode[pil] ~= 8.2"
|
"qrcode[pil] ~= 8.2"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue