1use crate::backend::load_config;
15use jiff::Zoned;
16use sioul_core::attention::{Attention, Column, Kind, Level, Now, Row, Slots};
17use sioul_core::quiet::{self, Blocks, Mode, Overrides};
18use sioul_core::reminders::Holds;
19use std::path::Path;
20use std::sync::Mutex;
21use std::time::SystemTime;
22
23fn changed(path: &Path) -> Option<SystemTime> {
25 std::fs::metadata(path).and_then(|m| m.modified()).ok()
26}
27
28fn held(now: &Zoned) -> Vec<(i64, i64)> {
31 static KEPT: Mutex<Option<(i64, jiff::civil::Date, Vec<(i64, i64)>)>> = Mutex::new(None);
32 let stamp = now.timestamp().as_second();
33 let mut kept = KEPT.lock().unwrap_or_else(|e| e.into_inner());
34 if let Some((at, day, held)) = kept.as_ref()
35 && *day == now.date()
36 && (0..300).contains(&(stamp - at))
37 {
38 return held.clone();
39 }
40 let midnight = now.date().to_zoned(now.time_zone().clone()).map_or(stamp, |z| z.timestamp().as_second());
41 let held = sioul_core::plan::event_spans(&sioul_core::agenda::occurrences(midnight - 86_400, midnight + 2 * 86_400), 0);
43 *kept = Some((stamp, now.date(), held.clone()));
44 held
45}
46
47pub(crate) fn blocks(now: &Zoned) -> Blocks {
50 type Key = (i64, Option<SystemTime>, Option<SystemTime>);
51 static KEPT: Mutex<Option<(Key, Blocks)>> = Mutex::new(None);
52 let (health, days) = (sioul_core::health::Health::default_path(), sioul_core::needs::Days::default_path());
53 let key = (now.timestamp().as_second() / 60, changed(&health), changed(&days));
54 if let Some((known, blocks)) = KEPT.lock().unwrap_or_else(|e| e.into_inner()).as_ref()
55 && *known == key
56 {
57 return blocks.clone();
58 }
59 let needs = sioul_core::health::Health::load(&health).needs;
60 let blocks = Blocks::of(&needs, &sioul_core::needs::Days::load(&days), &held(now), now);
61 *KEPT.lock().unwrap_or_else(|e| e.into_inner()) = Some((key, blocks.clone()));
62 blocks
63}
64
65pub(crate) fn mode_at(now: &Zoned) -> Mode {
67 let config = load_config();
68 let overrides = Overrides::load(&Overrides::default_path());
69 quiet::mode(&config.week_hours(), &config.time_off, &overrides, &blocks(now), now)
70}
71
72pub(crate) fn mode_now() -> Mode {
73 mode_at(&Zoned::now())
74}
75
76pub(crate) fn slots() -> Slots {
81 static KEPT: Mutex<Option<(Option<SystemTime>, Slots)>> = Mutex::new(None);
82 let path = Slots::default_path();
83 let stamp = changed(&path);
84 let mut kept = KEPT.lock().unwrap_or_else(|e| e.into_inner());
85 if let Some((known, slots)) = kept.as_ref()
86 && *known == stamp
87 && stamp.is_some()
88 {
89 return slots.clone();
90 }
91 let slots = Slots::load(&path);
92 *kept = Some((stamp, slots.clone()));
93 slots
94}
95
96pub(crate) fn in_slot(at: &Zoned) -> bool {
100 slots().at(at)
101}
102
103pub(crate) fn quiet_slot() -> bool {
105 in_slot(&Zoned::now())
106}
107
108pub(crate) fn attention() -> Attention {
110 Attention::of(&load_config())
111}
112
113pub(crate) fn attention_at(mode: &Mode, now: &Zoned) -> Now {
118 let config = load_config();
119 let overrides = Overrides::load(&Overrides::default_path());
120 let mut moment = Now::of(mode).layers(in_slot(now), crate::everywhere::holds_others());
121 moment.nothing = mode.free() && sioul_core::pause::nothing_now(&overrides, &config.free_time);
122 moment.reachable = crate::everywhere::reachable().is_some_and(|r| r.until.is_none_or(|u| now.timestamp().as_second() < u));
124 moment.holds.porch_rests = overrides.porch_rests(now.timestamp().as_second());
125 moment
126}
127
128pub(crate) fn attention_now() -> Now {
130 let now = Zoned::now();
131 attention_at(&mode_at(&now), &now)
132}
133
134pub(crate) fn level(kind: Kind) -> Level {
136 attention().level(Row::Own(kind), &attention_now())
137}
138
139pub(crate) fn comes(kind: Kind) -> bool {
141 level(kind) == Level::Now
142}
143
144pub(crate) fn with_layers(mut holds: Holds) -> Holds {
147 holds.now.slot = quiet_slot();
148 holds.now.dnd = crate::everywhere::holds_others();
149 holds
150}
151
152pub(crate) fn doses_silent() -> bool {
156 !comes(Kind::Doses)
157}
158
159pub(crate) fn waking() -> Option<i64> {
162 let mode = mode_now();
163 if mode.paused() {
164 return Some(Zoned::now().timestamp().as_second() + 5 * 60);
165 }
166 let sleeps = mode.sleeps();
167 mode.until.filter(|_| sleeps).map(|until| until.timestamp().as_second())
168}
169
170pub(crate) fn woke_from() -> Option<i64> {
175 woken().map(|(from, _)| from)
176}
177
178pub(crate) fn woken() -> Option<(i64, i64)> {
182 let attention = attention();
183 let held_in = |column: Column| attention.cell(Row::Own(Kind::Doses), column) != Level::Now;
184 let now = Zoned::now();
185 let stamp = now.timestamp().as_second();
186 let overrides = Overrides::load(&Overrides::default_path());
187 let paused = overrides.paused_since.zip(overrides.paused_ended).filter(|(since, ended)| held_in(Column::Pause) && ended > since && *ended <= stamp && stamp - ended < 30 * 60);
188 if !held_in(Column::Sleep) {
189 return paused;
190 }
191 let blocks = blocks(&now);
192 if blocks.at(stamp, &["sleep", "nap"]).is_some() {
194 return None;
195 }
196 blocks.kept.iter().filter(|k| (k.kind == "sleep" || k.kind == "nap") && k.end <= stamp && stamp - k.end < 30 * 60).map(|k| (k.start, k.end)).chain(paused).reduce(|a, b| (a.0.min(b.0), a.1.max(b.1)))
197}
198
199pub(crate) fn night_set() -> bool {
201 blocks(&Zoned::now()).night
202}