fix double logging initialization, fix minor

This commit is contained in:
VALLONGOL 2025-06-03 09:20:17 +02:00
parent 4042146ba9
commit aed7be91a4
4 changed files with 732 additions and 625 deletions

View File

@ -6,10 +6,10 @@ import tkinter as tk
# HOW: Added import.
from .data.logging_config import LOGGING_CONFIG # Import the config dictionary
# MODIFIED: Import the logger setup function AND get_logger function
# WHY: Need to call the centralized setup function early and get logger instances.
# HOW: Added get_logger to the import statement.
from .utils.logger import setup_logging, get_logger
# MODIFIED: Import the new setup_basic_logging function AND get_logger function
# WHY: Need to call the centralized basic setup function early and get logger instances.
# HOW: Changed setup_logging to setup_basic_logging.
from .utils.logger import setup_basic_logging, get_logger # MODIFIED HERE
from .gui.main_window import MainWindow
from .controller.app_controller import AppController
@ -18,41 +18,31 @@ from .controller.app_controller import AppController
def main():
"""
Main function to launch the Flight Monitor application.
Initializes the Tkinter root, sets up logging, creates the controller
Initializes the Tkinter root, sets up basic logging, creates the controller
and the main window, and starts the event loop.
"""
# Initialize the Tkinter root window FIRST
root = tk.Tk()
# MODIFIED: Setup logging AFTER Tkinter root is created
# WHY: The TkinterTextHandler requires a root instance.
# Setup logging as early as possible after dependencies are met.
# HOW: Called setup_logging here, passing the necessary arguments including the config.
setup_logging(
gui_log_widget=None, root_tk_instance=root, logging_config_dict=LOGGING_CONFIG
# MODIFIED: Setup basic logging AFTER Tkinter root is created
# WHY: The Tkinter-based log processor needs a root instance to schedule itself.
# The TkinterTextHandler itself will be added later by MainWindow.
# HOW: Called setup_basic_logging here, passing the necessary arguments.
setup_basic_logging( # MODIFIED HERE
root_tk_instance_for_processor=root, # MODIFIED: Pass root for the log processor
logging_config_dict=LOGGING_CONFIG
)
# Now that basic logging is set up, we can use module_logger
# MODIFIED: Call get_logger directly to get the logger for this module.
# WHY: get_logger is a function, not a method of setup_logging.
# HOW: Changed setup_logging.get_logger to just get_logger.
module_logger_main = get_logger(
__name__
) # Get logger using the function from logger.py
# Now that basic logging is set up (console, file, queue), we can use module_logger
module_logger_main = get_logger(__name__)
module_logger_main.info("Application starting...")
# Create the application controller
app_controller = AppController()
# Create the main application window
# Pass the root and the controller to the MainWindow.
# MainWindow itself will now handle the creation and passing of its log widget
# to setup_logging (which we already called above, but will be called again by MW if it needs to).
# Ideally, MainWindow's __init__ should just receive the logger configuration somehow
# and pass its log widget to setup_logging if it exists.
# With the current design, MainWindow will call setup_logging *again* in its __init__,
# passing its log widget. This is fine; setup_logging is designed to handle re-initialization
# and replace handlers.
# MainWindow itself will now handle the creation and specific addition of its log widget
# to the logging system using a new dedicated function in the logger module.
main_app_window = MainWindow(root, app_controller)
# Set the main window instance in the controller
@ -63,11 +53,8 @@ def main():
root.mainloop()
module_logger_main.info("Tkinter main loop finished.")
# Cleanup should happen when the window is closed (WM_DELETE_WINDOW)
# or after mainloop exits (if it exits cleanly).
# The on_application_exit in the controller handles non-GUI cleanup.
# The shutdown_gui_logging is called by MainWindow's _on_closing.
# Cleanup is handled by MainWindow's _on_closing method, which in turn
# calls controller.on_application_exit and logger.shutdown_logging_system.
module_logger_main.info("Application finished.")

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@ import logging
import logging.handlers # For RotatingFileHandler
import tkinter as tk
from tkinter.scrolledtext import ScrolledText
from queue import Queue, Empty as QueueEmpty # Renamed for clarity
from queue import Queue, Empty as QueueEmpty # Renamed for clarity
from typing import Optional, Dict, Any
# --- Module-level globals for the new centralized logging queue system ---
@ -94,10 +94,10 @@ class TkinterTextHandler(logging.Handler):
# This emit is called by _process_global_log_queue (already in GUI thread)
if not self._is_active or not self.text_widget.winfo_exists():
# This case should ideally not happen if setup is correct
print(
f"DEBUG: TkinterTextHandler.emit called but inactive or widget gone. Record: {record.getMessage()}",
flush=True,
)
# print(
# f"DEBUG: TkinterTextHandler.emit called but inactive or widget gone. Record: {record.getMessage()}",
# flush=True,
# )
return
try:
msg = self.format(
@ -169,7 +169,7 @@ class TkinterTextHandler(logging.Handler):
self._internal_after_id = None
def close(self):
print("INFO: TkinterTextHandler close called.", flush=True)
# print("INFO: TkinterTextHandler close called.", flush=True) # Reduced verbosity
self._is_active = False
if (
self._internal_after_id
@ -203,11 +203,8 @@ class QueuePuttingHandler(logging.Handler):
def emit(self, record: logging.LogRecord):
try:
# We can choose to make a copy of the record if there's any concern
# about it being modified before processing, though typically not an issue.
# For now, put the original record.
self.handler_queue.put_nowait(record)
except queue.Full:
except queue.Full: # Standard library 'queue.Full'
print(
f"CRITICAL: Global log queue is full! Log record for '{record.name}' might be lost.",
flush=True,
@ -224,7 +221,7 @@ def _process_global_log_queue():
and dispatches them to the actual configured handlers.
"""
global _logging_system_active, _log_processor_after_id
# print(f"DEBUG: _process_global_log_queue called. Active: {_logging_system_active}", flush=True) # Can be very verbose
# print(f"DEBUG: _process_global_log_queue called. Active: {_logging_system_active}", flush=True)
if not _logging_system_active:
if (
@ -243,8 +240,7 @@ def _process_global_log_queue():
_tk_root_instance_for_processing
and _tk_root_instance_for_processing.winfo_exists()
):
# print("DEBUG: Global log queue processor: Tk root instance gone. Stopping.", flush=True)
_logging_system_active = False # Stop if root is gone
_logging_system_active = False
_log_processor_after_id = None
return
@ -252,12 +248,11 @@ def _process_global_log_queue():
try:
while _global_log_queue and not _global_log_queue.empty():
if not _logging_system_active:
break # Check before processing each item
break
try:
record = _global_log_queue.get_nowait()
processed_count += 1
# Dispatch to actual handlers
if _actual_console_handler:
try:
_actual_console_handler.handle(record)
@ -278,13 +273,12 @@ def _process_global_log_queue():
_global_log_queue.task_done()
except QueueEmpty:
break # Should not happen due to outer loop condition, but defensive
break
except Exception as e_proc_item:
print(
f"Error processing a log item from global queue: {e_proc_item}",
flush=True,
)
# Potentially re-queue or log to a failsafe if critical
except Exception as e_outer_loop:
print(
f"Critical error in _process_global_log_queue outer loop: {e_outer_loop}",
@ -295,107 +289,82 @@ def _process_global_log_queue():
_log_processor_after_id = _tk_root_instance_for_processing.after(
GLOBAL_LOG_QUEUE_POLL_INTERVAL_MS, _process_global_log_queue
)
# else: print(f"DEBUG: Not rescheduling _process_global_log_queue. Active: {_logging_system_active}", flush=True)
def setup_logging(
gui_log_widget: Optional[tk.Text] = None,
root_tk_instance: Optional[tk.Tk] = None,
def setup_basic_logging( # MODIFIED: Renamed from setup_logging
root_tk_instance_for_processor: Optional[tk.Tk], # MODIFIED: Now mandatory for processor
logging_config_dict: Optional[Dict[str, Any]] = None,
):
global _global_log_queue, _actual_console_handler, _actual_file_handler
global _actual_tkinter_handler, _logging_system_active, _tk_root_instance_for_processing
global _log_processor_after_id, _base_formatter
print("INFO: Configuring centralized queued logging system...", flush=True)
global _logging_system_active, _tk_root_instance_for_processing
global _log_processor_after_id, _base_formatter, _actual_tkinter_handler # _actual_tkinter_handler should be None here
# This function should only be called ONCE.
if _logging_system_active:
print(
"INFO: Logging system already active. Shutting down existing to reconfigure.",
"WARNING: setup_basic_logging called but logging system is already active. Ignoring call.",
flush=True,
)
shutdown_logging_system() # Ensure clean state before re-setup
return
print("INFO: Configuring basic centralized queued logging system...", flush=True)
_actual_tkinter_handler = None # Ensure it's None at basic setup
if logging_config_dict is None:
print(
"Warning: No logging_config_dict. Using basic console-only defaults for queued logging.",
flush=True,
)
# Simplified default, as Tkinter parts are handled by add_tkinter_handler
logging_config_dict = {
"default_root_level": logging.INFO,
"specific_levels": {},
"format": "%(asctime)s [%(levelname)-8s] %(name)-25s : %(message)s",
"date_format": "%Y-%m-%d %H:%M:%S",
"colors": {
logging.INFO: "black",
logging.DEBUG: "blue",
logging.WARNING: "orange",
logging.ERROR: "red",
logging.CRITICAL: "purple",
},
"queue_poll_interval_ms": 100, # For TkinterTextHandler's internal queue
"enable_console": True,
"enable_file": False,
}
# --- Extract Config ---
root_log_level = logging_config_dict.get("default_root_level", logging.INFO)
specific_levels = logging_config_dict.get("specific_levels", {})
log_format_str = logging_config_dict.get(
"format", "%(asctime)s [%(levelname)-8s] %(name)-25s : %(message)s"
)
log_date_format_str = logging_config_dict.get("date_format", "%Y-%m-%d %H:%M:%S")
level_colors = logging_config_dict.get("colors", {})
tkinter_handler_poll_ms = logging_config_dict.get(
"queue_poll_interval_ms", 100
) # For TkinterTextHandler's internal queue
enable_console = logging_config_dict.get("enable_console", True)
enable_file = logging_config_dict.get("enable_file", False)
_base_formatter = logging.Formatter(log_format_str, datefmt=log_date_format_str)
_global_log_queue = Queue() # Initialize the global queue for LogRecords
_tk_root_instance_for_processing = root_tk_instance # Store for the after() loop
_global_log_queue = Queue()
_tk_root_instance_for_processing = root_tk_instance_for_processor
# --- Configure Root Logger ---
root_logger = logging.getLogger()
# Remove ALL existing handlers from the root logger to ensure clean state
for handler in root_logger.handlers[:]:
for handler in root_logger.handlers[:]: # Clear any pre-existing handlers (e.g. from basicConfig)
try:
handler.close() # Close handler first
handler.close()
root_logger.removeHandler(handler)
print(
f"DEBUG: Removed and closed old handler from root: {handler}",
flush=True,
)
except Exception as e_rem:
print(f"Error removing/closing old handler {handler}: {e_rem}", flush=True)
root_logger.handlers = [] # Ensure it's empty
except Exception: pass
root_logger.handlers = []
root_logger.setLevel(root_log_level)
print(
f"INFO: Root logger level set to {logging.getLevelName(root_logger.level)}.",
flush=True,
)
# --- Configure Specific Logger Levels ---
for logger_name, level in specific_levels.items():
named_logger = logging.getLogger(logger_name)
named_logger.setLevel(level)
# Ensure propagation is on if they are not root, or if they should also go to root's QueuePuttingHandler
named_logger.propagate = True
print(
f"INFO: Logger '{logger_name}' level set to {logging.getLevelName(named_logger.level)}.",
flush=True,
)
# --- Create Actual Handlers (but don't add to root logger yet) ---
if enable_console:
try:
_actual_console_handler = logging.StreamHandler() # Defaults to stderr
_actual_console_handler = logging.StreamHandler()
_actual_console_handler.setFormatter(_base_formatter)
_actual_console_handler.setLevel(
logging.DEBUG
) # Let console handler decide based on record level
_actual_console_handler.setLevel(logging.DEBUG)
print("INFO: Actual ConsoleHandler created.", flush=True)
except Exception as e:
print(f"ERROR: Failed to create actual ConsoleHandler: {e}", flush=True)
@ -404,9 +373,7 @@ def setup_logging(
if enable_file:
try:
file_path = logging_config_dict.get("file_path", "flight_monitor_app.log")
file_max_bytes = logging_config_dict.get(
"file_max_bytes", 5 * 1024 * 1024
) # 5MB
file_max_bytes = logging_config_dict.get("file_max_bytes", 5 * 1024 * 1024)
file_backup_count = logging_config_dict.get("file_backup_count", 3)
_actual_file_handler = logging.handlers.RotatingFileHandler(
file_path,
@ -415,125 +382,103 @@ def setup_logging(
encoding="utf-8",
)
_actual_file_handler.setFormatter(_base_formatter)
_actual_file_handler.setLevel(logging.DEBUG) # Let file handler decide
print(
f"INFO: Actual RotatingFileHandler created for '{file_path}'.",
flush=True,
)
_actual_file_handler.setLevel(logging.DEBUG)
print(f"INFO: Actual RotatingFileHandler created for '{file_path}'.", flush=True)
except Exception as e:
print(
f"ERROR: Failed to create actual FileHandler for '{file_path}': {e}",
flush=True,
)
print(f"ERROR: Failed to create actual FileHandler for '{file_path}': {e}", flush=True)
_actual_file_handler = None
if gui_log_widget and root_tk_instance:
is_widget_valid = (
isinstance(gui_log_widget, (tk.Text, ScrolledText))
and hasattr(gui_log_widget, "winfo_exists")
and gui_log_widget.winfo_exists()
)
if is_widget_valid:
try:
_actual_tkinter_handler = TkinterTextHandler(
text_widget=gui_log_widget,
level_colors=level_colors,
root_tk_instance_for_widget_update=root_tk_instance, # Pass root for its own after loop
internal_poll_interval_ms=tkinter_handler_poll_ms,
)
_actual_tkinter_handler.setFormatter(_base_formatter)
_actual_tkinter_handler.setLevel(
logging.DEBUG
) # Let Tkinter handler decide
print("INFO: Actual TkinterTextHandler created.", flush=True)
except Exception as e:
print(
f"ERROR: Failed to create actual TkinterTextHandler: {e}",
flush=True,
)
_actual_tkinter_handler = None
else:
print(
"ERROR: GUI log widget invalid or non-existent, cannot create TkinterTextHandler.",
flush=True,
)
elif gui_log_widget and not root_tk_instance:
print(
"WARNING: GUI log widget provided, but root_tk_instance missing for TkinterTextHandler.",
flush=True,
)
# --- Add ONLY the QueuePuttingHandler to the root logger ---
if _global_log_queue is not None:
queue_putter = QueuePuttingHandler(handler_queue=_global_log_queue)
queue_putter.setLevel(
logging.DEBUG
) # Capture all messages from root level downwards
queue_putter.setLevel(logging.DEBUG)
root_logger.addHandler(queue_putter)
print(
f"INFO: QueuePuttingHandler added to root logger. All logs will go to global queue.",
flush=True,
)
print(f"INFO: QueuePuttingHandler added to root logger.", flush=True)
else:
print(
"CRITICAL ERROR: Global log queue not initialized. Logging will not function correctly.",
flush=True,
)
# Fallback to basic console if queue setup failed badly
if not _actual_console_handler: # If even this failed
_bf = logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
_ch = logging.StreamHandler()
_ch.setFormatter(_bf)
root_logger.addHandler(_ch)
print("CRITICAL: Added basic emergency console logger.", flush=True)
print("CRITICAL ERROR: Global log queue not initialized in basic setup!", flush=True)
# --- Start the global log queue processor (if root_tk_instance is available) ---
_logging_system_active = True
if (
_tk_root_instance_for_processing
and _tk_root_instance_for_processing.winfo_exists()
):
if (
_log_processor_after_id
): # Cancel previous if any (shouldn't happen if shutdown was called)
try:
_tk_root_instance_for_processing.after_cancel(_log_processor_after_id)
except Exception:
pass
if _log_processor_after_id: # Should be None here, but defensive
try: _tk_root_instance_for_processing.after_cancel(_log_processor_after_id)
except Exception: pass
_log_processor_after_id = _tk_root_instance_for_processing.after(
GLOBAL_LOG_QUEUE_POLL_INTERVAL_MS, _process_global_log_queue
)
print(
f"INFO: Global log queue processor scheduled with Tkinter.after (ID: {_log_processor_after_id}).",
flush=True,
)
elif root_tk_instance is None and (
gui_log_widget is not None or enable_file or enable_console
):
# This case means we want logging but have no Tkinter root to schedule the queue processor.
# This is problematic for GUI logging. For console/file, logs will just queue up.
# A dedicated thread could process the queue if no Tkinter available, but adds complexity.
print(
"WARNING: No Tkinter root instance provided to setup_logging. "
"Logs will be queued but not processed by GUI/File/Console handlers unless _process_global_log_queue is run manually or by another mechanism.",
flush=True,
)
elif not (enable_console or enable_file or gui_log_widget):
print(
"INFO: No log output handlers (console, file, gui) are enabled. Logs will be queued but not displayed.",
flush=True,
)
print(f"INFO: Global log queue processor scheduled (ID: {_log_processor_after_id}).", flush=True)
elif not _tk_root_instance_for_processing:
print("WARNING: No Tkinter root instance for processor. Logs will queue but not be dispatched by this setup function alone.", flush=True)
print("INFO: Centralized queued logging system setup complete.", flush=True)
# Test log
test_logger = logging.getLogger("FlightMonitor.LoggerTest")
test_logger.info(
"Logging system initialized. This is a test message from setup_logging."
print("INFO: Basic centralized queued logging system setup complete.", flush=True)
logging.getLogger("FlightMonitor.LoggerTest").info(
"Basic logging initialized. This is a test from setup_basic_logging."
)
def add_tkinter_handler( # NEW FUNCTION
gui_log_widget: tk.Text,
root_tk_instance_for_gui_handler: tk.Tk,
logging_config_dict: Dict[str, Any],
):
global _actual_tkinter_handler, _base_formatter
if not _logging_system_active:
print("ERROR: Cannot add Tkinter handler, basic logging system not active.", flush=True)
return
if not _base_formatter:
print("ERROR: Cannot add Tkinter handler, base formatter not set.", flush=True)
return
print("INFO: Attempting to add TkinterTextHandler...", flush=True)
if _actual_tkinter_handler: # If one already exists, close and prepare to replace
print("INFO: Existing TkinterTextHandler found, closing it before adding new one.", flush=True)
try:
_actual_tkinter_handler.close()
except Exception as e_close_old_tk:
print(f"Warning: Error closing old TkinterTextHandler: {e_close_old_tk}", flush=True)
_actual_tkinter_handler = None
is_widget_valid = (
isinstance(gui_log_widget, (tk.Text, ScrolledText))
and hasattr(gui_log_widget, "winfo_exists")
and gui_log_widget.winfo_exists()
)
is_root_valid = (
root_tk_instance_for_gui_handler
and hasattr(root_tk_instance_for_gui_handler, "winfo_exists")
and root_tk_instance_for_gui_handler.winfo_exists()
)
if is_widget_valid and is_root_valid:
try:
level_colors = logging_config_dict.get("colors", {})
tkinter_handler_poll_ms = logging_config_dict.get("queue_poll_interval_ms", 100)
_actual_tkinter_handler = TkinterTextHandler(
text_widget=gui_log_widget,
level_colors=level_colors,
root_tk_instance_for_widget_update=root_tk_instance_for_gui_handler,
internal_poll_interval_ms=tkinter_handler_poll_ms,
)
_actual_tkinter_handler.setFormatter(_base_formatter)
_actual_tkinter_handler.setLevel(logging.DEBUG)
print("INFO: TkinterTextHandler added and configured successfully.", flush=True)
logging.getLogger("FlightMonitor.LoggerTest").info(
"TkinterTextHandler added. This is a test from add_tkinter_handler."
)
except Exception as e:
print(f"ERROR: Failed to create and add TkinterTextHandler: {e}", flush=True)
_actual_tkinter_handler = None
else:
if not is_widget_valid:
print("ERROR: GUI log widget invalid or non-existent, cannot add TkinterTextHandler.", flush=True)
if not is_root_valid:
print("ERROR: Root Tk instance for GUI handler invalid or non-existent.", flush=True)
def get_logger(name: str) -> logging.Logger:
"""
Retrieves a logger instance by name.
@ -551,8 +496,12 @@ def shutdown_logging_system():
global _actual_console_handler, _actual_file_handler, _actual_tkinter_handler
global _global_log_queue, _tk_root_instance_for_processing
if not _logging_system_active and not _global_log_queue: # Extra check if it was never really active
print("INFO: Logging system shutdown called, but system was not fully active or already shut down.", flush=True)
return
print("INFO: Initiating shutdown of centralized logging system...", flush=True)
_logging_system_active = False # Signal processor to stop
_logging_system_active = False
if (
_log_processor_after_id
@ -561,89 +510,62 @@ def shutdown_logging_system():
):
try:
_tk_root_instance_for_processing.after_cancel(_log_processor_after_id)
print("INFO: Cancelled global log queue processor task.", flush=True)
# print("INFO: Cancelled global log queue processor task.", flush=True) # Reduced verbosity
except Exception as e_cancel:
print(
f"Warning: Error cancelling log processor task: {e_cancel}", flush=True
)
print(f"Warning: Error cancelling log processor task: {e_cancel}", flush=True)
_log_processor_after_id = None
# Attempt to process any remaining logs in the global queue
print(
"INFO: Processing any remaining logs in the global queue before full shutdown...",
flush=True,
)
# print("INFO: Processing any remaining logs in the global queue before full shutdown...", flush=True) # Reduced
if _global_log_queue:
final_processed_count = 0
while not _global_log_queue.empty():
try:
record = _global_log_queue.get_nowait()
final_processed_count += 1
if _actual_console_handler:
_actual_console_handler.handle(record)
if _actual_file_handler:
_actual_file_handler.handle(record)
if _actual_tkinter_handler:
_actual_tkinter_handler.handle(record)
if _actual_console_handler: _actual_console_handler.handle(record)
if _actual_file_handler: _actual_file_handler.handle(record)
if _actual_tkinter_handler: _actual_tkinter_handler.handle(record)
_global_log_queue.task_done()
except QueueEmpty:
break
except QueueEmpty: break
except Exception as e_final_proc:
print(f"Error during final log processing: {e_final_proc}", flush=True)
break # Stop if error during final processing
if final_processed_count > 0:
print(
f"INFO: Processed {final_processed_count} remaining log messages.",
flush=True,
)
else:
print(
"INFO: No remaining log messages in global queue to process.",
flush=True,
)
break
# if final_processed_count > 0: print(f"INFO: Processed {final_processed_count} remaining log messages.", flush=True)
# else: print("INFO: No remaining log messages in global queue to process.", flush=True)
# Close actual handlers
if _actual_tkinter_handler:
try:
print("INFO: Closing actual TkinterTextHandler.", flush=True)
# print("INFO: Closing actual TkinterTextHandler.", flush=True) # Reduced
_actual_tkinter_handler.close()
except Exception as e:
print(f"Error closing TkinterTextHandler: {e}", flush=True)
except Exception as e: print(f"Error closing TkinterTextHandler: {e}", flush=True)
_actual_tkinter_handler = None
if _actual_console_handler:
try:
print("INFO: Closing actual ConsoleHandler.", flush=True)
# print("INFO: Closing actual ConsoleHandler.", flush=True) # Reduced
_actual_console_handler.close()
except Exception as e:
print(f"Error closing ConsoleHandler: {e}", flush=True)
except Exception as e: print(f"Error closing ConsoleHandler: {e}", flush=True)
_actual_console_handler = None
if _actual_file_handler:
try:
print("INFO: Closing actual FileHandler.", flush=True)
# print("INFO: Closing actual FileHandler.", flush=True) # Reduced
_actual_file_handler.close()
except Exception as e:
print(f"Error closing FileHandler: {e}", flush=True)
except Exception as e: print(f"Error closing FileHandler: {e}", flush=True)
_actual_file_handler = None
# Clear the root logger's handlers (should only be the QueuePuttingHandler)
root_logger = logging.getLogger()
for handler in root_logger.handlers[:]:
try:
if isinstance(
handler, QueuePuttingHandler
): # Specifically target our handler
if isinstance(handler, QueuePuttingHandler):
handler.close()
root_logger.removeHandler(handler)
print(
f"INFO: Removed and closed QueuePuttingHandler: {handler}",
flush=True,
)
# print(f"INFO: Removed and closed QueuePuttingHandler: {handler}", flush=True) # Reduced
except Exception as e_rem_qph:
print(f"Error removing QueuePuttingHandler: {e_rem_qph}", flush=True)
_global_log_queue = None # Allow it to be garbage collected
_tk_root_instance_for_processing = None # Clear reference
_global_log_queue = None
_tk_root_instance_for_processing = None
_base_formatter = None
print("INFO: Centralized logging system shutdown complete.", flush=True)

118
todo.md
View File

@ -324,3 +324,121 @@ Considerando le aree di miglioramento ecco una roadmap di sviluppo. Ho ipotizzat
* **Design:** Mantieni un design modulare e flessibile per facilitare l'aggiunta di nuove funzionalità in futuro.
Dimmi cosa ne pensi di questa proposta e quali sono le tue priorità, così possiamo elaborare un piano di sviluppo più dettagliato.
Ottima analisi preliminare! Hai colto molto bene la struttura generale dell'applicazione e hai già individuato alcuni punti chiave.
Sono d'accordo con te, la priorità assoluta è risolvere il problema di inizializzazione in `FullFlightDetailsWindow`. Quel `self.tk IS MISSING` è decisamente un campanello d'allarme e probabilmente la causa del fallimento della chiamata a `self.protocol`.
Ecco un riassunto di ciò che ho compreso dalla tua analisi e alcuni pensieri iniziali prima di passare a un piano di sviluppo più dettagliato:
**Punti di Forza Rilevati:**
* **Struttura MVC (o tentativo di):** L'organizzazione in cartelle `gui`, `controller`, `data`, `utils`, `map` è un buon inizio per una separazione delle responsabilità.
* **Modello Canonico (`CanonicalFlightState`):** Fondamentale per disaccoppiare la logica di business dalle fonti di dati specifiche.
* **Configurazione Centralizzata (`config.py`, `logging_config.py`):** Facilita la gestione delle impostazioni globali.
* **Logging Accodato per la GUI (`logger.py`):** Ottima soluzione per evitare che il logging blocchi l'interfaccia utente.
* **Gestione Asincrona Dati Live:** L'uso di un thread per `OpenSkyLiveAdapter` e di code per la comunicazione con il controller è corretto per non bloccare la GUI durante il recupero dei dati.
* **Gestione Asincrona Rendering Mappa (`MapCanvasManager`):** Anche qui, l'uso di un thread worker per il rendering delle tile e degli overlay è una scelta eccellente per mantenere la GUI reattiva.
* **Gestione Cache Tile (`MapTileManager`):** Importante per le performance e per l'uso offline (anche se il download offline non è esplicitamente menzionato come requisito, la cache aiuta).
* **Modularità Componenti Mappa:** La separazione tra `MapCanvasManager`, `MapTileManager`, `MapService`, `map_drawing` e `map_utils` è buona.
* **Gestione Dipendenze Opzionali:** La presenza di flag come `PIL_IMAGE_LIB_AVAILABLE` indica una buona gestione delle librerie non strettamente essenziali per il funzionamento base.
**Aree Chiave da Indagare e Potenziali Incongruenze (come hai giustamente notato):**
1. **`FullFlightDetailsWindow.__init__`:**
* **Problema `super().__init__()` e `self.tk`:** Come discusso, questo è critico. `super().__init__(parent)` deve essere chiamato prima di qualsiasi operazione che si aspetti che `self` sia un widget Tkinter completamente formato. Questo include `self.title()`, `self.geometry()`, `self.protocol()`, e la creazione di qualsiasi widget figlio *direttamente* su `self` (anche se i widget figli sono generalmente messi su frame interni, il Toplevel stesso deve essere inizializzato).
* **Azione Immediata:** Correggere l'ordine delle chiamate in `__init__`.
2. **Coerenza e Centralizzazione delle Utility:**
* **Disegno Testo su Placeholder:** Funzioni come `_draw_text_on_placeholder` appaiono sia in `MapCanvasManager` che in `MapTileManager`. Potrebbero essere centralizzate in `map_drawing.py` o `map_utils.py`.
* **Costanti di Disegno Tracce:** Attualmente, `TRACK_HISTORY_POINTS` e `TRACK_LINE_WIDTH` vengono letti in `map_drawing.py` da `app_config` (tramite `map_constants`). Se queste devono essere configurabili dinamicamente dall'utente (es. tramite la GUI in `MainWindow` che ha `track_length_spinbox`), il valore aggiornato deve essere propagato correttamente a `MapCanvasManager` e quindi passato al thread worker che usa `map_drawing`. `MapCanvasManager.set_max_track_points` sembra fare questo per `max_track_points`, ma `TRACK_LINE_WIDTH` sembra più statico.
3. **Robustezza e Gestione Errori:**
* **Dipendenze Mancanti:** La gestione attuale con flag è buona, ma assicurarsi che l'utente riceva un feedback chiaro se una funzionalità chiave (come la mappa) è disabilitata.
* **Thread Safety:** L'uso di `_map_data_lock` in `MapCanvasManager` è corretto. Una rapida scorsa suggerisce che sia usato nei punti giusti, ma una revisione più approfondita è sempre utile quando si lavora con thread e dati condivisi.
4. **Interfaccia Utente e Esperienza Utente (UX):**
* **Feedback Validazione BBox:** Come hai suggerito, un feedback più immediato nella GUI per input BBox non validi sarebbe utile.
* **Completamento Funzionalità Placeholder:** Le sezioni "History" e "Live: Airport" sono chiaramente da sviluppare.
**Piano di Sviluppo Proposto (Alto Livello):**
Concordo con la tua analisi delle priorità. Ecco una possibile strutturazione del piano di sviluppo, che possiamo poi affinare insieme:
**FASE 1: Stabilizzazione e Correzioni Critiche (Priorità Massima)**
1. **Risolvere Inizializzazione `FullFlightDetailsWindow`:**
* **Obiettivo:** Assicurare che la finestra dei dettagli completi si apra senza errori e funzioni come previsto.
* **Azioni:**
* Rivedere `__init__` come da tua analisi: `super().__init__(parent)` deve precedere l'uso di metodi specifici del Toplevel.
* Testare approfonditamente l'apertura e la chiusura della finestra.
2. **Verifica Gestione Errori Critici:**
* **Obiettivo:** Assicurare che l'applicazione gestisca con grazia la mancanza di dipendenze chiave (Pillow, mercantile per la mappa) mostrando messaggi chiari e disabilitando le funzionalità impattate senza crashare.
* **Azioni:** Testare l'avvio dell'applicazione in un ambiente virtuale dove mancano queste librerie.
3. **Revisione Logging Iniziale:**
* **Obiettivo:** Assicurare che il logging sia informativo fin dalle prime fasi di avvio e che non ci siano errori nella sua configurazione.
* **Azioni:** Controllare i primi log emessi all'avvio.
**FASE 2: Miglioramenti Strutturali e Usabilità Base (Breve-Medio Termine)**
1. **Refactoring Componenti Mappa:**
* **Obiettivo:** Migliorare la manutenibilità e ridurre la duplicazione di codice.
* **Azioni:**
* Centralizzare le utility di disegno comuni (es. testo su placeholder, caricamento font) in `map_drawing.py` o `map_utils.py`.
* Rivedere come `TRACK_LINE_WIDTH` viene gestito; idealmente dovrebbe essere una configurazione passata a `MapCanvasManager` e poi al worker, simile a `max_track_points`.
2. **Migliorare Feedback Utente GUI:**
* **Obiettivo:** Rendere l'interfaccia più intuitiva.
* **Azioni:**
* Implementare validazione BBox in tempo reale (o al momento del focus-out) nei campi di input di `MainWindow`.
* Assicurare che i messaggi di stato e gli indicatori (semaforo) siano sempre chiari e riflettano lo stato corrente dell'applicazione.
3. **Consolidare la Logica di `MapCanvasManager`:**
* **Obiettivo:** Data la sua complessità, assicurarsi che la gestione dello stato (zoom, centro, BBox target) sia robusta e che le interazioni tra thread GUI e worker siano impeccabili.
* **Azioni:** Revisione specifica del flusso di richieste e risultati di rendering, specialmente in scenari di interazioni utente rapide (pan/zoom veloci).
**FASE 3: Implementazione Funzionalità Mancanti (Medio Termine)**
1. **Modalità "History":**
* **Obiettivo:** Permettere la visualizzazione e l'analisi dei dati di volo storici.
* **Azioni:**
* Definire l'interfaccia utente per la selezione della data/ora o del volo.
* Implementare la logica nel controller per interrogare `DataStorage`.
* Adattare `MapCanvasManager` per visualizzare tracce storiche (potrebbe richiedere modifiche per non aspettarsi dati "live").
* Considerare una funzionalità di "replay" della traccia.
2. **Vista Tabellare:**
* **Obiettivo:** Fornire una vista alternativa ai dati dei voli live.
* **Azioni:**
* Usare `ttk.Treeview` per mostrare i dati da `AppController._current_flights_to_display_gui` (o una sua copia gestita).
* Implementare ordinamento e selezione. La selezione dovrebbe aggiornare il pannello dei dettagli e potenzialmente centrare la mappa.
3. **Modalità "Live: Airport":**
* **Obiettivo:** Fornire una vista focalizzata sull'attività di un aeroporto specifico.
* **Azioni:**
* **Definizione:** Scegliere se basarsi su un raggio, su dati specifici di arrivi/partenze (richiederebbe nuove fonti dati/API).
* **Dati Aeroporti:** Valutare se integrare un database di aeroporti (es. da `pyflightdata` o file CSV).
* Implementare la logica di filtraggio e visualizzazione.
**FASE 4: Funzionalità Avanzate e Rifinitura (Lungo Termine)**
1. **Visualizzazione Immagini Aerei e Link Esterni (completamento):**
* In `FullFlightDetailsWindow`, implementare il caricamento effettivo delle immagini (es. tramite API di JetPhotos, FlightAware, ecc., o scraping se consentito) e rendere più robusta l'apertura dei link.
2. **Ricerca e Filtri Avanzati:**
* Funzionalità di ricerca voli per callsign, ICAO24, tipo di aereo.
* Filtri sulla mappa (es. per altitudine, velocità, tipo di aereo).
3. **Configurazioni Utente:**
* Possibilità di cambiare il map provider (se ne verranno aggiunti altri).
* Opzioni per la gestione della cache dei tile (pulizia, dimensione massima).
* Personalizzazione dell'aspetto delle tracce (colore, spessore - se `TRACK_LINE_WIDTH` diventa configurabile).
4. **Test Approfonditi:**
* Scrivere test unitari per le logiche di calcolo in `map_utils`, per i data adapter, e per la gestione del DB.
* Test di integrazione per i flussi principali.
5. **Documentazione e Packaging:**
* Migliorare docstring e commenti.
* Creare un manuale utente.
* Preparare per la distribuzione (es. PyInstaller, cx_Freeze).
Questo è un primo abbozzo. Sono pronto a discutere ogni punto e ad integrare le tue idee. In particolare, vorrei capire meglio quali sono le tue priorità e le funzionalità che ritieni più importanti da sviluppare o migliorare nel breve termine, dopo la stabilizzazione iniziale.
Cosa ne pensi? Quali sono le tue idee o le aree su cui vorresti concentrarci?