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
+53 -1
View File
@@ -1,5 +1,5 @@
use time::OffsetDateTime;
use time::format_description::well_known::Rfc3339;
use time::{OffsetDateTime, UtcOffset};
/// Current UTC time as an RFC3339 string with second precision, e.g.
/// `2026-04-15T10:32:00Z`. Subsecond fractions are deliberately stripped
@@ -36,6 +36,27 @@ pub fn filename_stem_to_recorded_at(stem: &str) -> String {
}
}
/// Parse an RFC3339 timestamp. Returns `None` on any error so callers
/// can fall back to a lenient display path without having to classify
/// failures.
pub fn parse_rfc3339(s: &str) -> Option<OffsetDateTime> {
OffsetDateTime::parse(s, &Rfc3339).ok()
}
/// Extract `HH:MM` in the local timezone from an RFC3339 UTC timestamp.
/// On parse failure, returns the raw string truncated to its first 16
/// characters — the user still sees something readable even if the
/// value is shaped unexpectedly.
pub fn extract_hhmm(ts: &str) -> String {
match parse_rfc3339(ts) {
Some(t) => {
let local = t.to_offset(UtcOffset::current_local_offset().unwrap_or(UtcOffset::UTC));
format!("{:02}:{:02}", local.hour(), local.minute())
}
None => ts.chars().take(16).collect(),
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -88,4 +109,35 @@ mod tests {
);
assert!(now.ends_with('Z'), "expected UTC 'Z' suffix, got {now}");
}
#[test]
fn parse_rfc3339_accepts_utc_z() {
let t = parse_rfc3339("2026-04-18T10:32:00Z").expect("parse UTC");
assert_eq!(t.hour(), 10);
assert_eq!(t.minute(), 32);
}
#[test]
fn parse_rfc3339_returns_none_on_garbage() {
assert!(parse_rfc3339("not a timestamp").is_none());
}
/// `extract_hhmm` depends on the host timezone for formatting, so we
/// can't assert an exact value — but we can assert the shape and
/// that the fallback kicks in on unparseable input.
#[test]
fn extract_hhmm_shape_is_two_colon_two() {
let s = extract_hhmm("2026-04-18T10:32:00Z");
assert_eq!(s.len(), 5, "expected HH:MM, got {s}");
assert!(s.chars().nth(2) == Some(':'), "expected colon at index 2: {s}");
}
#[test]
fn extract_hhmm_falls_back_to_truncated_raw_on_parse_failure() {
assert_eq!(extract_hhmm("not-a-timestamp"), "not-a-timestamp");
assert_eq!(
extract_hhmm("way-longer-than-sixteen-chars"),
"way-longer-than-"
);
}
}