requests addressed, better error handling, more logging, small text corrections

This commit is contained in:
Carlos Guerra
2026-04-19 16:50:51 +02:00
committed by Will Greenberg
parent ba78c7bd01
commit 5a4a3034be
11 changed files with 123 additions and 82 deletions
+15 -8
View File
@@ -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;
+46 -20
View File
@@ -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<GpsRecord> {
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::<GpsRecord>(&line) {
records.push(record);
loop {
match lines.next_line().await {
Ok(Some(line)) => match serde_json::from_str::<GpsRecord>(&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}"),
}
}
+2
View File
@@ -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 {
+19 -33
View File
@@ -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<ServerState>,
entry_index: usize,
) -> Vec<GpsRecord> {
// 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<GpsPoint> {
+16 -7
View File
@@ -304,24 +304,33 @@ impl RecordingStore {
.map_err(RecordingStoreError::ReadFileError)
}
pub async fn open_entry_gps(&self, entry_index: usize) -> Result<File, RecordingStoreError> {
pub async fn open_entry_gps(
&self,
entry_index: usize,
) -> Result<Option<File>, 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<File, RecordingStoreError> {
) -> Result<Option<File>, 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(