# MyICR_Suite/app/modules/mediatrack/run.py import os import sys import subprocess import threading import time import socket from pathlib import Path # ================================================================= # Fix PYTHONPATH: MyICR_Suite/ per import app.* # ================================================================= # 1. Aggiungi mediatrack al PYTHONPATH per import relativi (backend, frontend, etc) mediatrack_path = os.path.dirname(os.path.abspath(__file__)) if mediatrack_path not in sys.path: sys.path.insert(0, mediatrack_path) # 2. Aggiungi MyICR_Suite/ per "from app.config_loader import ..." # run.py è in: MyICR_Suite/app/modules/mediatrack/run.py → risali 3 livelli myicr_suite_dir = Path(__file__).resolve().parent.parent.parent.parent if str(myicr_suite_dir) not in sys.path: sys.path.insert(0, str(myicr_suite_dir)) # ================================================================= import webview from loguru import logger try: from tkinter import Tk, filedialog TKINTER_AVAILABLE = True except ImportError: TKINTER_AVAILABLE = False print("WARNING: tkinter not available, using PyWebview dialog for directory selection") from backend.server import app def setup_logging(): import socket as _socket log_dir = myicr_suite_dir / "logs" log_dir.mkdir(exist_ok=True) log_format = "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}:{function}:{line} - {message}" logger.remove() logger.add( str(log_dir / "mediatrack.log"), rotation="10 MB", retention="7 days", format=log_format, level="INFO" ) # Telemetria centralizzata: scrivi log su server per diagnosi remota try: import os as _os server_log_dir = Path(r"I:\SOFTWARE\PYTHON_SRV\logs") server_log_dir.mkdir(parents=True, exist_ok=True) hostname = _socket.gethostname() username = _os.environ.get("USERNAME", "unknown") logger.add( str(server_log_dir / f"{hostname}-{username}.log"), rotation="5 MB", retention="30 days", format=log_format, level="INFO", enqueue=True, catch=True ) except Exception: pass # Offline o drive non raggiungibile — silenzioso # Import is_debug e get_version da config_loader (config.json) try: from app.config_loader import is_debug, get_version except ImportError: def is_debug(): return False def get_version(): return "" print("[MediaTrack] WARNING: config_loader non disponibile, debug disabilitato") # Import sync functions (pull/push DB server ↔ locale) _DEV_MODE = os.environ.get("MYICR_DEV_MODE") == "1" try: from launcher_src.shared_db_sync import pull_from_server, push_to_server, start_background_sync, stop_background_sync _SYNC_AVAILABLE = True except ImportError: _SYNC_AVAILABLE = False logger.warning("[MediaTrack] shared_db_sync non disponibile — sync disabilitato") """ Script principale per lanciare l'applicazione desktop ibrida MediaTrack. Funzionamento: 1. Importa l'istanza dell'applicazione Flask dal nostro modulo backend. 2. Definisce una funzione `run_server` che avvierà il server Flask. Flask di default gira su http://127.0.0.1:5000. 3. Avvia il server Flask in un "thread" separato. Questo è FONDAMENTALE perché impedisce al server di bloccare l'esecuzione della finestra dell'interfaccia. 4. Crea la finestra nativa utilizzando `pywebview`, puntandola all'URL del nostro server locale. 5. `webview.start()` avvia il loop degli eventi della UI, mostrando la finestra all'utente. """ class Api: """ Questa classe espone metodi che possono essere chiamati direttamente da JavaScript nel frontend. Questo è il "ponte" che permette al frontend di chiedere al backend di eseguire azioni native del sistema operativo. """ def open_file_dialog(self): """ Apre una finestra di dialogo nativa per la selezione di un file. Questo metodo viene chiamato da detail.js quando l'utente clicca "Sfoglia...". """ window = webview.windows[0] result = window.create_file_dialog(webview.OPEN_DIALOG) if result: return result[0] return None # ================================================================= # >>> NUOVO METODO per aprire un file esistente # ================================================================= def open_file(self, file_path): """ Apre un file utilizzando l'applicazione predefinita del sistema operativo. Se il path di rete non è raggiungibile, tenta la copia locale in local_db/attachments/. """ try: resolved = file_path if not os.path.exists(file_path): # Fallback: cerca nella cache locale allegati try: from app.config_loader import get_app_root p = Path(file_path) parts = p.parts[1:] # rimuove lettera drive local_copy = get_app_root() / 'local_db' / 'attachments' / Path(*parts) if local_copy.exists(): resolved = str(local_copy) else: print(f"File non trovato (rete e locale): {file_path}") return False except Exception: print(f"Errore: il file non esiste al percorso {file_path}") return False # Logica specifica per sistema operativo if sys.platform == "win32": os.startfile(resolved) elif sys.platform == "darwin": # macOS subprocess.call(["open", resolved]) else: # linux e altri subprocess.call(["xdg-open", resolved]) return True # Restituisce un feedback positivo a JavaScript except Exception as e: # Stampa l'errore nella console di Python per il debug print(f"Errore nell'aprire il file '{file_path}': {e}") return False # ================================================================= # ================================================================= # >>> METODO per selezionare directory (Gestione Sceneggiature) # ================================================================= def select_directory(self): """ Apre una finestra di dialogo per la selezione di una directory. Questo metodo viene chiamato da maintenance.js per la gestione sceneggiature. Usa tkinter (se disponibile) per mostrare la finestra classica di Windows Explorer con la barra laterale e la vista completa delle cartelle. Altrimenti usa il dialog nativo di PyWebview. Returns: str: Path della directory selezionata, o None se annullato """ try: # Prova a usare tkinter se disponibile (finestra Windows Explorer completa) if TKINTER_AVAILABLE: # Crea una root window di tkinter nascosta root = Tk() root.withdraw() # Nascondi la finestra principale root.wm_attributes('-topmost', 1) # Porta il dialog in primo piano # Apri il dialog di selezione cartella (stile Windows Explorer) selected_path = filedialog.askdirectory( parent=root, title='Seleziona Directory Sceneggiature', mustexist=True ) # Chiudi la root window root.destroy() if selected_path: print(f"Directory selezionata (tkinter): {selected_path}") return selected_path else: print("Selezione directory annullata") return None else: # Fallback a PyWebview se tkinter non è disponibile window = webview.windows[0] result = window.create_file_dialog(webview.FOLDER_DIALOG) if result and len(result) > 0: selected_path = result[0] print(f"Directory selezionata (PyWebview): {selected_path}") return selected_path print("Selezione directory annullata") return None except Exception as e: print(f"Errore nella selezione directory: {e}") return None # ================================================================= def wait_for_server(host='127.0.0.1', port=5000, timeout=10): """ Aspetta che il server Flask sia pronto prima di aprire pywebview. Questo risolve il problema della race condition dove pywebview si apre prima che Flask abbia fatto in tempo ad avviarsi. """ start_time = time.time() while time.time() - start_time < timeout: try: # Prova a connettersi alla porta Flask sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(1) result = sock.connect_ex((host, port)) sock.close() if result == 0: # Flask è pronto! print(f"[OK] Server Flask pronto su {host}:{port}") return True except Exception as e: pass # Aspetta un po' prima di riprovare time.sleep(0.1) # Timeout raggiunto print(f"[WARN] Timeout: Server Flask non risponde dopo {timeout} secondi") return False def run_server(): # Il server Flask viene avviato in base al flag debug unificato # 'daemon=True' permette al thread di chiudersi quando il programma principale esce. # use_reloader=False necessario per evitare conflitti con segnali in thread secondari _debug = is_debug() print(f"[MediaTrack] Flask server starting (debug={'ON' if _debug else 'OFF'})") app.run(host='127.0.0.1', port=5000, debug=_debug, use_reloader=False, threaded=True) if __name__ == '__main__': setup_logging() # URL di avvio: default dashboard, override via --start-url per modalità diretta import argparse parser = argparse.ArgumentParser() parser.add_argument('--start-url', default='http://127.0.0.1:5000', help='URL iniziale della finestra') args, _ = parser.parse_known_args() start_url = args.start_url logger.info("="*50) logger.info(f"Avvio MediaTrack standalone (Debug: {'ON' if is_debug() else 'OFF'})") # Sync DB: pull da server all'avvio, poi background push ogni 5 min # In DEV mode (MYICR_DEV_MODE=1) il sync è disabilitato — sandbox completa if _SYNC_AVAILABLE and not _DEV_MODE: pull_from_server() start_background_sync() # Avvia il server Flask in un thread demone print("Avvio server Flask...") server_thread = threading.Thread(target=run_server, daemon=True) server_thread.start() # IMPORTANTE: Aspetta che Flask sia pronto prima di aprire pywebview print("Attendo che il server Flask sia pronto...") if not wait_for_server(): print("ERRORE: Impossibile avviare il server Flask!") sys.exit(1) # Crea un'istanza della nostra classe Api api = Api() # Funzione per gestire la chiusura della finestra def on_closing(): """ Questa funzione viene chiamata quando l'utente clicca sulla X per chiudere la finestra. Restituisce True per permettere la chiusura, False per impedirla. """ window = webview.windows[0] result = window.create_confirmation_dialog( 'Conferma Uscita', 'Sei sicuro di voler uscire da MediaTrack?' ) if result and _SYNC_AVAILABLE and not _DEV_MODE: stop_background_sync() push_to_server() return result # Crea e mostra la finestra di pywebview _ver = get_version() _title = f"MediaTrack v{_ver}" if _ver else "MediaTrack" window = webview.create_window( _title, # Titolo della finestra start_url, # URL iniziale (dashboard o products-grid in modalità diretta) width=1280, height=800, resizable=True, min_size=(900, 600), maximized=True, # Avvia finestra massimizzata # 'js_api=api' rende l'oggetto 'api' e i suoi metodi # accessibili in JavaScript tramite l'oggetto `window.pywebview.api`. js_api=api ) # Registra l'evento di chiusura window.events.closing += on_closing print(f"[MediaTrack] Starting webview (Debug: {'ON' if is_debug() else 'OFF'})") webview.start(debug=is_debug())