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