synheart_sensor_agent/core/
windowing.rs1use crate::collector::types::{KeyboardEvent, MouseEvent, SensorEvent, ShortcutEvent};
7use chrono::{DateTime, Duration, Utc};
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct EventWindow {
13 pub start: DateTime<Utc>,
15 pub end: DateTime<Utc>,
17 pub keyboard_events: Vec<KeyboardEvent>,
19 pub mouse_events: Vec<MouseEvent>,
21 pub shortcut_events: Vec<ShortcutEvent>,
23 pub is_session_start: bool,
25 pub app_id: Option<String>,
28}
29
30impl EventWindow {
31 pub fn new(start: DateTime<Utc>, duration: Duration) -> Self {
33 Self {
34 start,
35 end: start + duration,
36 keyboard_events: Vec::new(),
37 mouse_events: Vec::new(),
38 shortcut_events: Vec::new(),
39 is_session_start: false,
40 app_id: None,
41 }
42 }
43
44 pub fn contains(&self, timestamp: DateTime<Utc>) -> bool {
46 timestamp >= self.start && timestamp < self.end
47 }
48
49 pub fn add_event(&mut self, event: SensorEvent) {
51 match event {
52 SensorEvent::Keyboard(e) => self.keyboard_events.push(e),
53 SensorEvent::Mouse(e) => self.mouse_events.push(e),
54 SensorEvent::Shortcut(e) => self.shortcut_events.push(e),
55 }
56 }
57
58 pub fn is_empty(&self) -> bool {
60 self.keyboard_events.is_empty()
61 && self.mouse_events.is_empty()
62 && self.shortcut_events.is_empty()
63 }
64
65 pub fn event_count(&self) -> usize {
67 self.keyboard_events.len() + self.mouse_events.len() + self.shortcut_events.len()
68 }
69
70 pub fn duration_secs(&self) -> f64 {
72 (self.end - self.start).num_milliseconds() as f64 / 1000.0
73 }
74}
75
76pub struct WindowManager {
78 window_duration: Duration,
80 session_gap_threshold: Duration,
82 current_window: Option<EventWindow>,
84 completed_windows: Vec<EventWindow>,
86 last_event_time: Option<DateTime<Utc>>,
88}
89
90impl WindowManager {
91 pub fn new(window_duration_secs: u64, session_gap_threshold_secs: u64) -> Self {
93 Self {
94 window_duration: Duration::seconds(window_duration_secs as i64),
95 session_gap_threshold: Duration::seconds(session_gap_threshold_secs as i64),
96 current_window: None,
97 completed_windows: Vec::new(),
98 last_event_time: None,
99 }
100 }
101
102 pub fn process_event(&mut self, event: SensorEvent) {
109 let event_time = event.timestamp();
110
111 let is_new_session = if let Some(last_time) = self.last_event_time {
113 event_time - last_time > self.session_gap_threshold
114 } else {
115 true };
117
118 if is_new_session && self.current_window.is_some() {
120 self.complete_current_window();
121 }
122
123 if self.current_window.is_none() {
125 let mut window = EventWindow::new(event_time, self.window_duration);
126 window.is_session_start = is_new_session;
127 self.current_window = Some(window);
128 }
129
130 let window = self.current_window.as_ref().unwrap();
132 if event_time >= window.end {
133 self.complete_current_window();
135
136 let mut window = EventWindow::new(event_time, self.window_duration);
138 window.is_session_start = is_new_session;
139 self.current_window = Some(window);
140 }
141
142 if let Some(ref mut window) = self.current_window {
144 window.add_event(event);
145 }
146
147 self.last_event_time = Some(event_time);
148 }
149
150 pub fn flush(&mut self) {
152 self.complete_current_window();
153 }
154
155 pub fn take_completed_windows(&mut self) -> Vec<EventWindow> {
157 std::mem::take(&mut self.completed_windows)
158 }
159
160 pub fn has_completed_windows(&self) -> bool {
162 !self.completed_windows.is_empty()
163 }
164
165 pub fn completed_window_count(&self) -> usize {
167 self.completed_windows.len()
168 }
169
170 fn complete_current_window(&mut self) {
172 if let Some(mut window) = self.current_window.take() {
173 if !window.is_empty() {
175 window.app_id = crate::collector::get_frontmost_app_id();
176 self.completed_windows.push(window);
177 }
178 }
179 }
180
181 pub fn check_window_expiry(&mut self) {
183 let now = Utc::now();
184 if let Some(ref window) = self.current_window {
185 if now >= window.end {
186 self.complete_current_window();
187 }
188 }
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 #[test]
197 fn test_window_creation() {
198 let start = Utc::now();
199 let window = EventWindow::new(start, Duration::seconds(10));
200
201 assert_eq!(window.start, start);
202 assert_eq!(window.end, start + Duration::seconds(10));
203 assert!(window.is_empty());
204 }
205
206 #[test]
207 fn test_window_contains() {
208 let start = Utc::now();
209 let window = EventWindow::new(start, Duration::seconds(10));
210
211 assert!(window.contains(start));
212 assert!(window.contains(start + Duration::seconds(5)));
213 assert!(!window.contains(start + Duration::seconds(10)));
214 assert!(!window.contains(start - Duration::seconds(1)));
215 }
216
217 #[test]
218 fn test_shortcut_events_routed() {
219 let start = Utc::now();
220 let mut window = EventWindow::new(start, Duration::seconds(10));
221 assert!(window.is_empty());
222
223 let shortcut = SensorEvent::Shortcut(crate::collector::types::ShortcutEvent {
224 timestamp: start,
225 shortcut_type: crate::collector::types::ShortcutType::Copy,
226 });
227 window.add_event(shortcut);
228
229 assert!(!window.is_empty());
230 assert_eq!(window.event_count(), 1);
231 assert_eq!(window.shortcut_events.len(), 1);
232 }
233
234 #[test]
235 fn test_event_count_includes_shortcuts() {
236 let start = Utc::now();
237 let mut window = EventWindow::new(start, Duration::seconds(10));
238
239 window.add_event(SensorEvent::Keyboard(
240 crate::collector::types::KeyboardEvent::new(true),
241 ));
242 window.add_event(SensorEvent::Shortcut(
243 crate::collector::types::ShortcutEvent {
244 timestamp: start,
245 shortcut_type: crate::collector::types::ShortcutType::Paste,
246 },
247 ));
248
249 assert_eq!(window.event_count(), 2);
250 assert_eq!(window.keyboard_events.len(), 1);
251 assert_eq!(window.shortcut_events.len(), 1);
252 }
253
254 #[test]
255 fn test_window_manager_basic() {
256 let mut manager = WindowManager::new(10, 300);
257
258 for _ in 0..5 {
260 let event = SensorEvent::Keyboard(crate::collector::types::KeyboardEvent::new(true));
261 manager.process_event(event);
262 }
263
264 assert!(!manager.has_completed_windows());
266
267 manager.flush();
269 assert!(manager.has_completed_windows());
270
271 let windows = manager.take_completed_windows();
272 assert_eq!(windows.len(), 1);
273 assert_eq!(windows[0].keyboard_events.len(), 5);
274 }
275}