use crate::collector::types::{KeyboardEvent, MouseEvent, SensorEvent, ShortcutEvent};
use chrono::{DateTime, Duration, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EventWindow {
pub start: DateTime<Utc>,
pub end: DateTime<Utc>,
pub keyboard_events: Vec<KeyboardEvent>,
pub mouse_events: Vec<MouseEvent>,
pub shortcut_events: Vec<ShortcutEvent>,
pub is_session_start: bool,
pub app_id: Option<String>,
}
impl EventWindow {
pub fn new(start: DateTime<Utc>, duration: Duration) -> Self {
Self {
start,
end: start + duration,
keyboard_events: Vec::new(),
mouse_events: Vec::new(),
shortcut_events: Vec::new(),
is_session_start: false,
app_id: None,
}
}
pub fn contains(&self, timestamp: DateTime<Utc>) -> bool {
timestamp >= self.start && timestamp < self.end
}
pub fn add_event(&mut self, event: SensorEvent) {
match event {
SensorEvent::Keyboard(e) => self.keyboard_events.push(e),
SensorEvent::Mouse(e) => self.mouse_events.push(e),
SensorEvent::Shortcut(e) => self.shortcut_events.push(e),
}
}
pub fn is_empty(&self) -> bool {
self.keyboard_events.is_empty()
&& self.mouse_events.is_empty()
&& self.shortcut_events.is_empty()
}
pub fn event_count(&self) -> usize {
self.keyboard_events.len() + self.mouse_events.len() + self.shortcut_events.len()
}
pub fn duration_secs(&self) -> f64 {
(self.end - self.start).num_milliseconds() as f64 / 1000.0
}
}
pub struct WindowManager {
window_duration: Duration,
session_gap_threshold: Duration,
current_window: Option<EventWindow>,
completed_windows: Vec<EventWindow>,
last_event_time: Option<DateTime<Utc>>,
}
impl WindowManager {
pub fn new(window_duration_secs: u64, session_gap_threshold_secs: u64) -> Self {
Self {
window_duration: Duration::seconds(window_duration_secs as i64),
session_gap_threshold: Duration::seconds(session_gap_threshold_secs as i64),
current_window: None,
completed_windows: Vec::new(),
last_event_time: None,
}
}
pub fn process_event(&mut self, event: SensorEvent) {
let event_time = event.timestamp();
let is_new_session = if let Some(last_time) = self.last_event_time {
event_time - last_time > self.session_gap_threshold
} else {
true };
if is_new_session && self.current_window.is_some() {
self.complete_current_window();
}
if self.current_window.is_none() {
let mut window = EventWindow::new(event_time, self.window_duration);
window.is_session_start = is_new_session;
self.current_window = Some(window);
}
let window = self.current_window.as_ref().unwrap();
if event_time >= window.end {
self.complete_current_window();
let mut window = EventWindow::new(event_time, self.window_duration);
window.is_session_start = is_new_session;
self.current_window = Some(window);
}
if let Some(ref mut window) = self.current_window {
window.add_event(event);
}
self.last_event_time = Some(event_time);
}
pub fn flush(&mut self) {
self.complete_current_window();
}
pub fn take_completed_windows(&mut self) -> Vec<EventWindow> {
std::mem::take(&mut self.completed_windows)
}
pub fn has_completed_windows(&self) -> bool {
!self.completed_windows.is_empty()
}
pub fn completed_window_count(&self) -> usize {
self.completed_windows.len()
}
fn complete_current_window(&mut self) {
if let Some(mut window) = self.current_window.take() {
if !window.is_empty() {
window.app_id = crate::collector::get_frontmost_app_id();
self.completed_windows.push(window);
}
}
}
pub fn check_window_expiry(&mut self) {
let now = Utc::now();
if let Some(ref window) = self.current_window {
if now >= window.end {
self.complete_current_window();
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_window_creation() {
let start = Utc::now();
let window = EventWindow::new(start, Duration::seconds(10));
assert_eq!(window.start, start);
assert_eq!(window.end, start + Duration::seconds(10));
assert!(window.is_empty());
}
#[test]
fn test_window_contains() {
let start = Utc::now();
let window = EventWindow::new(start, Duration::seconds(10));
assert!(window.contains(start));
assert!(window.contains(start + Duration::seconds(5)));
assert!(!window.contains(start + Duration::seconds(10)));
assert!(!window.contains(start - Duration::seconds(1)));
}
#[test]
fn test_shortcut_events_routed() {
let start = Utc::now();
let mut window = EventWindow::new(start, Duration::seconds(10));
assert!(window.is_empty());
let shortcut = SensorEvent::Shortcut(crate::collector::types::ShortcutEvent {
timestamp: start,
shortcut_type: crate::collector::types::ShortcutType::Copy,
});
window.add_event(shortcut);
assert!(!window.is_empty());
assert_eq!(window.event_count(), 1);
assert_eq!(window.shortcut_events.len(), 1);
}
#[test]
fn test_event_count_includes_shortcuts() {
let start = Utc::now();
let mut window = EventWindow::new(start, Duration::seconds(10));
window.add_event(SensorEvent::Keyboard(
crate::collector::types::KeyboardEvent::new(true),
));
window.add_event(SensorEvent::Shortcut(
crate::collector::types::ShortcutEvent {
timestamp: start,
shortcut_type: crate::collector::types::ShortcutType::Paste,
},
));
assert_eq!(window.event_count(), 2);
assert_eq!(window.keyboard_events.len(), 1);
assert_eq!(window.shortcut_events.len(), 1);
}
#[test]
fn test_window_manager_basic() {
let mut manager = WindowManager::new(10, 300);
for _ in 0..5 {
let event = SensorEvent::Keyboard(crate::collector::types::KeyboardEvent::new(true));
manager.process_event(event);
}
assert!(!manager.has_completed_windows());
manager.flush();
assert!(manager.has_completed_windows());
let windows = manager.take_completed_windows();
assert_eq!(windows.len(), 1);
assert_eq!(windows[0].keyboard_events.len(), 5);
}
}