Skip to main content

entracte_lib/
pause_store.rs

1use 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)]
11#[serde(default)]
12pub struct PauseSnapshot {
13    pub paused: bool,
14    pub until_epoch_secs: Option<u64>,
15}
16
17pub fn load(path: &Path) -> PauseSnapshot {
18    match fs::read_to_string(path) {
19        Ok(text) => serde_json::from_str(&text).unwrap_or_else(|e| {
20            error!(
21                "pause_store: failed to parse {}: {e} — using defaults",
22                path.display()
23            );
24            PauseSnapshot::default()
25        }),
26        Err(e) if e.kind() == io::ErrorKind::NotFound => PauseSnapshot::default(),
27        Err(e) => {
28            error!(
29                "pause_store: failed to read {}: {e} — using defaults",
30                path.display()
31            );
32            PauseSnapshot::default()
33        }
34    }
35}
36
37pub fn save(path: &Path, snapshot: &PauseSnapshot) -> io::Result<()> {
38    let body = serde_json::to_string_pretty(snapshot).map_err(io::Error::other)?;
39    write_user_only(path, body.as_bytes())
40}
41
42#[cfg(test)]
43mod tests {
44    use super::*;
45    use crate::test_support::{temp_dir, TempDir};
46
47    fn temp_pause_file() -> (TempDir, std::path::PathBuf) {
48        let dir = temp_dir();
49        let path = dir.path().join("pause.json");
50        (dir, path)
51    }
52
53    #[test]
54    fn load_missing_returns_default() {
55        let (_dir, path) = temp_pause_file();
56        let s = load(&path);
57        assert!(!s.paused);
58        assert!(s.until_epoch_secs.is_none());
59    }
60
61    #[test]
62    fn save_and_load_round_trip_indefinite() {
63        let (_dir, path) = temp_pause_file();
64        let snap = PauseSnapshot {
65            paused: true,
66            until_epoch_secs: None,
67        };
68        save(&path, &snap).unwrap();
69        let loaded = load(&path);
70        assert!(loaded.paused);
71        assert!(loaded.until_epoch_secs.is_none());
72    }
73
74    #[test]
75    fn save_and_load_round_trip_until() {
76        let (_dir, path) = temp_pause_file();
77        let snap = PauseSnapshot {
78            paused: true,
79            until_epoch_secs: Some(1_700_000_000),
80        };
81        save(&path, &snap).unwrap();
82        let loaded = load(&path);
83        assert!(loaded.paused);
84        assert_eq!(loaded.until_epoch_secs, Some(1_700_000_000));
85    }
86
87    #[test]
88    fn save_and_load_round_trip_running() {
89        let (_dir, path) = temp_pause_file();
90        let snap = PauseSnapshot {
91            paused: false,
92            until_epoch_secs: None,
93        };
94        save(&path, &snap).unwrap();
95        let loaded = load(&path);
96        assert!(!loaded.paused);
97        assert!(loaded.until_epoch_secs.is_none());
98    }
99
100    #[test]
101    fn load_corrupt_returns_default() {
102        let (_dir, path) = temp_pause_file();
103        fs::write(&path, "{not valid json").unwrap();
104        let loaded = load(&path);
105        assert!(!loaded.paused);
106        assert!(loaded.until_epoch_secs.is_none());
107    }
108
109    #[test]
110    fn save_creates_parent_dirs() {
111        let dir = temp_dir();
112        let path = dir.path().join("a").join("b").join("pause.json");
113        save(&path, &PauseSnapshot::default()).unwrap();
114        assert!(path.exists());
115    }
116}