diff --git a/flightmonitor/controller/app_controller.py b/flightmonitor/controller/app_controller.py index ed7beb8..8371f8d 100644 --- a/flightmonitor/controller/app_controller.py +++ b/flightmonitor/controller/app_controller.py @@ -3,8 +3,8 @@ import threading import json import os import time -import sys # MODIFICATO: Aggiunto per controllo OS -import subprocess # MODIFICATO: Aggiunto per aprire cartelle +import sys +import subprocess from queue import Queue, Empty as QueueEmpty from typing import List, Optional, Dict, Any, TYPE_CHECKING from tkinter import messagebox, simpledialog @@ -44,19 +44,16 @@ class AppController: def __init__(self): self.main_window: Optional["MainWindow"] = None - # --- Live Monitoring Attributes --- self.live_adapter_thread: Optional[OpenSkyLiveAdapter] = None self.is_live_monitoring_active: bool = False self.live_data_queue: Optional[Queue[AdapterMessage]] = None self.live_data_processor: Optional[LiveDataProcessor] = None - # --- Historical Download Attributes --- self.historical_adapter_thread: Optional[OpenSkyHistoricalAdapter] = None self.is_historical_download_active: bool = False self.historical_data_queue: Optional[Queue[AdapterMessage]] = None self.historical_data_processor: Optional[HistoricalDataProcessor] = None - # --- Shared/General Attributes --- self._current_scan_params: Optional[Dict[str, Any]] = None self._active_bounding_box: Optional[Dict[str, float]] = None self.data_storage: Optional[DataStorage] = None @@ -172,10 +169,7 @@ class AppController: self.main_window.update_semaphore_and_status(initial_status_level, initial_status_msg) def start_live_monitoring(self): - if not self.main_window: - module_logger.error("Controller: Main window not set for live monitoring.") - return - + if not self.main_window: return bounding_box = self.main_window.get_bounding_box_from_gui() if not bounding_box: err_msg = "Invalid or missing Bounding Box. Define monitoring area." @@ -190,38 +184,29 @@ class AppController: if not log_dir: self.main_window.show_error_message("Logging Error", "Logging is enabled but no save directory is specified.") return - if self.raw_data_logger and not self.raw_data_logger.start_logging_session(log_dir, bounding_box): self.main_window.show_error_message("Logging Error", "Failed to start raw data logging session. Check logs.") return if hasattr(self.main_window.function_notebook_panel, "clear_all_panel_data"): self.main_window.function_notebook_panel.clear_all_panel_data() - if hasattr(self.main_window, "clear_all_views_data"): self.main_window.clear_all_views_data() - if hasattr(self.main_window, "set_monitoring_button_states"): self.main_window.set_monitoring_button_states(True) - if not self.data_storage and hasattr(self.main_window, "update_semaphore_and_status"): - self.main_window.update_semaphore_and_status(GUI_STATUS_WARNING, "DataStorage N/A. History will not be saved.") - if self.is_live_monitoring_active: module_logger.warning("Live monitoring requested but already active.") return module_logger.info(f"Controller: Starting live monitoring for bbox: {bounding_box}") self._active_bounding_box = bounding_box - if self.main_window.map_manager_instance: self.main_window.map_manager_instance.set_target_bbox(bounding_box) - if self.live_data_queue: while not self.live_data_queue.empty(): try: self.live_data_queue.get_nowait() except QueueEmpty: break - if not self.live_data_processor: module_logger.critical("Controller: LiveDataProcessor not initialized.") if hasattr(self.main_window, "_reset_gui_to_stopped_state"): @@ -229,9 +214,7 @@ class AppController: return self.live_adapter_thread = OpenSkyLiveAdapter( - output_queue=self.live_data_queue, - bounding_box=self._active_bounding_box, - polling_interval=app_config.LIVE_POLLING_INTERVAL_SECONDS, + self.live_data_queue, self._active_bounding_box, app_config.LIVE_POLLING_INTERVAL_SECONDS ) self.is_live_monitoring_active = True self.live_adapter_thread.start() @@ -246,19 +229,17 @@ class AppController: module_logger.info(f"Controller: Stopping live monitoring (from_error={from_error}).") self.is_live_monitoring_active = False - if self.live_data_processor: - self.live_data_processor.stop_processing_queue() - + if self.live_data_processor: self.live_data_processor.stop_processing_queue() if self.live_adapter_thread and self.live_adapter_thread.is_alive(): self.live_adapter_thread.stop() - self.live_adapter_thread.join(timeout=ADAPTER_JOIN_TIMEOUT_SECONDS) + self.live_adapter_thread.join(ADAPTER_JOIN_TIMEOUT_SECONDS) self.live_adapter_thread = None if self.raw_data_logger and self.raw_data_logger.is_active: self.raw_data_logger.stop_logging_session() if self.main_window and hasattr(self.main_window, "_reset_gui_to_stopped_state"): - msg = "Monitoring stopped due to an error." if from_error else "Monitoring stopped. Last view retained." + msg = "Monitoring stopped due to an error." if from_error else "Monitoring stopped." self.main_window._reset_gui_to_stopped_state(msg) def process_raw_data_logging(self, raw_json_string: str, canonical_data: List['CanonicalFlightState']): @@ -275,26 +256,20 @@ class AppController: panel.add_summary_entry(timestamp, aircraft_count) panel.update_last_query_result(timestamp, aircraft_count) - # MODIFICATO: Aggiunto nuovo metodo per aprire la cartella def open_log_directory(self, directory_path: str): - """Opens the specified directory in the system's file explorer.""" if not directory_path: self.main_window.show_error_message("Error", "No directory path specified.") return abs_path = os.path.abspath(directory_path) - if not os.path.isdir(abs_path): self.main_window.show_error_message("Error", f"Directory does not exist:\n{abs_path}") return try: - if sys.platform == "win32": - os.startfile(abs_path) - elif sys.platform == "darwin": # macOS - subprocess.Popen(["open", abs_path]) - else: # Linux and other UNIX-like - subprocess.Popen(["xdg-open", abs_path]) + if sys.platform == "win32": os.startfile(abs_path) + elif sys.platform == "darwin": subprocess.Popen(["open", abs_path]) + else: subprocess.Popen(["xdg-open", abs_path]) module_logger.info(f"Opening log directory: {abs_path}") except Exception as e: module_logger.error(f"Failed to open directory '{abs_path}': {e}") @@ -316,20 +291,31 @@ class AppController: params = historical_panel.get_download_parameters() if not params: - self.main_window.show_error_message("Input Error", "Invalid download parameters. Please check dates, times, and numeric values.") + self.main_window.show_error_message("Input Error", "Invalid download parameters.") return bbox = self.main_window.get_bounding_box_from_gui() if not bbox: - self.main_window.show_error_message("Input Error", "A valid Bounding Box must be defined in the main panel.") + self.main_window.show_error_message("Input Error", "A valid Bounding Box must be defined.") return + # MODIFICATO: Aggiunta logica di avvio del logging + logging_settings = self.main_window.function_notebook_panel.get_logging_panel_settings() + if logging_settings and logging_settings['enabled']: + log_dir = logging_settings.get('directory') + if not log_dir: + self.main_window.show_error_message("Logging Error", "Logging is enabled but no save directory is specified.") + return + if self.raw_data_logger and not self.raw_data_logger.start_logging_session(log_dir, bbox): + self.main_window.show_error_message("Logging Error", "Failed to start raw data logging session. Check logs.") + return + if self.aircraft_db_manager: overlapping_scans = self.aircraft_db_manager.find_overlapping_scans(params['start_time'], params['end_time'], bbox) - if overlapping_scans: - if not messagebox.askyesno("Confirm Download", "A similar time/area range has been scanned before. Download again? (Existing data points will not be duplicated)."): - module_logger.info("Historical download cancelled by user due to existing scan.") - return + if overlapping_scans and not messagebox.askyesno("Confirm Download", "A similar scan exists. Download again?"): + module_logger.info("Historical download cancelled by user.") + if self.raw_data_logger and self.raw_data_logger.is_active: self.raw_data_logger.stop_logging_session() + return module_logger.info(f"Controller: Starting historical download with params: {params} for bbox: {bbox}") self.is_historical_download_active = True @@ -337,18 +323,15 @@ class AppController: self._active_bounding_box = bbox historical_panel.set_controls_state(is_downloading=True) + if hasattr(self.main_window.function_notebook_panel, "clear_all_panel_data"): + self.main_window.function_notebook_panel.clear_all_panel_data() self.main_window.clear_all_views_data() self.historical_adapter_thread = OpenSkyHistoricalAdapter( - output_queue=self.historical_data_queue, - bounding_box=bbox, - start_timestamp=params['start_time'], - end_timestamp=params['end_time'], - sampling_interval_sec=params['sampling_interval_sec'], - scan_rate_sec=params['scan_rate_sec'] + self.historical_data_queue, bbox, params['start_time'], params['end_time'], + params['sampling_interval_sec'], params['scan_rate_sec'] ) self.historical_adapter_thread.start() - self.historical_data_processor.start_processing() self.main_window.update_semaphore_and_status(GUI_STATUS_FETCHING, "Historical download started.") @@ -360,84 +343,58 @@ class AppController: module_logger.info(f"Controller: Stopping historical download (from_error={from_error}, finished_normally={finished_normally}).") if self.aircraft_db_manager and self._current_scan_params and self._active_bounding_box: - status = "unknown" - if finished_normally: status = "completed" - elif from_error: status = "failed" - else: status = "aborted" - - self.aircraft_db_manager.add_scan_history( - self._current_scan_params, self._active_bounding_box, status - ) + status = "completed" if finished_normally else ("failed" if from_error else "aborted") + self.aircraft_db_manager.add_scan_history(self._current_scan_params, self._active_bounding_box, status) self.is_historical_download_active = False self._current_scan_params = None - if self.historical_data_processor: - self.historical_data_processor.stop_processing() - + if self.historical_data_processor: self.historical_data_processor.stop_processing() if self.historical_adapter_thread and self.historical_adapter_thread.is_alive(): if hasattr(self.historical_adapter_thread, 'stop'): - self.historical_adapter_thread.stop() - self.historical_adapter_thread.join(timeout=ADAPTER_JOIN_TIMEOUT_SECONDS) + self.historical_adapter_thread.stop(); self.historical_adapter_thread.join(ADAPTER_JOIN_TIMEOUT_SECONDS) self.historical_adapter_thread = None + # MODIFICATO: Aggiunta chiusura del logger + if self.raw_data_logger and self.raw_data_logger.is_active: + self.raw_data_logger.stop_logging_session() + if self.main_window and hasattr(self.main_window.function_notebook_panel, 'historical_panel'): historical_panel = self.main_window.function_notebook_panel.historical_panel - if historical_panel: - historical_panel.set_controls_state(is_downloading=False) + if historical_panel: historical_panel.set_controls_state(is_downloading=False) - status_msg = "Historical download finished." if finished_normally else "Historical download stopped." - if from_error: status_msg = "Historical download stopped due to an error." - self.main_window.update_semaphore_and_status(GUI_STATUS_OK if finished_normally else GUI_STATUS_WARNING, status_msg) + status_msg = "Historical download finished." if finished_normally else ("stopped due to an error." if from_error else "stopped.") + self.main_window.update_semaphore_and_status(GUI_STATUS_OK if finished_normally else GUI_STATUS_WARNING, f"Historical download {status_msg}") def on_application_exit(self): self._save_app_settings() - - if self.is_historical_download_active: - self.stop_historical_download() - + if self.is_live_monitoring_active: self.stop_live_monitoring() + if self.is_historical_download_active: self.stop_historical_download() if self.raw_data_logger and self.raw_data_logger.is_active: self.raw_data_logger.stop_logging_session() - - if self.cleanup_manager: - self.cleanup_manager.on_application_exit() - else: - module_logger.critical("CleanupManager not initialized. Cannot perform proper cleanup.") + if self.cleanup_manager: self.cleanup_manager.on_application_exit() + else: module_logger.critical("CleanupManager not initialized.") def on_function_tab_changed(self, tab_text: str): module_logger.info(f"Controller notified of function tab change to: {tab_text}") - - if self.is_live_monitoring_active: - self.stop_live_monitoring() - if self.is_historical_download_active: - self.stop_historical_download() + if self.is_live_monitoring_active: self.stop_live_monitoring() + if self.is_historical_download_active: self.stop_historical_download() if self.main_window: self.main_window.clear_all_views_data() if hasattr(self.main_window.function_notebook_panel, "clear_all_panel_data"): self.main_window.function_notebook_panel.clear_all_panel_data() - placeholder = "Select a mode to begin." - if "Live Monitor" in tab_text: - placeholder = "Define area and press Start Live Monitoring." - elif "Historical Download" in tab_text: - placeholder = "Set parameters and press Start Download." - elif "Playback" in tab_text: - placeholder = "Playback - Coming Soon" - - if hasattr(self.main_window, "_update_map_placeholder"): - self.main_window._update_map_placeholder(placeholder) + placeholders = {"Live Monitor": "Define area and press Start Live Monitoring.", "Historical Download": "Set parameters and press Start Download.", "Playback": "Playback - Coming Soon"} + placeholder = placeholders.get(tab_text, "Select a mode to begin.") + if hasattr(self.main_window, "_update_map_placeholder"): self.main_window._update_map_placeholder(placeholder) - def import_aircraft_database_from_file_with_progress( - self, csv_filepath: str, progress_dialog_ref: "ImportProgressDialog" - ): + def import_aircraft_database_from_file_with_progress(self, csv_filepath: str, progress_dialog_ref: "ImportProgressDialog"): if not self.aircraft_db_importer: - module_logger.error("AircraftDBImporter not initialized. Cannot import.") + module_logger.error("AircraftDBImporter not initialized.") if progress_dialog_ref and progress_dialog_ref.winfo_exists(): if self.main_window and self.main_window.root.winfo_exists(): - self.main_window.root.after(0, lambda: progress_dialog_ref.import_finished( - False, "Error: Import function not initialized correctly." - )) + self.main_window.root.after(0, lambda: progress_dialog_ref.import_finished(False, "Error: Import function not initialized.")) return self.aircraft_db_importer.import_aircraft_database_with_progress(csv_filepath, progress_dialog_ref) @@ -447,11 +404,7 @@ class AppController: from datetime import datetime, timezone, timedelta max_age_hours = getattr(app_config, "TRACK_MAX_AGE_HOURS_FOR_CONTINUITY", 4) current_utc_dt = datetime.now(timezone.utc) - since_dt = current_utc_dt - timedelta(hours=max_age_hours) - since_timestamp = since_dt.timestamp() - track_states = self.data_storage.get_flight_track_for_icao_on_date( - icao24, current_utc_dt, since_timestamp=since_timestamp - ) + track_states = self.data_storage.get_flight_track_for_icao_on_date(icao24, current_utc_dt) if track_states: return [state.to_dict() for state in track_states] except Exception as e: module_logger.error(f"Controller: Error retrieving historical track for {icao24}: {e}", exc_info=True) @@ -468,9 +421,7 @@ class AppController: map_mgr = self.main_window.map_manager_instance with map_mgr._map_data_lock: for state in map_mgr._current_flights_to_display_gui: - if state.icao24 == normalized_icao24: - combined_details.update(state.to_dict()) - break + if state.icao24 == normalized_icao24: combined_details.update(state.to_dict()); break if self.aircraft_db_manager: static_data = self.aircraft_db_manager.get_aircraft_details(normalized_icao24) if static_data: @@ -481,13 +432,10 @@ class AppController: def request_and_show_full_flight_details(self, icao24: str): normalized_icao24 = icao24.lower().strip() - module_logger.info(f"Controller: Requesting to show full details for ICAO24: {normalized_icao24}") - if not (self.main_window and self.main_window.root and self.main_window.root.winfo_exists()): - module_logger.error("Controller: MainWindow not available to show full flight details.") - return + module_logger.info(f"Controller: Requesting full details for ICAO24: {normalized_icao24}") + if not (self.main_window and self.main_window.root and self.main_window.root.winfo_exists()): return if not normalized_icao24: - module_logger.warning("Controller: Empty ICAO24 for full details request.") - if hasattr(self.main_window, "show_info_message"): self.main_window.show_info_message("Flight Details", "No ICAO24 provided for full details.") + if hasattr(self.main_window, "show_info_message"): self.main_window.show_info_message("Flight Details", "No ICAO24 provided.") return if self.active_detail_window_ref and self.active_detail_window_ref.winfo_exists(): if self.active_detail_window_icao == normalized_icao24: @@ -495,52 +443,42 @@ class AppController: else: try: self.active_detail_window_ref.destroy() except tk.TclError: pass - static_data: Optional[Dict[str, Any]] = self.aircraft_db_manager.get_aircraft_details(normalized_icao24) if self.aircraft_db_manager else None - live_data: Optional[Dict[str, Any]] = None + static_data, live_data, full_track_data_list = None, None, [] + if self.aircraft_db_manager: static_data = self.aircraft_db_manager.get_aircraft_details(normalized_icao24) if self.main_window and self.main_window.map_manager_instance: map_mgr = self.main_window.map_manager_instance with map_mgr._map_data_lock: for state in map_mgr._current_flights_to_display_gui: - if state.icao24 == normalized_icao24: - live_data = state.to_dict(); break - full_track_data_list: List[Dict[str, Any]] = [] + if state.icao24 == normalized_icao24: live_data = state.to_dict(); break if self.data_storage: try: from datetime import datetime, timezone - current_utc_date = datetime.now(timezone.utc) - track_states = self.data_storage.get_flight_track_for_icao_on_date(normalized_icao24, current_utc_date) + track_states = self.data_storage.get_flight_track_for_icao_on_date(normalized_icao24, datetime.now(timezone.utc)) if track_states: full_track_data_list = [state.to_dict() for state in track_states] - except Exception as e_track: - module_logger.error(f"FullDetails: Error retrieving historical track for {normalized_icao24}: {e_track}", exc_info=True) + except Exception as e_track: module_logger.error(f"Error retrieving track for {normalized_icao24}: {e_track}", exc_info=True) try: from ..gui.dialogs.full_flight_details_window import FullFlightDetailsWindow details_win = FullFlightDetailsWindow(self.main_window.root, normalized_icao24, self) - self.active_detail_window_ref = details_win - self.active_detail_window_icao = normalized_icao24 + self.active_detail_window_ref, self.active_detail_window_icao = details_win, normalized_icao24 if self.main_window: self.main_window.full_flight_details_window = details_win details_win.update_details(static_data, live_data, full_track_data_list) - except ImportError: - module_logger.error("FullFlightDetailsWindow class not found.") - if hasattr(self.main_window, "show_error_message"): self.main_window.show_error_message("UI Error", "Could not open full details window (import error).") - except Exception as e_show_details: - module_logger.error(f"Error showing full flight details window for {normalized_icao24}: {e_show_details}", exc_info=True) - if hasattr(self.main_window, "show_error_message"): self.main_window.show_error_message("Error", f"Could not display full details: {e_show_details}") - self.active_detail_window_ref = None - self.active_detail_window_icao = None + except Exception as e_show: + module_logger.error(f"Error showing full details for {normalized_icao24}: {e_show}", exc_info=True) + if hasattr(self.main_window, "show_error_message"): self.main_window.show_error_message("Error", f"Could not display full details: {e_show}") + self.active_detail_window_ref, self.active_detail_window_icao = None, None if self.main_window: self.main_window.full_flight_details_window = None def details_window_closed(self, closed_icao24: str): if self.cleanup_manager: self.cleanup_manager.details_window_closed(closed_icao24) - else: module_logger.warning("CleanupManager not initialized for details_window_closed.") - def on_map_left_click(self, latitude: Optional[float], longitude: Optional[float], canvas_x: int, canvas_y: int, screen_x: int, screen_y: int): - if self.map_command_handler: self.map_command_handler.on_map_left_click(latitude, longitude, canvas_x, canvas_y, screen_x, screen_y) - def on_map_right_click(self, latitude: float, longitude: float, screen_x: int, screen_y: int): - if self.map_command_handler: self.map_command_handler.on_map_right_click(latitude, longitude, screen_x, screen_y) - def recenter_map_at_coords(self, lat: float, lon: float): + def on_map_left_click(self, lat, lon, cx, cy, sx, sy): + if self.map_command_handler: self.map_command_handler.on_map_left_click(lat, lon, cx, cy, sx, sy) + def on_map_right_click(self, lat, lon, sx, sy): + if self.map_command_handler: self.map_command_handler.on_map_right_click(lat, lon, sx, sy) + def recenter_map_at_coords(self, lat, lon): if self.map_command_handler: self.map_command_handler.recenter_map_at_coords(lat, lon) - def set_bbox_around_coords(self, center_lat: float, center_lon: float, area_size_km: float): - if self.map_command_handler: self.map_command_handler.set_bbox_around_coords(center_lat, center_lon, area_size_km) - def update_bbox_gui_fields(self, bbox_dict: Dict[str, float]): + def set_bbox_around_coords(self, lat, lon, size_km): + if self.map_command_handler: self.map_command_handler.set_bbox_around_coords(lat, lon, size_km) + def update_bbox_gui_fields(self, bbox_dict): if self.main_window: self.main_window.update_bbox_gui_fields(bbox_dict) def update_general_map_info(self): if self.map_command_handler: self.map_command_handler.update_general_map_info() @@ -548,13 +486,12 @@ class AppController: if self.map_command_handler: self.map_command_handler.map_zoom_in() def map_zoom_out(self): if self.map_command_handler: self.map_command_handler.map_zoom_out() - def map_pan_direction(self, direction: str): + def map_pan_direction(self, direction): if self.map_command_handler: self.map_command_handler.map_pan_direction(direction) - def map_center_on_coords_and_fit_patch(self, lat: float, lon: float, patch_size_km: float): - if self.map_command_handler: self.map_command_handler.map_center_on_coords_and_fit_patch(lat, lon, patch_size_km) - def set_map_track_length(self, length: int): + def map_center_on_coords_and_fit_patch(self, lat, lon, patch_km): + if self.map_command_handler: self.map_command_handler.map_center_on_coords_and_fit_patch(lat, lon, patch_km) + def set_map_track_length(self, length): if self.main_window and self.main_window.map_manager_instance: self.main_window.map_manager_instance.set_max_track_points(length) - else: module_logger.warning("Controller: Cannot set track length, map manager not available.") def get_profile_names(self) -> List[str]: if self.profile_manager: return self.profile_manager.get_profile_names() return [] diff --git a/flightmonitor/controller/historical_data_processor.py b/flightmonitor/controller/historical_data_processor.py index c14d891..fa1d219 100644 --- a/flightmonitor/controller/historical_data_processor.py +++ b/flightmonitor/controller/historical_data_processor.py @@ -5,9 +5,11 @@ It updates the GUI's virtual clock and dispatches flight states to the map. """ from queue import Queue, Empty as QueueEmpty from typing import Optional, TYPE_CHECKING +import time # MODIFICATO: Aggiunto per ottenere il timestamp corrente from flightmonitor.utils.logger import get_logger from flightmonitor.data.opensky_live_adapter import AdapterMessage, MSG_TYPE_FLIGHT_DATA, MSG_TYPE_ADAPTER_STATUS, STATUS_STOPPED +from flightmonitor.data.common_models import CanonicalFlightState # MODIFICATO: Importato per il type hinting if TYPE_CHECKING: from flightmonitor.controller.app_controller import AppController @@ -90,41 +92,47 @@ class HistoricalDataProcessor: if message_type == MSG_TYPE_FLIGHT_DATA: snapshot_timestamp = message.get("timestamp") - flight_states = message.get("payload", []) + + # MODIFICATO: Estrae il payload strutturato + payload_dict = message.get("payload", {}) + flight_states: Optional[list[CanonicalFlightState]] = payload_dict.get("canonical") + raw_json_payload: str = payload_dict.get("raw_json", "{}") # Aggiorna sempre l'orologio virtuale per mostrare il progresso if snapshot_timestamp and hasattr(main_window, "function_notebook_panel") and main_window.function_notebook_panel.historical_panel: main_window.function_notebook_panel.historical_panel.update_virtual_clock(snapshot_timestamp) - # --- INIZIO MODIFICA --- - # Controlla se sono stati trovati aerei in questo snapshot - if flight_states: - # Se ci sono dati, salvali e aggiorna la mappa - if data_storage: - for state in flight_states: - flight_id = data_storage.add_or_update_flight_daily( - icao24=state.icao24, callsign=state.callsign, - origin_country=state.origin_country, detection_timestamp=state.timestamp + if flight_states is not None: + # MODIFICATO: Notifica il controller per il logging dei dati grezzi e l'aggiornamento della UI + if hasattr(self.app_controller, 'process_raw_data_logging'): + self.app_controller.process_raw_data_logging(raw_json_payload, flight_states) + if hasattr(self.app_controller, 'update_live_summary_table'): + self.app_controller.update_live_summary_table(snapshot_timestamp, len(flight_states)) + + if flight_states: # Se ci sono dati, salvali e aggiorna la mappa + if data_storage: + for state in flight_states: + flight_id = data_storage.add_or_update_flight_daily( + icao24=state.icao24, callsign=state.callsign, + origin_country=state.origin_country, detection_timestamp=state.timestamp + ) + if flight_id: + data_storage.add_position_daily(flight_id, state) + + if main_window.map_manager_instance: + main_window.map_manager_instance.update_flights_on_map(flight_states) + else: # Se non ci sono dati, aggiorna solo il messaggio di stato per dare feedback + if main_window and hasattr(main_window, 'update_semaphore_and_status'): + from ..utils.gui_utils import GUI_STATUS_FETCHING + from datetime import datetime + + dt_object = datetime.fromtimestamp(snapshot_timestamp) + time_str = dt_object.strftime("%H:%M:%S") + + main_window.update_semaphore_and_status( + GUI_STATUS_FETCHING, + f"No flights found at {time_str}. Continuing scan..." ) - if flight_id: - data_storage.add_position_daily(flight_id, state) - - if main_window.map_manager_instance: - main_window.map_manager_instance.update_flights_on_map(flight_states) - else: - # Se non ci sono dati, aggiorna solo il messaggio di stato per dare feedback - if main_window and hasattr(main_window, 'update_semaphore_and_status'): - from ..utils.gui_utils import GUI_STATUS_FETCHING - from datetime import datetime - - dt_object = datetime.fromtimestamp(snapshot_timestamp) - time_str = dt_object.strftime("%H:%M:%S") - - main_window.update_semaphore_and_status( - GUI_STATUS_FETCHING, - f"No flights found at {time_str}. Continuing scan..." - ) - # --- FINE MODIFICA --- elif message_type == MSG_TYPE_ADAPTER_STATUS: if message.get("status_code") == STATUS_STOPPED: diff --git a/flightmonitor/data/opensky_historical_adapter.py b/flightmonitor/data/opensky_historical_adapter.py index a33213a..ae73034 100644 --- a/flightmonitor/data/opensky_historical_adapter.py +++ b/flightmonitor/data/opensky_historical_adapter.py @@ -6,6 +6,7 @@ It requires authentication to access historical data. """ import time import threading +import json # MODIFICATO: Aggiunto import per json from queue import Queue, Full as QueueFull from typing import Dict, Any, List, Optional @@ -71,7 +72,6 @@ class OpenSkyHistoricalAdapter(BaseLiveDataAdapter): if not (self.client_id and self.client_secret and self.token_url): module_logger.critical(f"{self.name}: OAuth2 credentials/token URL missing. Historical adapter cannot function.") - # This will be handled in the run method to send a failure status. def _get_oauth_token(self) -> bool: """Requests an OAuth2 access token from OpenSky Network.""" @@ -87,13 +87,10 @@ class OpenSkyHistoricalAdapter(BaseLiveDataAdapter): } encoded_payload = urlencode(payload_dict) - # --- MODIFICA CHIAVE --- - # Aggiunto l'header User-Agent per replicare esattamente l'adapter live. headers = { "Content-Type": "application/x-www-form-urlencoded", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0" } - # --- FINE MODIFICA --- module_logger.info(f"{self.name}: Requesting new OAuth2 access token from {self.token_url}...") try: @@ -109,7 +106,7 @@ class OpenSkyHistoricalAdapter(BaseLiveDataAdapter): token_data = response.json() self.access_token = token_data.get("access_token") expires_in = token_data.get("expires_in", 300) - self.token_expires_at = time.time() + expires_in - 60 # 60-second buffer + self.token_expires_at = time.time() + expires_in - 60 if self.access_token: module_logger.info(f"{self.name}: Successfully obtained OAuth2 access token.") @@ -205,23 +202,15 @@ class OpenSkyHistoricalAdapter(BaseLiveDataAdapter): } headers = {"Authorization": f"Bearer {self.access_token}"} - # --- INIZIO LOGGING FORENSE --- - req = requests.Request('GET', app_config.OPENSKY_API_URL, params=params, headers=headers) - prepared_req = req.prepare() - - module_logger.info("-----------------------------------------------------") - module_logger.info(f"--- Making API Call for Historical Data ---") - module_logger.info(f"URL: {prepared_req.url}") - #module_logger.info(f"Headers: {prepared_req.headers}") - module_logger.info("-----------------------------------------------------") - # --- FINE LOGGING FORENSE --- - with requests.Session() as session: + req = requests.Request('GET', app_config.OPENSKY_API_URL, params=params, headers=headers) + prepared_req = session.prepare_request(req) response = session.send(prepared_req, timeout=self.api_timeout, verify=False) response.raise_for_status() - json_response = response.json() + raw_json_string = response.text # Cattura la stringa JSON grezza + json_response = json.loads(raw_json_string) # Ora parsa il JSON raw_states_list = json_response.get("states", []) canonical_states: List[CanonicalFlightState] = [] @@ -231,21 +220,25 @@ class OpenSkyHistoricalAdapter(BaseLiveDataAdapter): if cs: canonical_states.append(cs) - data_message: AdapterMessage = {"type": MSG_TYPE_FLIGHT_DATA, "timestamp": current_time, "payload": canonical_states} + # MODIFICATO: Crea un payload strutturato per la coda + payload = { + "canonical": canonical_states, + "raw_json": raw_json_string + } + data_message: AdapterMessage = { + "type": MSG_TYPE_FLIGHT_DATA, + "timestamp": current_time, + "payload": payload + } self.output_queue.put(data_message) module_logger.info(f"{self.name}: Success for timestamp {current_time}. Found {len(canonical_states)} states.") except requests.exceptions.HTTPError as http_err: - # --- LOGGING FORENSE DELL'ERRORE --- - module_logger.error("-----------------------------------------------------") - module_logger.error(f"--- API Call FAILED ---") - module_logger.error(f"Status Code: {http_err.response.status_code}") + module_logger.error(f"API Call FAILED for timestamp {current_time}. Status: {http_err.response.status_code}") try: - error_details = http_err.response.json() - module_logger.error(f"Error JSON Response: {error_details}") - except requests.exceptions.JSONDecodeError: + module_logger.error(f"Error JSON Response: {http_err.response.json()}") + except json.JSONDecodeError: module_logger.error(f"Error Raw Text: {http_err.response.text}") - module_logger.error("-----------------------------------------------------") if http_err.response.status_code in [401, 403]: self.access_token = None