Files
rayhunter/daemon/src/gps.rs
2026-05-18 15:30:55 -07:00

91 lines
2.8 KiB
Rust

use axum::Json;
use axum::extract::State;
use axum::http::StatusCode;
use serde::{Deserialize, Deserializer, Serialize};
use std::sync::Arc;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use crate::server::ServerState;
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)]
pub struct GpsData {
pub latitude: f64,
pub longitude: f64,
#[serde(deserialize_with = "deserialize_unix_ts")]
pub timestamp: i64,
}
#[derive(Serialize, Deserialize)]
pub struct GpsRecord {
pub unix_ts: u32,
pub lat: f64,
pub lon: f64,
}
/// Reads all GPS records from a sidecar file, 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);
}
}
records
}
pub async fn post_gps(
State(state): State<Arc<ServerState>>,
Json(gps_data): Json<GpsData>,
) -> Result<StatusCode, (StatusCode, String)> {
if state.config.gps_mode != 2 {
return Err((
StatusCode::FORBIDDEN,
"GPS API endpoint is disabled. Set gps_mode to 2 in configuration.".to_string(),
));
}
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() {
if let Ok(mut file) = qmdl_store.open_entry_gps_for_append(entry_idx).await {
let record = GpsRecord { unix_ts: chrono::Utc::now().timestamp() as u32, lat: gps_data.latitude, lon: gps_data.longitude };
if let Ok(json) = serde_json::to_string(&record) {
let _ = file.write_all(format!("{json}\n").as_bytes()).await;
}
}
}
Ok(StatusCode::OK)
}
pub async fn get_gps(
State(state): State<Arc<ServerState>>,
) -> Result<Json<GpsData>, StatusCode> {
let gps = state.gps_state.read().await;
match gps.as_ref() {
Some(data) => Ok(Json(data.clone())),
None => Err(StatusCode::NOT_FOUND),
}
}