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