Skip to main content

ix/
idle.rs

1//! Idle/dormancy detection for the daemon.
2
3use std::time::Instant;
4
5/// Activity level of the indexing daemon, based on time since last query
6/// or filesystem event.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum DaemonState {
9    /// Less than 5 minutes since the last query or change.
10    Active,
11    /// Between 5 and 30 minutes of inactivity.
12    Idle,
13    /// More than 30 minutes of inactivity.
14    Dormant,
15}
16
17/// Tracks elapsed time since the last query and the last filesystem change
18/// to determine the current [`DaemonState`].
19pub 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    /// Create a new tracker with the current time for both timestamps.
32    #[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    /// Mark that a search query was just received.
42    pub fn record_query(&mut self) {
43        self.last_query = Instant::now();
44    }
45
46    /// Mark that a filesystem change was just processed.
47    pub fn record_change(&mut self) {
48        self.last_change = Instant::now();
49    }
50
51    /// Return the current daemon state based on elapsed inactivity.
52    #[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        // Simulate 6 minutes of inactivity
79        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        // Simulate 31 minutes of inactivity
84        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        // Record activity and check if it's Active again
89        tracker.record_query();
90        assert_eq!(tracker.state(), DaemonState::Active);
91    }
92}