add raw save for historical data

This commit is contained in:
VALLONGOL 2025-06-13 11:41:39 +02:00
parent c465ab48cb
commit dfd58a347c
3 changed files with 137 additions and 199 deletions

View File

@ -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 []

View File

@ -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:

View File

@ -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