Skip to content

Commit e880a85

Browse files
committed
requests addressed, better error handling, more logging, small text corrections
1 parent 903e110 commit e880a85

11 files changed

Lines changed: 122 additions & 84 deletions

File tree

daemon/src/diag.rs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -166,15 +166,22 @@ impl DiagTask {
166166
if self.gps_mode == GpsMode::Fixed
167167
&& let Some((lat, lon)) = self.gps_fixed_coords
168168
&& let Some((entry_idx, _)) = qmdl_store.get_current_entry()
169-
&& let Ok(mut gps_file) = qmdl_store.open_entry_gps_for_append(entry_idx).await
170169
{
171-
let record = GpsRecord {
172-
unix_ts: 0,
173-
lat,
174-
lon,
175-
};
176-
if let Ok(json) = serde_json::to_string(&record) {
177-
let _ = gps_file.write_all(format!("{json}\n").as_bytes()).await;
170+
match qmdl_store.open_entry_gps_for_append(entry_idx).await {
171+
Ok(Some(mut gps_file)) => {
172+
let record = GpsRecord {
173+
unix_ts: 0,
174+
lat,
175+
lon,
176+
};
177+
if let Ok(json) = serde_json::to_string(&record) {
178+
let _ = gps_file.write_all(format!("{json}\n").as_bytes()).await;
179+
}
180+
}
181+
Ok(None) => {
182+
error!("GPS sidecar directory not found, cannot write fixed-mode coordinates")
183+
}
184+
Err(e) => error!("failed to open GPS sidecar for fixed-mode entry: {e}"),
178185
}
179186
}
180187
self.stop_current_recording().await;

daemon/src/gps.rs

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use axum::Json;
22
use axum::extract::State;
33
use axum::http::StatusCode;
44
use chrono::Utc;
5-
use log::error;
5+
use log::{error, warn};
66
use serde::{Deserialize, Deserializer, Serialize};
77
use std::sync::Arc;
88
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
@@ -47,14 +47,22 @@ pub struct GpsRecord {
4747
pub lon: f64,
4848
}
4949

50-
/// Reads all GPS records from a sidecar NDJSON file, skipping malformed lines.
50+
/// Reads all GPS records from a sidecar NDJSON file, logging and skipping malformed lines.
5151
pub async fn load_gps_records(file: tokio::fs::File) -> Vec<GpsRecord> {
5252
let reader = BufReader::new(file);
5353
let mut lines = reader.lines();
5454
let mut records = Vec::new();
55-
while let Ok(Some(line)) = lines.next_line().await {
56-
if let Ok(record) = serde_json::from_str::<GpsRecord>(&line) {
57-
records.push(record);
55+
loop {
56+
match lines.next_line().await {
57+
Ok(Some(line)) => match serde_json::from_str::<GpsRecord>(&line) {
58+
Ok(record) => records.push(record),
59+
Err(e) => warn!("skipping malformed GPS sidecar line: {e}"),
60+
},
61+
Ok(None) => break,
62+
Err(e) => {
63+
error!("error reading GPS sidecar file: {e}");
64+
break;
65+
}
5866
}
5967
}
6068
records
@@ -67,29 +75,47 @@ pub async fn post_gps(
6775
if state.config.gps_mode != GpsMode::Api {
6876
return Err((
6977
StatusCode::FORBIDDEN,
70-
"GPS API endpoint is disabled. Set gps_mode to 2 in configuration.".to_string(),
78+
"GPS API endpoint is disabled. Set gps_mode to API endpoint in configuration."
79+
.to_string(),
7180
));
7281
}
7382
let mut gps = state.gps_state.write().await;
7483
*gps = Some(gps_data.clone());
7584
drop(gps);
7685

7786
let qmdl_store = state.qmdl_store_lock.read().await;
78-
if let Some((entry_idx, _)) = qmdl_store.get_current_entry()
79-
&& let Ok(mut file) = qmdl_store.open_entry_gps_for_append(entry_idx).await
80-
{
81-
let record = GpsRecord {
82-
unix_ts: Utc::now().timestamp(),
83-
lat: gps_data.latitude,
84-
lon: gps_data.longitude,
85-
};
86-
match serde_json::to_string(&record) {
87-
Ok(json) => {
88-
if let Err(e) = file.write_all(format!("{json}\n").as_bytes()).await {
89-
error!("failed to write GPS record to sidecar: {e}");
90-
}
87+
if let Some((entry_idx, _)) = qmdl_store.get_current_entry() {
88+
match qmdl_store.open_entry_gps_for_append(entry_idx).await {
89+
Ok(Some(mut file)) => {
90+
let record = GpsRecord {
91+
unix_ts: Utc::now().timestamp(),
92+
lat: gps_data.latitude,
93+
lon: gps_data.longitude,
94+
};
95+
let json = serde_json::to_string(&record).map_err(|e| {
96+
error!("failed to serialize GPS record: {e}");
97+
(
98+
StatusCode::INTERNAL_SERVER_ERROR,
99+
format!("failed to serialize GPS record: {e}"),
100+
)
101+
})?;
102+
file.write_all(format!("{json}\n").as_bytes())
103+
.await
104+
.map_err(|e| {
105+
error!("failed to write GPS record to sidecar: {e}");
106+
(
107+
StatusCode::INTERNAL_SERVER_ERROR,
108+
format!("failed to write GPS record to sidecar: {e}"),
109+
)
110+
})?;
111+
}
112+
Ok(None) => error!("GPS sidecar directory not found, cannot write GPS record"),
113+
Err(e) => {
114+
return Err((
115+
StatusCode::INTERNAL_SERVER_ERROR,
116+
format!("failed to open GPS sidecar: {e}"),
117+
));
91118
}
92-
Err(e) => error!("failed to serialize GPS record: {e}"),
93119
}
94120
}
95121

daemon/src/main.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,8 @@ async fn run_with_config(
293293
notification_service,
294294
config.enabled_notifications.clone(),
295295
);
296-
296+
// For fixed configuration, we use timestamp 0 to not break other
297+
// the GET request for GPS but user won't see the 0 in PCAPs
297298
let initial_gps = if config.gps_mode == GpsMode::Fixed {
298299
match (config.gps_fixed_latitude, config.gps_fixed_longitude) {
299300
(Some(lat), Some(lon)) => Some(gps::GpsData {

daemon/src/pcap.rs

Lines changed: 19 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
use crate::config::GpsMode;
21
use crate::gps::{GpsRecord, load_gps_records};
32
use crate::server::ServerState;
43

4+
use crate::config::GpsMode;
55
use anyhow::Error;
66
use axum::body::Body;
77
use axum::extract::{Path, State};
@@ -74,41 +74,27 @@ pub(crate) async fn load_gps_records_for_entry(
7474
state: &Arc<ServerState>,
7575
entry_index: usize,
7676
) -> Vec<GpsRecord> {
77-
// Always try the per-session sidecar first — it reflects what was actually
78-
// recorded regardless of what the current gps_mode config is.
79-
let entry_gps_mode;
80-
{
81-
let qmdl_store = state.qmdl_store_lock.read().await;
82-
if let Ok(file) = qmdl_store.open_entry_gps(entry_index).await {
83-
let records = load_gps_records(file).await;
84-
if !records.is_empty() {
85-
return records;
77+
let qmdl_store = state.qmdl_store_lock.read().await;
78+
match qmdl_store.open_entry_gps(entry_index).await {
79+
Ok(Some(file)) => load_gps_records(file).await,
80+
Ok(None) => {
81+
let gps_mode = qmdl_store
82+
.manifest
83+
.entries
84+
.get(entry_index)
85+
.and_then(|e| e.gps_mode);
86+
if gps_mode.is_some_and(|m| m != GpsMode::Disabled) {
87+
error!(
88+
"GPS sidecar expected for entry {entry_index} (mode: {gps_mode:?}) but not found"
89+
);
8690
}
91+
vec![]
92+
}
93+
Err(e) => {
94+
error!("failed to open GPS sidecar: {e}");
95+
vec![]
8796
}
88-
// Capture the entry's recorded GPS mode before releasing the lock.
89-
entry_gps_mode = qmdl_store
90-
.manifest
91-
.entries
92-
.get(entry_index)
93-
.and_then(|e| e.gps_mode);
94-
}
95-
// Sidecar missing or empty — fall back using the entry's own recorded GPS mode,
96-
// not the current config, so old fixed-mode sessions still get coordinates even
97-
// if the mode has since been changed. Use the configured fixed coords directly
98-
// rather than gps_state, which can be overwritten by API calls or be None.
99-
if entry_gps_mode == Some(GpsMode::Fixed)
100-
&& let (Some(lat), Some(lon)) = (
101-
state.config.gps_fixed_latitude,
102-
state.config.gps_fixed_longitude,
103-
)
104-
{
105-
return vec![GpsRecord {
106-
unix_ts: 0,
107-
lat,
108-
lon,
109-
}];
11097
}
111-
vec![]
11298
}
11399

114100
fn find_nearest_gps(records: &[GpsRecord], packet_unix_ts: i64) -> Option<GpsPoint> {

daemon/src/qmdl_store.rs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -299,24 +299,33 @@ impl RecordingStore {
299299
.map_err(RecordingStoreError::ReadFileError)
300300
}
301301

302-
pub async fn open_entry_gps(&self, entry_index: usize) -> Result<File, RecordingStoreError> {
302+
pub async fn open_entry_gps(
303+
&self,
304+
entry_index: usize,
305+
) -> Result<Option<File>, RecordingStoreError> {
303306
let entry = &self.manifest.entries[entry_index];
304-
File::open(entry.get_gps_filepath(&self.path))
305-
.await
306-
.map_err(RecordingStoreError::ReadFileError)
307+
match File::open(entry.get_gps_filepath(&self.path)).await {
308+
Ok(file) => Ok(Some(file)),
309+
Err(e) if e.kind() == ErrorKind::NotFound => Ok(None),
310+
Err(e) => Err(RecordingStoreError::ReadFileError(e)),
311+
}
307312
}
308313

309314
pub async fn open_entry_gps_for_append(
310315
&self,
311316
entry_index: usize,
312-
) -> Result<File, RecordingStoreError> {
317+
) -> Result<Option<File>, RecordingStoreError> {
313318
let entry = &self.manifest.entries[entry_index];
314-
OpenOptions::new()
319+
match OpenOptions::new()
315320
.create(true)
316321
.append(true)
317322
.open(entry.get_gps_filepath(&self.path))
318323
.await
319-
.map_err(RecordingStoreError::CreateFileError)
324+
{
325+
Ok(file) => Ok(Some(file)),
326+
Err(e) if e.kind() == ErrorKind::NotFound => Ok(None),
327+
Err(e) => Err(RecordingStoreError::CreateFileError(e)),
328+
}
320329
}
321330

322331
pub async fn clear_and_open_entry_analysis(

daemon/web/src/lib/components/AnalysisView.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script lang="ts">
22
import { type ReportMetadata } from '$lib/analysis.svelte';
33
import type { ManifestEntry } from '$lib/manifest.svelte';
4+
import { GpsMode } from '$lib/utils.svelte';
45
import { AnalysisManager } from '$lib/analysisManager.svelte';
56
import AnalysisTable from './AnalysisTable.svelte';
67
import ReAnalyzeButton from './ReAnalyzeButton.svelte';
@@ -57,7 +58,7 @@
5758
</div>
5859
<div>
5960
<p class="text-lg underline">GPS Mode</p>
60-
<p>{(entry.gps_mode ?? 0) === 0 ? 'Disabled' : entry.gps_mode === 1 ? 'Fixed coordinates' : 'API endpoint'}</p>
61+
<p>{(entry.gps_mode ?? GpsMode.Disabled) === GpsMode.Disabled ? 'Disabled' : entry.gps_mode === GpsMode.Fixed ? 'Fixed coordinates' : 'API endpoint'}</p>
6162
</div>
6263
<div>
6364
<p class="text-lg underline">Analyzers</p>

daemon/web/src/lib/components/ConfigForm.svelte

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script lang="ts">
2-
import { get_config, set_config, test_notification, type Config } from '../utils.svelte';
2+
import { get_config, set_config, test_notification, GpsMode, type Config } from '../utils.svelte';
33
import Modal from './Modal.svelte';
44
55
let { shown = $bindable() }: { shown: boolean } = $props();
@@ -382,21 +382,21 @@
382382
<div>
383383
<label for="gps_mode" class="block text-sm font-medium text-gray-700 mb-1">GPS Mode</label>
384384
<select id="gps_mode" bind:value={config.gps_mode} class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-rayhunter-blue">
385-
<option value={0}>Disabled</option>
386-
<option value={1}>Fixed coordinates</option>
387-
<option value={2}>API endpoint</option>
385+
<option value={GpsMode.Disabled}>Disabled</option>
386+
<option value={GpsMode.Fixed}>Fixed coordinates</option>
387+
<option value={GpsMode.Api}>API endpoint</option>
388388
</select>
389389
<p class="text-xs text-gray-500 mt-1">
390-
{#if config.gps_mode === 2}
390+
{#if config.gps_mode === GpsMode.Api}
391391
POST latitude, longitude, and timestamp to <code>/api/gps</code> from any device on the network.
392-
{:else if config.gps_mode === 1}
392+
{:else if config.gps_mode === GpsMode.Fixed}
393393
GPS coordinates are fixed to the values below.
394394
{:else}
395395
GPS is disabled; no coordinates will be tracked.
396396
{/if}
397397
</p>
398398
</div>
399-
{#if config.gps_mode === 1}
399+
{#if config.gps_mode === GpsMode.Fixed}
400400
<div>
401401
<label for="gps_fixed_latitude" class="block text-sm font-medium text-gray-700 mb-1">Fixed Latitude</label>
402402
<input id="gps_fixed_latitude" type="number" min="-90" max="90" step="any" required

daemon/web/src/lib/components/ManifestCard.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script lang="ts">
22
import { ManifestEntry } from '$lib/manifest.svelte';
3+
import { GpsMode } from '$lib/utils.svelte';
34
import { AnalysisManager } from '$lib/analysisManager.svelte';
45
import DownloadLink from '$lib/components/DownloadLink.svelte';
56
import DeleteButton from '$lib/components/DeleteButton.svelte';
@@ -88,7 +89,7 @@
8889
{/if}
8990
{#if entry.gps_mode !== undefined}
9091
<div class="text-sm text-gray-500">
91-
GPS: {entry.gps_mode === 0 ? 'Disabled' : entry.gps_mode === 1 ? 'Fixed coordinates' : 'API endpoint'}
92+
GPS: {entry.gps_mode === GpsMode.Disabled ? 'Disabled' : entry.gps_mode === GpsMode.Fixed ? 'Fixed coordinates' : 'API endpoint'}
9293
</div>
9394
{/if}
9495
<div class="flex flex-row justify-between lg:justify-end gap-1 mt-2 overflow-x-auto">

daemon/web/src/lib/manifest.svelte.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { get_report, type AnalysisReport } from './analysis.svelte';
22
import { AnalysisStatus, type AnalysisManager } from './analysisManager.svelte';
3+
import { GpsMode } from './utils.svelte';
34

45
interface JsonManifest {
56
entries: JsonManifestEntry[];
@@ -12,7 +13,7 @@ interface JsonManifestEntry {
1213
last_message_time: string;
1314
qmdl_size_bytes: number;
1415
stop_reason: string | null;
15-
gps_mode: number | null;
16+
gps_mode: GpsMode | null;
1617
}
1718

1819
export class Manifest {
@@ -60,7 +61,7 @@ export class ManifestEntry {
6061
public analysis_status: AnalysisStatus | undefined = $state(undefined);
6162
public analysis_report: AnalysisReport | string | undefined = $state(undefined);
6263
public stop_reason: string | undefined = $state(undefined);
63-
public gps_mode: number | undefined = $state(undefined);
64+
public gps_mode: GpsMode | undefined = $state(undefined);
6465

6566
constructor(json: JsonManifestEntry) {
6667
this.name = json.name;

daemon/web/src/lib/utils.svelte.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ export enum enabled_notifications {
1818
LowBattery = 'LowBattery',
1919
}
2020

21+
export enum GpsMode {
22+
Disabled = 0,
23+
Fixed = 1,
24+
Api = 2,
25+
}
26+
2127
export interface Config {
2228
ui_level: number;
2329
colorblind_mode: boolean;
@@ -27,7 +33,7 @@ export interface Config {
2733
analyzers: AnalyzerConfig;
2834
min_space_to_start_recording_mb: number;
2935
min_space_to_continue_recording_mb: number;
30-
gps_mode: number;
36+
gps_mode: GpsMode;
3137
gps_fixed_latitude: number | null;
3238
gps_fixed_longitude: number | null;
3339
}

0 commit comments

Comments
 (0)