# -*- coding: utf-8 -*- """ Manages the logging output to the GUI's scrolled text widget and to a log file. """ import sys import queue import threading import traceback import tkinter as tk from tkinter import scrolledtext import os import datetime from typing import Optional, TYPE_CHECKING if TYPE_CHECKING: from pyinstallerguiwrapper.gui.main_window import PyInstallerGUI LOG_FILE_NAME_BASE = "pyinstaller_gui_wrapper" LOG_FILE_TIMESTAMP_FORMAT = "%Y%m%d_%H%M%S" LOG_DIRECTORY = "logs" class OutputLogger: def __init__(self, output_text_widget: scrolledtext.ScrolledText, build_queue: queue.Queue, parent_window: "PyInstallerGUI"): self.output_text_widget = output_text_widget self.build_queue = build_queue self.parent_window = parent_window self.log_file_path = self._initialize_log_file() if self.log_file_path: self._write_to_log_file(f"--- Log Session Started: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')} ---\n", include_timestamp=False) def _initialize_log_file(self) -> Optional[str]: try: if getattr(sys, 'frozen', False): base_path = os.path.dirname(sys.executable) else: base_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) log_dir_path = os.path.join(base_path, LOG_DIRECTORY) os.makedirs(log_dir_path, exist_ok=True) timestamp = datetime.datetime.now().strftime(LOG_FILE_TIMESTAMP_FORMAT) log_file_name = f"{LOG_FILE_NAME_BASE}_{timestamp}.log" full_log_path = os.path.join(log_dir_path, log_file_name) with open(full_log_path, 'a', encoding='utf-8') as f: pass print(f"[OutputLogger] Logging to file: {full_log_path}") return full_log_path except Exception as e: print(f"[CRITICAL OutputLogger ERROR] Failed to initialize log file: {e}\n{traceback.format_exc()}", file=sys.stderr) return None # --- MODIFICA QUI --- def _write_to_log_file(self, message: str, include_timestamp: bool = True) -> None: """Writes a message to the configured log file.""" if not self.log_file_path: return # Nonostante il flag, il timestamp è già nel messaggio formattato # che arriva da log_message. Il flag era per il messaggio di avvio. # La logica è semplice: scrivi il messaggio così com'è. try: with open(self.log_file_path, 'a', encoding='utf-8') as f: f.write(message) except Exception as e: print(f"[CRITICAL OutputLogger FILE WRITE ERROR] {e}\nMessage was: {message.strip()}", file=sys.stderr) # --- FINE MODIFICA --- def log_message(self, message: str, level: str = "INFO") -> None: """ Logs a message to the GUI's text area and to the log file. Thread-safe. """ formatted_message = f"[{level}] {str(message).strip()}\n" if threading.current_thread() is threading.main_thread(): self._update_output_log_widget(formatted_message) self._write_to_log_file(formatted_message) else: # Da un thread non principale, scriviamo subito sul file (append è thread-safe) # e mettiamo in coda solo per l'aggiornamento della GUI. self._write_to_log_file(formatted_message) self.build_queue.put(("LOG_STREAM", "LOG", formatted_message)) def _update_output_log_widget(self, message: str) -> None: """ Internal method to update the ScrolledText widget. Must be called from main thread. """ try: if not self.output_text_widget.winfo_exists(): return self.output_text_widget.config(state="normal") self.output_text_widget.insert(tk.END, message) self.output_text_widget.see(tk.END) self.output_text_widget.config(state="disabled") self.output_text_widget.update_idletasks() except Exception as e: critical_error_msg = f"[CRITICAL GUI LOG ERROR] Failed to write to GUI log: {e}\nMessage was: {message}{traceback.format_exc()}\n" print(critical_error_msg, file=sys.stderr) self._write_to_log_file(critical_error_msg) def check_build_queue(self) -> None: """ Periodically checks the shared build queue for ("LOG_STREAM", "LOG", data) messages. """ try: for _ in range(100): raw_item = self.build_queue.get_nowait() if isinstance(raw_item, tuple) and len(raw_item) == 3: source, msg_type, data = raw_item if source == "LOG_STREAM" and msg_type == "LOG": self._update_output_log_widget(str(data)) self.build_queue.task_done() else: self.build_queue.put(raw_item) break else: self.log_message(f"Discarding malformed queue item: {raw_item}", "WARNING") self.build_queue.task_done() except queue.Empty: pass except Exception as e: print(f"[CRITICAL OutputLogger.check_build_queue ERROR] {e}\n{traceback.format_exc()}", file=sys.stderr) self._write_to_log_file(f"[CRITICAL] Error in OutputLogger queue processing: {e}\n{traceback.format_exc()}\n") finally: if self.parent_window.winfo_exists(): self.parent_window.after(100, self.check_build_queue)