SXXXXXXX_FlightMonitor/flightmonitor/controller/raw_data_logger.py
2025-06-13 11:48:49 +02:00

184 lines
7.0 KiB
Python

# 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 flightmonitor.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