use crate::db::pauses::Pauses;
use crate::db::workdays::Workdays;
use crate::libs::config::MonitorConfig;
use crate::libs::messages::Message;
use crate::{msg_debug, msg_error, msg_info};
use anyhow::Result;
use chrono::{Local, NaiveDate};
use rdev::{EventType, listen};
use std::sync::{Arc, Mutex};
use tokio::time::{self, Duration, Instant};
use tracing::{Level, debug, instrument, span};
#[derive(Debug, Clone, Copy, PartialEq)]
enum State {
Active,
InPause,
}
pub struct Monitor {
pub config: MonitorConfig,
pub pauses: Pauses,
pub workdays: Workdays,
pub last_activity: Arc<Mutex<Instant>>,
pub activity_start: Arc<Mutex<Option<Instant>>>,
state: State,
}
impl Monitor {
#[instrument(skip(config))]
pub fn new(config: MonitorConfig) -> Result<Self> {
let span = span!(Level::INFO, "monitor_init");
let _enter = span.enter();
debug!("Initializing monitor with config: {:?}", config);
let pauses = Pauses::new()?;
let workdays = Workdays::new()?;
let last_activity = Arc::new(Mutex::new(Instant::now()));
let activity_start = Arc::new(Mutex::new(None));
let last_activity_clone = Arc::clone(&last_activity);
let activity_start_clone = Arc::clone(&activity_start);
std::thread::spawn(move || {
if let Err(e) = listen(move |event| match event.event_type {
EventType::KeyPress(_)
| EventType::KeyRelease(_)
| EventType::ButtonPress(_)
| EventType::ButtonRelease(_)
| EventType::MouseMove { .. }
| EventType::Wheel { .. } => {
{
let mut last_activity = last_activity_clone.lock().unwrap();
*last_activity = Instant::now();
}
{
let mut activity_start = activity_start_clone.lock().unwrap();
if activity_start.is_none() {
*activity_start = Some(Instant::now());
}
}
}
}) {
msg_error!(Message::ErrorInRdevListener(format!("{:?}", e)));
}
});
Ok(Monitor {
config,
pauses,
workdays,
last_activity,
activity_start,
state: State::Active,
})
}
#[instrument(skip(self))]
pub async fn run(&mut self) -> Result<()> {
msg_info!(Message::MonitorStarted {
pause_threshold: self.config.pause_threshold,
poll_interval: self.config.poll_interval,
activity_threshold: self.config.activity_threshold,
});
if self.config.pause_threshold == 0 {
return Ok(());
}
loop {
let activity_detected = self.detect_activity();
let today = Local::now().date_naive();
match self.state {
State::Active if !activity_detected => {
if let Err(e) = self.handle_inactivity() {
msg_error!(Message::DatabaseOperationFailed {
operation: "handle_inactivity".to_string(),
error: e.to_string()
});
}
}
State::InPause if activity_detected => {
if let Err(e) = self.handle_return_from_pause() {
msg_error!(Message::DatabaseOperationFailed {
operation: "handle_return_from_pause".to_string(),
error: e.to_string()
});
}
}
State::Active if activity_detected => {
if let Err(e) = self.ensure_workday_started(today) {
msg_error!(Message::DatabaseOperationFailed {
operation: "ensure_workday_started".to_string(),
error: e.to_string()
});
}
}
_ => {}
}
time::sleep(Duration::from_millis(self.config.poll_interval)).await;
}
}
pub fn detect_activity(&self) -> bool {
let elapsed = self.last_activity.lock().unwrap().elapsed();
let is_active = elapsed < Duration::from_millis(self.config.poll_interval);
msg_debug!(format!(
"Activity check: elapsed={:?}, active={}, threshold={:?}",
elapsed,
is_active,
Duration::from_millis(self.config.poll_interval)
));
is_active
}
fn handle_inactivity(&mut self) -> Result<()> {
let idle_time = self.last_activity.lock().unwrap().elapsed();
if idle_time >= Duration::from_secs(self.config.pause_threshold) {
let today = Local::now().date_naive();
if self.workdays.fetch(today)?.is_none() {
return Ok(());
}
msg_info!(Message::PauseStarted);
let pause_start_time = Local::now().naive_local() - chrono::Duration::seconds(self.config.pause_threshold as i64);
self.pauses.insert_start_with_time(pause_start_time)?;
self.state = State::InPause;
*self.activity_start.lock().unwrap() = None;
}
Ok(())
}
fn handle_return_from_pause(&mut self) -> Result<()> {
msg_info!(Message::PauseEnded);
self.pauses.insert_end()?;
self.state = State::Active;
Ok(())
}
pub fn ensure_workday_started(&mut self, today: NaiveDate) -> Result<()> {
let activity_start_time = {
let activity_start_guard = self.activity_start.lock().unwrap();
*activity_start_guard
};
if let Some(start_time) = activity_start_time {
let activity_duration = start_time.elapsed();
if activity_duration >= Duration::from_secs(self.config.activity_threshold) && self.workdays.fetch(today)?.is_none() {
match self.workdays.insert_start(today) {
Ok(()) => {
msg_info!(Message::WorkdayStarting(today.to_string()));
*self.activity_start.lock().unwrap() = None;
}
Err(e) => {
msg_error!(Message::WorkdayCreateFailed);
debug!("Workday creation error: {:?}", e);
}
}
}
}
Ok(())
}
}