""" Linker launcher — avvia la finestra Linker come QMainWindow floating. Chiamato da MyICR Suite main_window.py. """ import json import logging import sqlite3 import threading from pathlib import Path from PyQt6.QtWidgets import QMessageBox _LINKER_MODULE_DIR = Path(__file__).resolve().parent _APP_ROOT = _LINKER_MODULE_DIR.parent.parent.parent # MyICR_Suite/ _DEFAULT_PARQUET_DIR = _APP_ROOT / 'local_db' / 'parquet' _DEFAULT_LINKER_DB = _APP_ROOT / 'sync_db' / 'linker.db' logger = logging.getLogger(__name__) _VACUUM_THRESHOLD = 0.20 # soglia freelist/total oltre cui eseguire VACUUM def _vacuum_if_needed(db_path: Path): """Apre una connessione dedicata e lancia VACUUM se freelist > soglia.""" try: con = sqlite3.connect(str(db_path)) freelist = con.execute('PRAGMA freelist_count').fetchone()[0] total = con.execute('PRAGMA page_count').fetchone()[0] if total > 0 and freelist / total > _VACUUM_THRESHOLD: logger.info(f"[Linker] VACUUM linker.db ({freelist}/{total} pagine libere, {freelist*100//total}%)") con.execute('VACUUM') logger.info("[Linker] VACUUM completato") con.close() except Exception as e: logger.warning(f"[Linker] VACUUM fallito (non critico): {e}") _linker_window = None # Mantiene il riferimento per evitare garbage collection def _load_config() -> dict: config_path = Path(__file__).parent / 'config.json' if not config_path.exists(): raise FileNotFoundError(f"config.json non trovato: {config_path}") with open(config_path, 'r', encoding='utf-8') as f: return json.load(f) def _l3_sync_bg(app_root: Path): """Rigenera i parquet L3 in background se necessario.""" try: from launcher_src.parquet_l3 import sync_parquet_l3 sync_parquet_l3(app_root) except Exception as e: logger.warning(f"[Linker] L3 sync fallito (non critico): {e}") def launch_linker_window(parent=None): """ Apre la finestra Linker. Se già aperta, la porta in primo piano. """ global _linker_window # Se già aperta, portala in primo piano if _linker_window is not None and _linker_window.isVisible(): _linker_window.raise_() _linker_window.activateWindow() return try: config = _load_config() from app.modules.linker.src.database.linker_db import LinkerDatabase from app.modules.linker.src.database.parquet_db import ParquetCatalog from app.modules.linker.src.gui.main_window import MainWindow parquet_dir = Path(config.get('parquet_path', str(_DEFAULT_PARQUET_DIR))) catalog = None if parquet_dir.exists(): try: catalog = ParquetCatalog(str(parquet_dir)) catalog._get_con() # validate eagerly logger.info(f"[Linker] ParquetCatalog pronto: {parquet_dir}") except Exception as cat_err: logger.warning(f"[Linker] ParquetCatalog non disponibile: {cat_err}") if catalog is None: QMessageBox.critical( parent, "Errore Linker", f"Impossibile inizializzare il catalogo Parquet:\n{parquet_dir}\n\n" "Verificare che i file Parquet siano presenti." ) return import os _dev = os.environ.get("MYICR_DEV_MODE") == "1" server_linker_db_path = config.get('linker_db_path', '') ott_parquet_server_path = config.get('ott_parquet_server_path', '') linker_db_path = _DEFAULT_LINKER_DB if _dev: logger.info("[Linker] DEV MODE") logger.info(f"[Linker] linker.db locale: {linker_db_path}") if not linker_db_path.exists(): QMessageBox.critical( parent, "Errore Linker", f"Database linker.db non trovato:\n{linker_db_path}" ) return try: sqlite_con = sqlite3.connect(str(linker_db_path), check_same_thread=False) logger.info(f"[Linker] linker.db aperto: {linker_db_path}") except Exception as e: QMessageBox.critical(parent, "Errore Linker", f"Impossibile aprire linker.db:\n{e}") return server_con = None if server_linker_db_path: server_p = Path(server_linker_db_path) if server_p.exists(): try: server_con = sqlite3.connect(str(server_p), check_same_thread=False) logger.info(f"[Linker] server linker.db aperto: {server_p}") except Exception as e: logger.warning(f"[Linker] server linker.db non apribile (scritture solo locali): {e}") else: logger.warning(f"[Linker] server linker.db non raggiungibile: {server_p}") threading.Thread(target=_vacuum_if_needed, args=(linker_db_path,), daemon=True).start() linker_db = LinkerDatabase(catalog, sqlite_con, server_con=server_con) linker_db.ensure_ott_ext() linker_db.ensure_valutazioni_extra_gemma() linker_db.ensure_product_annotations() catalog.register_ott_ext(sqlite_con) window = MainWindow( linker_db=linker_db, catalog=catalog, dev_mode=_dev, server_linker_db_path=server_linker_db_path, ott_parquet_server_path=ott_parquet_server_path, ) window.showMaximized() _linker_window = window logger.info("[Linker] Finestra aperta") from app.modules.linker.src.gui.dev_notes import show_dev_notes show_dev_notes(_dev, parent=window) # L3 aggregation in background — no-op se già aggiornati threading.Thread(target=_l3_sync_bg, args=(_APP_ROOT,), daemon=True).start() except Exception as e: logger.error(f"[Linker] Errore avvio: {e}", exc_info=True) QMessageBox.critical(parent, "Errore Linker", str(e))