1use chrono::{DateTime, TimeDelta, Utc};
2use std::cmp::max;
3use tracing::debug;
4
5pub struct Tracker {
6 last_input_time: DateTime<Utc>,
7 is_idle: bool,
8 is_changed: bool,
9 idle_timeout: TimeDelta,
10
11 idle_end: Option<DateTime<Utc>>,
12}
13
14#[derive(Debug)]
15pub enum Status {
16 Idle {
17 changed: bool,
18 last_input_time: DateTime<Utc>,
19 duration: TimeDelta,
20 },
21 Active {
22 changed: bool,
23 last_input_time: DateTime<Utc>,
24 },
25}
26
27impl Tracker {
28 pub fn new(now: DateTime<Utc>, idle_timeout: TimeDelta) -> Self {
29 Self {
30 last_input_time: now,
31 is_idle: false,
32 is_changed: false,
33 idle_timeout,
34 idle_end: None,
35 }
36 }
37
38 fn set_idle(&mut self, is_idle: bool) {
39 self.is_idle = is_idle;
40 self.is_changed = true;
41 }
42
43 pub fn mark_not_idle(&mut self, now: DateTime<Utc>) {
44 debug!("No longer idle");
45 self.last_input_time = now;
46 self.set_idle(false);
47
48 self.idle_end = Some(now);
49 }
50
51 pub fn mark_idle(&mut self, _: DateTime<Utc>) {
52 debug!("Idle again");
53 self.set_idle(true);
54 }
55
56 pub fn get_with_last_input(
59 &mut self,
60 now: DateTime<Utc>,
61 seconds_since_input: u32,
62 ) -> anyhow::Result<Status> {
63 let time_since_input = TimeDelta::seconds(i64::from(seconds_since_input));
64
65 self.last_input_time = now - time_since_input;
66
67 if self.is_idle
68 && u64::from(seconds_since_input) < self.idle_timeout.num_seconds().try_into().unwrap()
69 {
70 debug!("No longer idle");
71 self.set_idle(false);
72 } else if !self.is_idle
73 && u64::from(seconds_since_input) >= self.idle_timeout.num_seconds().try_into().unwrap()
74 {
75 debug!("Idle again");
76 self.set_idle(true);
77 }
78
79 Ok(self.get_status(now))
80 }
81
82 pub fn get_reactive(&mut self, now: DateTime<Utc>) -> anyhow::Result<Status> {
83 if !self.is_idle {
84 self.last_input_time = max(self.last_input_time, now - self.idle_timeout);
85
86 if let Some(idle_end) = self.idle_end {
87 if self.last_input_time < idle_end {
88 self.last_input_time = idle_end;
89 }
90 }
91 }
92
93 Ok(self.get_status(now))
94 }
95
96 fn get_status(&mut self, now: DateTime<Utc>) -> Status {
97 let result = if self.is_changed {
98 if self.is_idle {
99 Status::Idle {
100 changed: self.is_changed,
101 last_input_time: self.last_input_time,
102 duration: now - self.last_input_time,
103 }
104 } else {
105 Status::Active {
106 changed: self.is_changed,
107 last_input_time: self.last_input_time,
108 }
109 }
110 } else if self.is_idle {
111 Status::Idle {
112 changed: self.is_changed,
113 last_input_time: self.last_input_time,
114 duration: now - self.last_input_time,
115 }
116 } else {
117 Status::Active {
118 changed: self.is_changed,
119 last_input_time: self.last_input_time,
120 }
121 };
122 self.is_changed = false;
123
124 result
125 }
126}