1use std::time::Instant;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum DaemonState {
9 Active,
11 Idle,
13 Dormant,
15}
16
17pub struct IdleTracker {
20 last_query: Instant,
21 last_change: Instant,
22}
23
24impl Default for IdleTracker {
25 fn default() -> Self {
26 Self::new()
27 }
28}
29
30impl IdleTracker {
31 #[must_use]
33 pub fn new() -> Self {
34 let now = Instant::now();
35 Self {
36 last_query: now,
37 last_change: now,
38 }
39 }
40
41 pub fn record_query(&mut self) {
43 self.last_query = Instant::now();
44 }
45
46 pub fn record_change(&mut self) {
48 self.last_change = Instant::now();
49 }
50
51 #[must_use]
53 pub fn state(&self) -> DaemonState {
54 let last_activity = self.last_query.max(self.last_change);
55 let elapsed = last_activity.elapsed().as_secs();
56
57 if elapsed < 5 * 60 {
58 DaemonState::Active
59 } else if elapsed < 30 * 60 {
60 DaemonState::Idle
61 } else {
62 DaemonState::Dormant
63 }
64 }
65}
66
67#[cfg(test)]
68#[allow(clippy::as_conversions, clippy::unwrap_used, clippy::indexing_slicing)]
69mod tests {
70 use super::*;
71 use std::time::Duration;
72
73 #[test]
74 fn test_idle_tracker() {
75 let mut tracker = IdleTracker::new();
76 assert_eq!(tracker.state(), DaemonState::Active);
77
78 tracker.last_query = Instant::now().checked_sub(Duration::from_mins(6)).unwrap();
80 tracker.last_change = Instant::now().checked_sub(Duration::from_mins(6)).unwrap();
81 assert_eq!(tracker.state(), DaemonState::Idle);
82
83 tracker.last_query = Instant::now().checked_sub(Duration::from_mins(31)).unwrap();
85 tracker.last_change = Instant::now().checked_sub(Duration::from_mins(31)).unwrap();
86 assert_eq!(tracker.state(), DaemonState::Dormant);
87
88 tracker.record_query();
90 assert_eq!(tracker.state(), DaemonState::Active);
91 }
92}