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:
SimplifiedPrivacy 2026-07-12 17:06:08 -04:00
parent 332a84f187
commit 50e9469178
5 changed files with 69 additions and 31 deletions

View file

@ -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:')

View 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

View file

@ -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] ============================================================")

View file

@ -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"