diff --git a/daemon/src/diag.rs b/daemon/src/diag.rs index ba520dc..b8e9215 100644 --- a/daemon/src/diag.rs +++ b/daemon/src/diag.rs @@ -167,15 +167,22 @@ impl DiagTask { if self.gps_mode == GpsMode::Fixed && let Some((lat, lon)) = self.gps_fixed_coords && let Some((entry_idx, _)) = qmdl_store.get_current_entry() - && let Ok(mut gps_file) = qmdl_store.open_entry_gps_for_append(entry_idx).await { - let record = GpsRecord { - unix_ts: 0, - lat, - lon, - }; - if let Ok(json) = serde_json::to_string(&record) { - let _ = gps_file.write_all(format!("{json}\n").as_bytes()).await; + match qmdl_store.open_entry_gps_for_append(entry_idx).await { + Ok(Some(mut gps_file)) => { + let record = GpsRecord { + unix_ts: 0, + lat, + lon, + }; + if let Ok(json) = serde_json::to_string(&record) { + let _ = gps_file.write_all(format!("{json}\n").as_bytes()).await; + } + } + Ok(None) => { + error!("GPS sidecar directory not found, cannot write fixed-mode coordinates") + } + Err(e) => error!("failed to open GPS sidecar for fixed-mode entry: {e}"), } } self.stop_current_recording().await; diff --git a/daemon/src/gps.rs b/daemon/src/gps.rs index 1b61281..3158fca 100644 --- a/daemon/src/gps.rs +++ b/daemon/src/gps.rs @@ -2,7 +2,7 @@ use axum::Json; use axum::extract::State; use axum::http::StatusCode; use chrono::Utc; -use log::error; +use log::{error, warn}; use serde::{Deserialize, Deserializer, Serialize}; use std::sync::Arc; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; @@ -47,14 +47,22 @@ pub struct GpsRecord { pub lon: f64, } -/// Reads all GPS records from a sidecar NDJSON file, skipping malformed lines. +/// Reads all GPS records from a sidecar NDJSON file, logging and skipping malformed lines. pub async fn load_gps_records(file: tokio::fs::File) -> Vec { let reader = BufReader::new(file); let mut lines = reader.lines(); let mut records = Vec::new(); - while let Ok(Some(line)) = lines.next_line().await { - if let Ok(record) = serde_json::from_str::(&line) { - records.push(record); + loop { + match lines.next_line().await { + Ok(Some(line)) => match serde_json::from_str::(&line) { + Ok(record) => records.push(record), + Err(e) => warn!("skipping malformed GPS sidecar line: {e}"), + }, + Ok(None) => break, + Err(e) => { + error!("error reading GPS sidecar file: {e}"); + break; + } } } records @@ -67,7 +75,8 @@ pub async fn post_gps( if state.config.gps_mode != GpsMode::Api { return Err(( StatusCode::FORBIDDEN, - "GPS API endpoint is disabled. Set gps_mode to 2 in configuration.".to_string(), + "GPS API endpoint is disabled. Set gps_mode to API endpoint in configuration." + .to_string(), )); } let mut gps = state.gps_state.write().await; @@ -75,21 +84,38 @@ pub async fn post_gps( drop(gps); let qmdl_store = state.qmdl_store_lock.read().await; - if let Some((entry_idx, _)) = qmdl_store.get_current_entry() - && let Ok(mut file) = qmdl_store.open_entry_gps_for_append(entry_idx).await - { - let record = GpsRecord { - unix_ts: Utc::now().timestamp(), - lat: gps_data.latitude, - lon: gps_data.longitude, - }; - match serde_json::to_string(&record) { - Ok(json) => { - if let Err(e) = file.write_all(format!("{json}\n").as_bytes()).await { - error!("failed to write GPS record to sidecar: {e}"); - } + if let Some((entry_idx, _)) = qmdl_store.get_current_entry() { + match qmdl_store.open_entry_gps_for_append(entry_idx).await { + Ok(Some(mut file)) => { + let record = GpsRecord { + unix_ts: Utc::now().timestamp(), + lat: gps_data.latitude, + lon: gps_data.longitude, + }; + let json = serde_json::to_string(&record).map_err(|e| { + error!("failed to serialize GPS record: {e}"); + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("failed to serialize GPS record: {e}"), + ) + })?; + file.write_all(format!("{json}\n").as_bytes()) + .await + .map_err(|e| { + error!("failed to write GPS record to sidecar: {e}"); + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("failed to write GPS record to sidecar: {e}"), + ) + })?; + } + Ok(None) => error!("GPS sidecar directory not found, cannot write GPS record"), + Err(e) => { + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + format!("failed to open GPS sidecar: {e}"), + )); } - Err(e) => error!("failed to serialize GPS record: {e}"), } } diff --git a/daemon/src/main.rs b/daemon/src/main.rs index 2d232f8..ad79161 100644 --- a/daemon/src/main.rs +++ b/daemon/src/main.rs @@ -306,6 +306,8 @@ async fn run_with_config( config.webdav.clone().into(), ); } + // For fixed configuration, we use timestamp 0 to not break other + // the GET request for GPS but user won't see the 0 in PCAPs let initial_gps = if config.gps_mode == GpsMode::Fixed { match (config.gps_fixed_latitude, config.gps_fixed_longitude) { (Some(lat), Some(lon)) => Some(gps::GpsData { diff --git a/daemon/src/pcap.rs b/daemon/src/pcap.rs index 33068b4..eb93a8d 100644 --- a/daemon/src/pcap.rs +++ b/daemon/src/pcap.rs @@ -1,7 +1,7 @@ -use crate::config::GpsMode; use crate::gps::{GpsRecord, load_gps_records}; use crate::server::ServerState; +use crate::config::GpsMode; use anyhow::Error; use axum::body::Body; use axum::extract::{Path, State}; @@ -74,41 +74,27 @@ pub(crate) async fn load_gps_records_for_entry( state: &Arc, entry_index: usize, ) -> Vec { - // Always try the per-session sidecar first — it reflects what was actually - // recorded regardless of what the current gps_mode config is. - let entry_gps_mode; - { - let qmdl_store = state.qmdl_store_lock.read().await; - if let Ok(file) = qmdl_store.open_entry_gps(entry_index).await { - let records = load_gps_records(file).await; - if !records.is_empty() { - return records; + let qmdl_store = state.qmdl_store_lock.read().await; + match qmdl_store.open_entry_gps(entry_index).await { + Ok(Some(file)) => load_gps_records(file).await, + Ok(None) => { + let gps_mode = qmdl_store + .manifest + .entries + .get(entry_index) + .and_then(|e| e.gps_mode); + if gps_mode.is_some_and(|m| m != GpsMode::Disabled) { + error!( + "GPS sidecar expected for entry {entry_index} (mode: {gps_mode:?}) but not found" + ); } + vec![] + } + Err(e) => { + error!("failed to open GPS sidecar: {e}"); + vec![] } - // Capture the entry's recorded GPS mode before releasing the lock. - entry_gps_mode = qmdl_store - .manifest - .entries - .get(entry_index) - .and_then(|e| e.gps_mode); } - // Sidecar missing or empty — fall back using the entry's own recorded GPS mode, - // not the current config, so old fixed-mode sessions still get coordinates even - // if the mode has since been changed. Use the configured fixed coords directly - // rather than gps_state, which can be overwritten by API calls or be None. - if entry_gps_mode == Some(GpsMode::Fixed) - && let (Some(lat), Some(lon)) = ( - state.config.gps_fixed_latitude, - state.config.gps_fixed_longitude, - ) - { - return vec![GpsRecord { - unix_ts: 0, - lat, - lon, - }]; - } - vec![] } fn find_nearest_gps(records: &[GpsRecord], packet_unix_ts: i64) -> Option { diff --git a/daemon/src/qmdl_store.rs b/daemon/src/qmdl_store.rs index 1569391..e74c66a 100644 --- a/daemon/src/qmdl_store.rs +++ b/daemon/src/qmdl_store.rs @@ -304,24 +304,33 @@ impl RecordingStore { .map_err(RecordingStoreError::ReadFileError) } - pub async fn open_entry_gps(&self, entry_index: usize) -> Result { + pub async fn open_entry_gps( + &self, + entry_index: usize, + ) -> Result, RecordingStoreError> { let entry = &self.manifest.entries[entry_index]; - File::open(entry.get_gps_filepath(&self.path)) - .await - .map_err(RecordingStoreError::ReadFileError) + match File::open(entry.get_gps_filepath(&self.path)).await { + Ok(file) => Ok(Some(file)), + Err(e) if e.kind() == ErrorKind::NotFound => Ok(None), + Err(e) => Err(RecordingStoreError::ReadFileError(e)), + } } pub async fn open_entry_gps_for_append( &self, entry_index: usize, - ) -> Result { + ) -> Result, RecordingStoreError> { let entry = &self.manifest.entries[entry_index]; - OpenOptions::new() + match OpenOptions::new() .create(true) .append(true) .open(entry.get_gps_filepath(&self.path)) .await - .map_err(RecordingStoreError::CreateFileError) + { + Ok(file) => Ok(Some(file)), + Err(e) if e.kind() == ErrorKind::NotFound => Ok(None), + Err(e) => Err(RecordingStoreError::CreateFileError(e)), + } } pub async fn clear_and_open_entry_analysis( diff --git a/daemon/web/src/lib/components/AnalysisView.svelte b/daemon/web/src/lib/components/AnalysisView.svelte index 0d5d763..e6b278c 100644 --- a/daemon/web/src/lib/components/AnalysisView.svelte +++ b/daemon/web/src/lib/components/AnalysisView.svelte @@ -1,6 +1,7 @@