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
from gui.v2.infrastructure.ThreadSafetyTool import ThreadSafetyTool
mainthread = ThreadSafetyTool()
from gui.v2.infrastructure import orm
from gui.v2.infrastructure.navigator import Navigator
from gui.v2.infrastructure.setup_observers import setup_observers
@ -656,7 +659,21 @@ class CustomWindow(QMainWindow):
self.status_label.setStyleSheet(
f"color: rgb(0, 255, 255); font-size: {font_size}px;")
@mainthread # wrapper of ThreadSafetyTool
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:
self._set_status_font_size(16)
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.actions.flags import is_fast_registration_enabled
from core.errors.logger import logger
class Navigator(QObject):
@ -79,26 +80,25 @@ class Navigator(QObject):
def _preload_one(self, name):
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
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
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()
try:
page = self._instantiate(name)
self.register_instance(name, page)
dt_ms = (time.perf_counter() - t0) * 1000.0
print(f"[preload] {name:30s} OK ({dt_ms:6.1f} ms, "
f"cached={len(self._instances)}, stack_size={self.page_stack.count()})",
flush=True)
logger.debug(f"[preload] {name:30s} OK ({dt_ms:6.1f} ms, "
f"cached={len(self._instances)}, stack_size={self.page_stack.count()})")
return True
except Exception as e:
dt_ms = (time.perf_counter() - t0) * 1000.0
print(f"[preload] {name:30s} FAIL ({dt_ms:6.1f} ms) "
f"{type(e).__name__}: {e}", flush=True)
logger.debug(f"[preload] {name:30s} FAIL ({dt_ms:6.1f} ms) "
f"{type(e).__name__}: {e}")
traceback.print_exc()
from core.errors.logger import logger as core_logger
core_logger.warning(f"Background preload failed for '{name}': "
@ -121,29 +121,29 @@ class Navigator(QObject):
self._preload_inflight = 0
self._preload_summary_emitted = False
self._preload_t_start = time.perf_counter()
print(f"[preload] ============================================================", flush=True)
print(f"[preload] starting background preload: {self._preload_total} pages queued", flush=True)
print(f"[preload] order : {', '.join(self._preload_queue)}", flush=True)
print(f"[preload] skipped : {', '.join(sorted(PRELOAD_SKIP))} (unsafe init)", flush=True)
logger.debug(f"[preload] ============================================================")
logger.debug(f"[preload] starting background preload: {self._preload_total} pages queued")
logger.debug(f"[preload] order : {', '.join(self._preload_queue)}")
logger.debug(f"[preload] skipped : {', '.join(sorted(PRELOAD_SKIP))} (unsafe init)")
if fast_mode:
print(f"[preload] fastmode: ON -> skipping regular-flow pages: "
f"{', '.join(skipped_regular) if skipped_regular else '(none)'}", flush=True)
print(f"[preload] gap : 250 ms between loads, 700 ms initial delay", flush=True)
print(f"[preload] ============================================================", flush=True)
logger.debug(f"[preload] fastmode: ON -> skipping regular-flow pages: "
f"{', '.join(skipped_regular) if skipped_regular else '(none)'}")
logger.debug(f"[preload] gap : 250 ms between loads, 700 ms initial delay")
logger.debug(f"[preload] ============================================================")
self._preload_timer.start(700)
def _preload_next(self):
while self._preload_queue and self._preload_queue[0] in self._instances:
skipped = self._preload_queue.pop(0)
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:
self._maybe_emit_preload_summary()
return
self._preload_dispatched += 1
idx = self._preload_dispatched
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:
self._dispatch_async_preload(name)
else:
@ -165,7 +165,7 @@ class Navigator(QObject):
def _dispatch_async_preload(self, 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.data_ready.connect(self._on_preload_data_ready)
worker.failed.connect(self._on_preload_failed)
@ -177,22 +177,22 @@ class Navigator(QObject):
def _on_preload_data_ready(self, name, payload):
self._preload_inflight -= 1
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:
try:
page = self._instantiate(name, prepared=payload)
self.register_instance(name, page)
self._preload_done += 1
print(f"[preload] {name:30s} OK (async, cached={len(self._instances)}, "
f"stack_size={self.page_stack.count()})", flush=True)
logger.debug(f"[preload] {name:30s} OK (async, cached={len(self._instances)}, "
f"stack_size={self.page_stack.count()})")
except Exception as e:
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()
def _on_preload_failed(self, name, error):
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:
ok = self._preload_one(name)
if ok:
@ -215,10 +215,10 @@ class Navigator(QObject):
def _emit_preload_summary(self):
total_s = time.perf_counter() - self._preload_t_start
print(f"[preload] ============================================================", flush=True)
print(f"[preload] complete: {self._preload_done} loaded, "
logger.debug(f"[preload] ============================================================")
logger.debug(f"[preload] complete: {self._preload_done} loaded, "
f"{self._preload_failed} failed, "
f"{self._preload_skipped_runtime} skipped (user beat us to it) "
f"in {total_s:.2f}s", flush=True)
print(f"[preload] cached pages now: {sorted(self._instances.keys())}", flush=True)
print(f"[preload] ============================================================", flush=True)
f"in {total_s:.2f}s")
logger.debug(f"[preload] cached pages now: {sorted(self._instances.keys())}")
logger.debug(f"[preload] ============================================================")

View file

@ -1,6 +1,6 @@
[project]
name = "sp-hydra-veil-gui"
version = "2.4.6"
version = "2.4.7"
authors = [
{ name = "Simplified Privacy" },
]
@ -12,7 +12,7 @@ classifiers = [
"Operating System :: POSIX :: Linux",
]
dependencies = [
"sp-hydra-veil-core == 2.3.8",
"sp-hydra-veil-core == 2.3.9",
"pyperclip ~= 1.9.0",
"pyqt6 ~= 6.7.1",
"qrcode[pil] ~= 8.2"