mirror of
https://github.com/EFForg/rayhunter.git
synced 2026-07-17 21:38:11 -07:00
Use latest packet timestamp in GPS file, move writing into DiagTask to eliminate RwLocks, remove "sidecar" word from codebase
This commit is contained in:
committed by
Will Greenberg
parent
2ada840919
commit
0c90f8910a
+31
-71
@@ -1,12 +1,13 @@
|
||||
use axum::Json;
|
||||
use axum::extract::State;
|
||||
use axum::http::StatusCode;
|
||||
use log::{error, info, warn};
|
||||
use log::{error, warn};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use std::sync::Arc;
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||
|
||||
use crate::config::GpsMode;
|
||||
use crate::diag::DiagDeviceCtrlMessage;
|
||||
use crate::server::ServerState;
|
||||
|
||||
fn deserialize_latitude<'de, D>(deserializer: D) -> Result<f64, D::Error>
|
||||
@@ -37,28 +38,6 @@ where
|
||||
Ok(v)
|
||||
}
|
||||
|
||||
fn deserialize_unix_ts<'de, D>(deserializer: D) -> Result<i64, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
use serde::de;
|
||||
use serde_json::Value;
|
||||
match Value::deserialize(deserializer)? {
|
||||
Value::Number(n) => n
|
||||
.as_i64()
|
||||
.or_else(|| n.as_f64().map(|f| f as i64))
|
||||
.ok_or_else(|| de::Error::custom("timestamp out of range")),
|
||||
Value::String(s) => s
|
||||
.trim()
|
||||
.parse::<f64>()
|
||||
.map(|f| f as i64)
|
||||
.map_err(|_| de::Error::custom("timestamp must be a numeric value")),
|
||||
_ => Err(de::Error::custom(
|
||||
"timestamp must be a number or numeric string",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "apidocs", derive(utoipa::ToSchema))]
|
||||
pub struct GpsData {
|
||||
@@ -66,18 +45,20 @@ pub struct GpsData {
|
||||
pub latitude: f64,
|
||||
#[serde(deserialize_with = "deserialize_longitude")]
|
||||
pub longitude: f64,
|
||||
#[serde(deserialize_with = "deserialize_unix_ts")]
|
||||
pub timestamp: i64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct GpsRecord {
|
||||
pub unix_ts: i64,
|
||||
/// Packet timestamp (modem clock) for correlation with captured packets.
|
||||
/// None if no packets have been received yet.
|
||||
pub latest_packet_timestamp: Option<i64>,
|
||||
/// Drift-corrected system time when this GPS fix was received
|
||||
pub system_time: i64,
|
||||
pub lat: f64,
|
||||
pub lon: f64,
|
||||
}
|
||||
|
||||
/// Reads all GPS records from a sidecar NDJSON file, logging and skipping malformed lines.
|
||||
/// Reads all GPS records from a storage 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();
|
||||
@@ -86,16 +67,16 @@ pub async fn load_gps_records(file: tokio::fs::File) -> Vec<GpsRecord> {
|
||||
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}"),
|
||||
Err(e) => warn!("skipping malformed GPS storage line: {e}"),
|
||||
},
|
||||
Ok(None) => break,
|
||||
Err(e) => {
|
||||
error!("error reading GPS sidecar file: {e}");
|
||||
error!("error reading GPS storage file: {e}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
records.sort_by_key(|r| r.unix_ts);
|
||||
records.sort_by_key(|r| r.latest_packet_timestamp.unwrap_or(i64::MIN));
|
||||
records
|
||||
}
|
||||
|
||||
@@ -108,10 +89,10 @@ pub async fn load_gps_records(file: tokio::fs::File) -> Vec<GpsRecord> {
|
||||
responses(
|
||||
(status = StatusCode::OK, description = "GPS data accepted"),
|
||||
(status = StatusCode::FORBIDDEN, description = "GPS API endpoint is disabled"),
|
||||
(status = StatusCode::INTERNAL_SERVER_ERROR, description = "Failed to write GPS record")
|
||||
(status = StatusCode::INTERNAL_SERVER_ERROR, description = "Failed to send GPS update")
|
||||
),
|
||||
summary = "Submit GPS coordinates",
|
||||
description = "Submit GPS coordinates from an external source (e.g. a phone app). Requires gps_mode to be set to 'Api' in configuration. latitude is in decimal degrees from -90 to 90, longitude is in decimal degrees from -180 to 180, timestamp is a Unix timestamp in seconds."
|
||||
description = "Submit GPS coordinates from an external source (e.g. a phone app). Requires gps_mode to be set to 'Api' in configuration. latitude is in decimal degrees from -90 to 90, longitude is in decimal degrees from -180 to 180. The timestamp is derived from the most recent packet's modem timestamp."
|
||||
))]
|
||||
pub async fn post_gps(
|
||||
State(state): State<Arc<ServerState>>,
|
||||
@@ -124,48 +105,27 @@ pub async fn post_gps(
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// Update in-memory state for GET /api/gps
|
||||
let mut gps = state.gps_state.write().await;
|
||||
*gps = Some(gps_data.clone());
|
||||
drop(gps);
|
||||
|
||||
let qmdl_store = state.qmdl_store_lock.read().await;
|
||||
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: gps_data.timestamp,
|
||||
lat: gps_data.latitude,
|
||||
lon: gps_data.longitude,
|
||||
};
|
||||
let mut json = serde_json::to_vec(&record).map_err(|e| {
|
||||
error!("failed to serialize GPS record: {e}");
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("failed to serialize GPS record: {e}"),
|
||||
)
|
||||
})?;
|
||||
json.push(b'\n');
|
||||
file.write_all(&json).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}"),
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
info!(
|
||||
"GPS data received but no recording is active — position updated in memory only, not persisted to sidecar"
|
||||
);
|
||||
}
|
||||
// Send to DiagTask to write to storage with packet timestamp
|
||||
state
|
||||
.diag_device_ctrl_sender
|
||||
.send(DiagDeviceCtrlMessage::GpsUpdate {
|
||||
lat: gps_data.latitude,
|
||||
lon: gps_data.longitude,
|
||||
})
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("failed to send GPS update: {e}");
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("failed to send GPS update: {e}"),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user