entracte_lib/
screen_time_store.rs1use std::fs;
2use std::io;
3use std::path::Path;
4
5use log::error;
6use serde::{Deserialize, Serialize};
7
8use crate::secure_io::write_user_only;
9
10#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
11#[serde(default)]
12pub struct ScreenTimeSnapshot {
13 pub date: String,
14 pub seconds: u64,
15 pub last_reminder_epoch_secs: Option<u64>,
16}
17
18pub fn load(path: &Path) -> ScreenTimeSnapshot {
19 match fs::read_to_string(path) {
20 Ok(text) => serde_json::from_str(&text).unwrap_or_else(|e| {
21 error!(
22 "screen_time_store: failed to parse {}: {e} — using defaults",
23 path.display()
24 );
25 ScreenTimeSnapshot::default()
26 }),
27 Err(e) if e.kind() == io::ErrorKind::NotFound => ScreenTimeSnapshot::default(),
28 Err(e) => {
29 error!(
30 "screen_time_store: failed to read {}: {e} — using defaults",
31 path.display()
32 );
33 ScreenTimeSnapshot::default()
34 }
35 }
36}
37
38pub fn save(path: &Path, snapshot: &ScreenTimeSnapshot) -> io::Result<()> {
39 let body = serde_json::to_string_pretty(snapshot).map_err(io::Error::other)?;
40 write_user_only(path, body.as_bytes())
41}
42
43#[cfg(test)]
44mod tests {
45 use super::*;
46 use crate::test_support::{temp_dir, TempDir};
47
48 fn temp_screen_time_file() -> (TempDir, std::path::PathBuf) {
49 let dir = temp_dir();
50 let path = dir.path().join("screen_time.json");
51 (dir, path)
52 }
53
54 #[test]
55 fn load_missing_returns_default() {
56 let (_dir, path) = temp_screen_time_file();
57 let s = load(&path);
58 assert!(s.date.is_empty());
59 assert_eq!(s.seconds, 0);
60 assert!(s.last_reminder_epoch_secs.is_none());
61 }
62
63 #[test]
64 fn save_and_load_round_trip() {
65 let (_dir, path) = temp_screen_time_file();
66 let snap = ScreenTimeSnapshot {
67 date: "2026-05-15".to_string(),
68 seconds: 1234,
69 last_reminder_epoch_secs: Some(1_700_000_000),
70 };
71 save(&path, &snap).unwrap();
72 let loaded = load(&path);
73 assert_eq!(loaded, snap);
74 }
75
76 #[test]
77 fn load_corrupt_returns_default() {
78 let (_dir, path) = temp_screen_time_file();
79 fs::write(&path, "{not valid json").unwrap();
80 let loaded = load(&path);
81 assert!(loaded.date.is_empty());
82 assert_eq!(loaded.seconds, 0);
83 }
84
85 #[test]
86 fn save_creates_parent_dirs() {
87 let dir = temp_dir();
88 let path = dir.path().join("a").join("b").join("screen_time.json");
89 save(&path, &ScreenTimeSnapshot::default()).unwrap();
90 assert!(path.exists());
91 }
92}