Add TOML dependency and new config/startup modules

The TOML dependency is added to `Cargo.lock` to support configuration
file parsing.
New modules `config` and `startup` are introduced in
`doctate-client-core`:
- `config`: Handles client configuration loading and saving, including
  defaults for polling intervals and window hours.
- `startup`: Contains routines for initial cleanup tasks, such as
  removing stale markers and orphan audio files based on defined
  retention periods.
This commit is contained in:
2026-04-18 14:09:26 +02:00
parent 5b17c331c9
commit 5d308df2b5
10 changed files with 666 additions and 458 deletions
+16 -36
View File
@@ -14,10 +14,13 @@ use std::sync::Arc;
use std::time::{Duration, Instant};
use doctate_client_core::{
cleanup_orphan_audio, write_sidecar, CaseMarker, CaseStore, PendingUpload, ServerSync,
SnapshotCache, SyncConfig, UploadEvent, WorkerSnapshot, WorkerState,
pick_footer_status, run_startup_cleanup, write_sidecar, CaseMarker, CaseStore, FooterStatus,
PendingUpload, ServerSync, SnapshotCache, SyncConfig, UploadEvent, WorkerSnapshot, WorkerState,
DEFAULT_MARKER_RETENTION, DEFAULT_ORPHAN_AUDIO_RETENTION,
};
use doctate_common::timestamp::{
extract_hhmm, now_rfc3339, parse_rfc3339, recorded_at_to_filename_stem,
};
use doctate_common::timestamp::{now_rfc3339, recorded_at_to_filename_stem};
use eframe::egui;
use tokio::runtime::Runtime;
use tokio::sync::{mpsc, watch};
@@ -26,7 +29,7 @@ use uuid::Uuid;
use crate::config::Config;
use crate::recorder::{Recorder, RecorderEvent};
use crate::state::{pick_footer_status, AppState, FooterStatus};
use crate::state::AppState;
pub struct DoctateApp {
runtime: Arc<Runtime>,
@@ -72,7 +75,7 @@ impl DoctateApp {
cases_dir: PathBuf,
snapshot_cache_path: PathBuf,
) -> Self {
let config = match Config::load() {
let config = match crate::config::load() {
Ok(Some(c)) => Some(c),
Ok(None) => None,
Err(e) => {
@@ -102,18 +105,13 @@ impl DoctateApp {
let store = case_store.clone();
let pending = pending_dir.clone();
runtime.block_on(async move {
let marker_cutoff = time::OffsetDateTime::now_utc() - time::Duration::hours(72);
match store.cleanup_stale(marker_cutoff).await {
Ok(n) if n > 0 => info!(count = n, "startup cleanup: stale markers removed"),
Ok(_) => {}
Err(e) => warn!(error = %e, "startup cleanup: marker sweep failed"),
}
let audio_cutoff =
std::time::SystemTime::now() - std::time::Duration::from_secs(24 * 3600);
let n = cleanup_orphan_audio(&pending, audio_cutoff).await;
if n > 0 {
info!(count = n, "startup cleanup: orphan audio removed");
}
run_startup_cleanup(
&store,
&pending,
DEFAULT_MARKER_RETENTION,
DEFAULT_ORPHAN_AUDIO_RETENTION,
)
.await;
});
}
@@ -164,7 +162,7 @@ impl DoctateApp {
.as_ref()
.and_then(|c| c.oneliner_window_hours),
};
if let Err(e) = cfg.save() {
if let Err(e) = crate::config::save(&cfg) {
error!(error = %e, "config save failed");
self.state = AppState::Error {
message: format!("Speichern: {e}"),
@@ -641,24 +639,6 @@ fn spawn_worker(
(sync, events_rx, snapshot_rx)
}
fn parse_rfc3339(s: &str) -> Option<time::OffsetDateTime> {
time::OffsetDateTime::parse(s, &time::format_description::well_known::Rfc3339).ok()
}
/// Extract `HH:MM` from an RFC3339 UTC timestamp. Returns the raw string
/// truncated to the first 16 chars as a fallback on parse failure — the
/// user still gets something readable.
fn extract_hhmm(ts: &str) -> String {
match parse_rfc3339(ts) {
Some(t) => {
let local = t
.to_offset(time::UtcOffset::current_local_offset().unwrap_or(time::UtcOffset::UTC));
format!("{:02}:{:02}", local.hour(), local.minute())
}
None => ts.chars().take(16).collect(),
}
}
impl eframe::App for DoctateApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
self.drain_recorder_events();