Skip to main content

entracte_lib/
screen_time_store.rs

1use 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}