add raw save for historical data
This commit is contained in:
parent
c465ab48cb
commit
dfd58a347c
@ -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,19 +291,30 @@ 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.")
|
||||
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}")
|
||||
@ -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
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
def on_application_exit(self):
|
||||
self._save_app_settings()
|
||||
|
||||
if self.is_historical_download_active:
|
||||
self.stop_historical_download()
|
||||
|
||||
# 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.cleanup_manager:
|
||||
self.cleanup_manager.on_application_exit()
|
||||
else:
|
||||
module_logger.critical("CleanupManager not initialized. Cannot perform proper cleanup.")
|
||||
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)
|
||||
|
||||
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_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.")
|
||||
|
||||
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"
|
||||
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)
|
||||
|
||||
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 []
|
||||
|
||||
@ -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,16 +92,24 @@ 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 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(
|
||||
@ -111,8 +121,7 @@ class HistoricalDataProcessor:
|
||||
|
||||
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
|
||||
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
|
||||
@ -124,7 +133,6 @@ class HistoricalDataProcessor:
|
||||
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:
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user