# FlightMonitor/controller/raw_data_logger.py """ Manages the creation and writing of raw data logs and summary reports. This class is designed to be reusable for live, historical, and playback modes. """ import os import json from datetime import datetime, timezone from typing import Optional, Dict, Any, TextIO from ..utils.logger import get_logger module_logger = get_logger(__name__) class RawDataLogger: """ Handles file operations for a single data logging session. It creates and writes to a raw data file and a corresponding summary report. """ def __init__(self): """Initializes the RawDataLogger.""" self._raw_log_file: Optional[TextIO] = None self._report_file: Optional[TextIO] = None self._is_active: bool = False self.raw_log_filepath: Optional[str] = None self.report_filepath: Optional[str] = None module_logger.debug("RawDataLogger instance created.") @property def is_active(self) -> bool: """Returns True if a logging session is currently active.""" return self._is_active def start_logging_session( self, target_directory: str, bounding_box: Dict[str, float] ) -> bool: """ Starts a new logging session. Creates files and writes the report header. Args: target_directory (str): The directory where log files will be saved. bounding_box (Dict[str, float]): The bounding box for the session. Returns: bool: True if the session started successfully, False otherwise. """ if self._is_active: module_logger.warning( "start_logging_session called while a session is already active. Please stop the current session first." ) return False # --- MODIFICA CHIAVE --- # Risolve il percorso in un percorso assoluto per evitare ambiguità. # Questo garantisce che la cartella venga creata nel posto giusto. resolved_directory = os.path.abspath(target_directory) module_logger.info(f"Resolved logging directory to absolute path: {resolved_directory}") # --- FINE MODIFICA --- try: os.makedirs(resolved_directory, exist_ok=True) except OSError as e: module_logger.error( f"Failed to create target directory '{resolved_directory}': {e}" ) return False now_utc = datetime.now(timezone.utc) timestamp_str = now_utc.strftime("%Y%m%d-%H%M%S") base_filename = f"atc-{timestamp_str}" self.raw_log_filepath = os.path.join(resolved_directory, f"{base_filename}.txt") self.report_filepath = os.path.join( resolved_directory, f"{base_filename}_report.txt" ) try: # Open both files before setting the active state self._raw_log_file = open(self.raw_log_filepath, "w", encoding="utf-8") self._report_file = open(self.report_filepath, "w", encoding="utf-8") self._write_report_header(now_utc, bounding_box) self._is_active = True module_logger.info(f"Raw logging session started. File: {self.raw_log_filepath}") return True except IOError as e: module_logger.error(f"Failed to open log files for writing: {e}") self.stop_logging_session() # Ensure cleanup if one file fails return False def _write_report_header(self, start_time: datetime, bbox: Dict[str, float]): """Writes the header information to the report file.""" if not self._report_file: return self._report_file.write("--- Flight Monitor Session Report ---\n") self._report_file.write(f"Session Start (UTC): {start_time.strftime('%Y-%m-%d %H:%M:%S')}\n") self._report_file.write(f"Bounding Box:\n") self._report_file.write(f" Lat Min: {bbox.get('lat_min')}\n") self._report_file.write(f" Lon Min: {bbox.get('lon_min')}\n") self._report_file.write(f" Lat Max: {bbox.get('lat_max')}\n") self._report_file.write(f" Lon Max: {bbox.get('lon_max')}\n") self._report_file.write("\n--- Data Points ---\n") self._report_file.write(f"{'Timestamp (UTC)':<25} | {'Aircraft Count'}\n") self._report_file.write(f"{'-'*25} | {'-'*15}\n") def log_raw_data(self, raw_json_string: str): """ Writes the raw JSON data string to the log file. Args: raw_json_string (str): The raw JSON string received from the provider. """ if not self._is_active or not self._raw_log_file: # module_logger.warning("Attempted to log raw data, but no active session.") return try: # We assume the input is already a JSON string. # We add a newline to separate the JSON objects in the file. self._raw_log_file.write(raw_json_string + "\n") except IOError as e: module_logger.error(f"IOError while writing to raw log file: {e}") self.stop_logging_session() # Stop session on write error def log_summary_data(self, timestamp: float, aircraft_count: int): """ Writes a summary line to the report file. Args: timestamp (float): The epoch timestamp of the data fetch. aircraft_count (int): The number of aircraft found in that fetch. """ if not self._is_active or not self._report_file: # module_logger.warning("Attempted to log summary data, but no active session.") return try: dt_object = datetime.fromtimestamp(timestamp, timezone.utc) time_str = dt_object.strftime("%Y-%m-%d %H:%M:%S") self._report_file.write(f"{time_str:<25} | {aircraft_count}\n") except (IOError, ValueError) as e: module_logger.error(f"Error while writing to report file: {e}") self.stop_logging_session() # Stop session on write error def stop_logging_session(self): """ Closes the log and report files and deactivates the session. """ if not self._is_active: return module_logger.info("Raw logging session stopping.") self._is_active = False if self._raw_log_file: try: self._raw_log_file.close() except IOError as e: module_logger.error(f"Error closing raw log file: {e}") self._raw_log_file = None if self._report_file: try: self._report_file.write(f"\nSession End (UTC): {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S')}\n") self._report_file.write("--- End of Report ---\n") self._report_file.close() except IOError as e: module_logger.error(f"Error closing report file: {e}") self._report_file = None self.raw_log_filepath = None self.report_filepath = None