1use chrono::{DateTime, Utc};
13use serde::{Deserialize, Serialize};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
16#[serde(rename_all = "snake_case", tag = "kind", content = "until")]
17pub enum PauseState {
18 Indefinite,
19 Until(DateTime<Utc>),
20}
21
22impl PauseState {
23 pub fn is_active(&self, now: DateTime<Utc>) -> bool {
25 match self {
26 PauseState::Indefinite => true,
27 PauseState::Until(t) => *t > now,
28 }
29 }
30}
31
32pub fn read_pause_state() -> Option<PauseState> {
36 let path = crate::paths::paused_until_path();
37 let contents = std::fs::read_to_string(&path).ok()?;
38 parse_pause_state(contents.trim())
39}
40
41fn parse_pause_state(s: &str) -> Option<PauseState> {
42 if s.is_empty() {
43 return None;
44 }
45 if s.eq_ignore_ascii_case("indefinite") {
46 return Some(PauseState::Indefinite);
47 }
48 DateTime::parse_from_rfc3339(s)
49 .ok()
50 .map(|t| PauseState::Until(t.with_timezone(&Utc)))
51}
52
53pub fn write_pause_state(state: Option<PauseState>) -> std::io::Result<()> {
55 let path = crate::paths::paused_until_path();
56 match state {
57 None => match std::fs::remove_file(&path) {
58 Ok(()) => Ok(()),
59 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
60 Err(e) => Err(e),
61 },
62 Some(PauseState::Indefinite) => {
63 crate::paths::ensure_state_dir()?;
64 std::fs::write(&path, "indefinite\n")
65 }
66 Some(PauseState::Until(t)) => {
67 crate::paths::ensure_state_dir()?;
68 std::fs::write(&path, format!("{}\n", t.to_rfc3339()))
69 }
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76
77 #[test]
78 fn parses_indefinite() {
79 assert_eq!(
80 parse_pause_state("indefinite"),
81 Some(PauseState::Indefinite)
82 );
83 assert_eq!(
84 parse_pause_state("INDEFINITE"),
85 Some(PauseState::Indefinite)
86 );
87 }
88
89 #[test]
90 fn parses_rfc3339() {
91 let s = "2030-01-01T00:00:00Z";
92 match parse_pause_state(s) {
93 Some(PauseState::Until(_)) => {}
94 other => panic!("expected Until, got {:?}", other),
95 }
96 }
97
98 #[test]
99 fn rejects_garbage() {
100 assert_eq!(parse_pause_state(""), None);
101 assert_eq!(parse_pause_state("nope"), None);
102 }
103
104 #[test]
105 fn is_active_indefinite() {
106 assert!(PauseState::Indefinite.is_active(Utc::now()));
107 }
108
109 #[test]
110 fn is_active_until() {
111 let past = Utc::now() - chrono::Duration::hours(1);
112 let future = Utc::now() + chrono::Duration::hours(1);
113 assert!(!PauseState::Until(past).is_active(Utc::now()));
114 assert!(PauseState::Until(future).is_active(Utc::now()));
115 }
116}