"""Runtime helpers for console-less Windows launches.""" from __future__ import annotations import sys import traceback from datetime import datetime from pathlib import Path import tempfile from typing import Callable, TypeVar from version_info import module_version __version__ = module_version(__name__) BASE_DIR = Path(__file__).resolve().parent FATAL_LOG = BASE_DIR / "warehouse_fatal.log" TEMP_DIR = Path(tempfile.gettempdir()) _STDIO_HANDLES = [] T = TypeVar("T") def _open_log_file(path: Path): """Open a log path, falling back to the user temp directory when needed.""" try: path.parent.mkdir(parents=True, exist_ok=True) return path.open("a", encoding="utf-8", buffering=1) except Exception: fallback = TEMP_DIR / path.name fallback.parent.mkdir(parents=True, exist_ok=True) return fallback.open("a", encoding="utf-8", buffering=1) def ensure_stdio(app_name: str) -> None: """Give ``pythonw`` a real stdout/stderr target before loggers are imported.""" stamp = datetime.now().strftime("%Y%m%d") if sys.stdout is None: handle = _open_log_file(BASE_DIR / f"{app_name}_stdout_{stamp}.log") _STDIO_HANDLES.append(handle) sys.stdout = handle if sys.stderr is None: handle = _open_log_file(BASE_DIR / f"{app_name}_stderr_{stamp}.log") _STDIO_HANDLES.append(handle) sys.stderr = handle def log_fatal(app_name: str, exc: BaseException) -> None: """Write one startup/runtime crash to a persistent log file.""" with _open_log_file(FATAL_LOG) as handle: handle.write("\n" + "=" * 80 + "\n") handle.write(f"{datetime.now():%Y-%m-%d %H:%M:%S} | {app_name}\n") handle.write("".join(traceback.format_exception(type(exc), exc, exc.__traceback__))) def log_runtime_event(app_name: str, message: str) -> None: """Write a lightweight launch/shutdown trace for console-less starts.""" safe_name = "".join(ch if ch.isalnum() or ch in ("_", "-") else "_" for ch in app_name.lower()) path = BASE_DIR / f"{safe_name}_launch.log" with _open_log_file(path) as handle: handle.write(f"{datetime.now():%Y-%m-%d %H:%M:%S} | {message}\n") def show_fatal_message(app_name: str, exc: BaseException) -> None: """Show a best-effort message box for console-less launches.""" try: import tkinter as tk from tkinter import messagebox root = tk.Tk() root.withdraw() messagebox.showerror( app_name, "Avvio non riuscito.\n\n" f"Controlla i log in:\n{BASE_DIR}\n\n" f"Se non ci sono, controlla anche:\n{TEMP_DIR}\n\n" f"{exc}", parent=root, ) root.destroy() except Exception: pass def run_with_fatal_log(app_name: str, func: Callable[[], T]) -> T: """Run an app entry point and persist otherwise invisible ``pythonw`` crashes.""" try: log_runtime_event( app_name, f"START exe={sys.executable!r} version={sys.version.split()[0]} cwd={Path.cwd()} base={BASE_DIR} argv={sys.argv!r}", ) result = func() log_runtime_event(app_name, f"RETURN result={result!r}") return result except BaseException as exc: log_runtime_event(app_name, f"EXCEPTION type={type(exc).__name__} value={exc!r}") log_fatal(app_name, exc) show_fatal_message(app_name, exc) raise