use std::{pin::Pin, time::Duration};
use rand::Rng;
use tokio::time::{Instant, Sleep};
const MIN_INTERVAL_SECS: u64 = 15 * 60;
const MAX_INTERVAL_SECS: u64 = 30 * 60;
pub struct IdlerHandle {
app_ids: Vec<u32>,
sleep: Pin<Box<Sleep>>,
}
impl IdlerHandle {
pub fn new(app_ids: Vec<u32>) -> Self {
let delay = random_duration();
Self { app_ids, sleep: Box::pin(tokio::time::sleep(delay)) }
}
pub async fn tick(&mut self) {
(&mut self.sleep).await;
let delay = random_duration();
self.sleep = Box::pin(tokio::time::sleep(delay));
}
#[inline]
pub fn app_ids(&self) -> &[u32] {
&self.app_ids
}
pub fn set_app_ids(&mut self, app_ids: Vec<u32>) {
self.app_ids = app_ids;
}
pub fn time_until_next_tick(&self) -> Duration {
let deadline = self.sleep.deadline();
let now = Instant::now();
if deadline > now {
deadline - now
} else {
Duration::ZERO
}
}
pub fn reset(&mut self) {
let delay = random_duration();
self.sleep = Box::pin(tokio::time::sleep(delay));
}
pub fn trigger_now(&mut self) {
self.sleep = Box::pin(tokio::time::sleep(Duration::ZERO));
}
}
fn random_duration() -> Duration {
let secs = rand::rng().random_range(MIN_INTERVAL_SECS..=MAX_INTERVAL_SECS);
Duration::from_secs(secs)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_random_duration_in_range() {
for _ in 0..100 {
let duration = random_duration();
let secs = duration.as_secs();
assert!((MIN_INTERVAL_SECS..=MAX_INTERVAL_SECS).contains(&secs), "Duration {} outside range [{}, {}]", secs, MIN_INTERVAL_SECS, MAX_INTERVAL_SECS);
}
}
#[tokio::test]
async fn test_new_handle_has_app_ids() {
let handle = IdlerHandle::new(vec![730, 440]);
assert_eq!(handle.app_ids(), &[730, 440]);
}
#[tokio::test]
async fn test_set_app_ids() {
let mut handle = IdlerHandle::new(vec![730]);
handle.set_app_ids(vec![440, 570]);
assert_eq!(handle.app_ids(), &[440, 570]);
}
#[tokio::test]
async fn test_tick_resets_timer() {
let mut handle = IdlerHandle { app_ids: vec![730], sleep: Box::pin(tokio::time::sleep(Duration::from_millis(10))) };
handle.tick().await;
let remaining = handle.time_until_next_tick();
assert!(remaining.as_secs() > 0 || remaining == Duration::ZERO);
}
#[tokio::test]
async fn test_reset() {
let mut handle = IdlerHandle::new(vec![730]);
let original_deadline = handle.sleep.deadline();
tokio::time::sleep(Duration::from_millis(1)).await;
handle.reset();
let new_deadline = handle.sleep.deadline();
assert_ne!(new_deadline, original_deadline);
}
}