//! Tracks the last-activity timestamp; the GTK timer queries this each tick //! to decide whether 60s of idle has elapsed. use std::time::{Duration, Instant}; pub(crate) struct WatchdogState { last_activity: Instant, timeout: Duration, } impl WatchdogState { pub fn new(timeout: Duration) -> Self { Self { last_activity: Instant::now(), timeout, } } pub fn poke(&mut self) { self.last_activity = Instant::now(); } /// True iff `timeout` has passed since the last poke. Once expired, /// stays expired until the caller drops + recreates. pub fn expired(&self) -> bool { self.last_activity.elapsed() >= self.timeout } } #[cfg(test)] mod tests { use super::*; #[test] fn fresh_state_is_not_expired() { let w = WatchdogState::new(Duration::from_secs(60)); assert!(!w.expired()); } #[test] fn zero_timeout_is_immediately_expired() { let w = WatchdogState::new(Duration::from_millis(0)); std::thread::sleep(Duration::from_millis(2)); assert!(w.expired()); } #[test] fn poke_resets_the_clock() { let mut w = WatchdogState::new(Duration::from_millis(100)); std::thread::sleep(Duration::from_millis(150)); assert!(w.expired()); w.poke(); assert!(!w.expired()); } }