aster_forge_cloud_files_windows/
watchdog.rs1use std::time::{Duration, Instant};
4
5use crate::{Result, WindowsCloudFilesError};
6
7pub const WINDOWS_CFAPI_CALLBACK_TIMEOUT: Duration = Duration::from_mins(1);
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct WindowsFetchDataWatchdogConfig {
13 timeout: Duration,
14}
15
16impl WindowsFetchDataWatchdogConfig {
17 pub fn new(timeout: Duration) -> Result<Self> {
23 if timeout.is_zero() {
24 return Err(WindowsCloudFilesError::InvalidWatchdogTimeout {
25 reason: "timeout must be positive",
26 });
27 }
28 if timeout > WINDOWS_CFAPI_CALLBACK_TIMEOUT {
29 return Err(WindowsCloudFilesError::InvalidWatchdogTimeout {
30 reason: "timeout exceeds the fixed CFAPI callback timeout",
31 });
32 }
33 Ok(Self { timeout })
34 }
35
36 #[must_use]
38 pub const fn timeout(self) -> Duration {
39 self.timeout
40 }
41}
42
43impl Default for WindowsFetchDataWatchdogConfig {
44 fn default() -> Self {
45 Self {
46 timeout: WINDOWS_CFAPI_CALLBACK_TIMEOUT,
47 }
48 }
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub struct WindowsFetchDataWatchdog {
55 config: WindowsFetchDataWatchdogConfig,
56 last_activity: Instant,
57}
58
59impl WindowsFetchDataWatchdog {
60 #[must_use]
62 pub const fn started(config: WindowsFetchDataWatchdogConfig, now: Instant) -> Self {
63 Self {
64 config,
65 last_activity: now,
66 }
67 }
68
69 #[must_use]
71 pub fn deadline(self) -> Option<Instant> {
72 self.last_activity.checked_add(self.config.timeout)
73 }
74
75 pub fn touch(&mut self, now: Instant) {
77 self.last_activity = now;
78 }
79
80 #[must_use]
82 pub fn is_due(self, now: Instant) -> bool {
83 self.deadline().is_some_and(|deadline| now >= deadline)
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn watchdog_rejects_zero_and_longer_than_platform_deadline() {
93 assert!(WindowsFetchDataWatchdogConfig::new(Duration::ZERO).is_err());
94 assert!(
95 WindowsFetchDataWatchdogConfig::new(
96 WINDOWS_CFAPI_CALLBACK_TIMEOUT + Duration::from_secs(1)
97 )
98 .is_err()
99 );
100 }
101
102 #[test]
103 fn watchdog_deadline_is_refreshed_by_activity() {
104 let config = WindowsFetchDataWatchdogConfig::new(Duration::from_secs(5)).unwrap();
105 let start = Instant::now();
106 let mut watchdog = WindowsFetchDataWatchdog::started(config, start);
107 assert!(!watchdog.is_due(start + Duration::from_secs(4)));
108 watchdog.touch(start + Duration::from_secs(4));
109 assert!(!watchdog.is_due(start + Duration::from_secs(8)));
110 assert!(watchdog.is_due(start + Duration::from_secs(9)));
111 }
112}