use anyhow::Result;
use clap::Parser;
pub mod cli;
pub mod conditions;
pub fn run() -> Result<()> {
let args = cli::Cli::parse();
run_with_args(args)
}
fn run_with_args(args: cli::Cli) -> Result<()> {
let condition = args
.command
.into_wait_condition(args.jitter, args.verbose, args.probability);
condition.wait()
}
pub fn verbose_wait<F>(total_wait: std::time::Duration, update_period: std::time::Duration, mut display_fn: F)
where
F: FnMut(std::time::Duration),
{
let start = std::time::Instant::now();
let mut last_displayed_eta: Option<u64> = None;
loop {
let elapsed = start.elapsed();
let remaining = total_wait.saturating_sub(elapsed);
let eta = remaining.as_secs_f64();
let rounded_eta = eta.round() as u64;
if remaining == std::time::Duration::ZERO {
display_fn(std::time::Duration::ZERO);
break;
}
if last_displayed_eta.map_or(true, |last_eta| last_eta != rounded_eta) {
display_fn(std::time::Duration::from_secs(rounded_eta));
last_displayed_eta = Some(rounded_eta);
}
let next_update_time = elapsed + update_period;
let sleep_duration = next_update_time.saturating_sub(elapsed);
if sleep_duration > std::time::Duration::ZERO {
std::thread::sleep(sleep_duration);
} else if remaining > std::time::Duration::ZERO {
std::thread::yield_now();
} else {
break;
}
}
}
pub fn adaptive_verbose_wait<F>(total_wait: std::time::Duration, mut display_fn: F)
where
F: FnMut(std::time::Duration),
{
let start = std::time::Instant::now();
let mut last_displayed_eta: Option<u64> = None;
loop {
let elapsed = start.elapsed();
let remaining = total_wait.saturating_sub(elapsed);
let eta = remaining.as_secs_f64();
let rounded_eta = eta.round() as u64;
if remaining == std::time::Duration::ZERO {
display_fn(std::time::Duration::ZERO);
break;
}
if last_displayed_eta.map_or(true, |last_eta| last_eta != rounded_eta) {
display_fn(std::time::Duration::from_secs(rounded_eta));
last_displayed_eta = Some(rounded_eta);
}
let current_update_period = get_adaptive_update_period(remaining);
let remaining_secs = remaining.as_secs();
let time_to_next_marker = if current_update_period.as_secs() == 0 {
remaining
} else {
let target_marker_secs = (remaining_secs / current_update_period.as_secs()) * current_update_period.as_secs();
remaining.saturating_sub(std::time::Duration::from_secs(target_marker_secs))
};
let time_to_next_threshold = if remaining_secs > 600 {
remaining.saturating_sub(std::time::Duration::from_secs(600))
} else if remaining_secs > 300 {
remaining.saturating_sub(std::time::Duration::from_secs(300))
} else if remaining_secs > 60 {
remaining.saturating_sub(std::time::Duration::from_secs(60))
} else if remaining_secs > 20 {
remaining.saturating_sub(std::time::Duration::from_secs(20))
} else {
remaining
};
let sleep_duration = std::cmp::min(current_update_period, std::cmp::min(time_to_next_threshold, time_to_next_marker));
let sleep_duration = sleep_duration.max(std::time::Duration::from_millis(1));
if sleep_duration > std::time::Duration::ZERO {
std::thread::sleep(sleep_duration);
} else if remaining > std::time::Duration::ZERO {
std::thread::yield_now();
} else {
break;
}
}
}
fn get_adaptive_update_period(remaining: std::time::Duration) -> std::time::Duration {
let remaining_secs = remaining.as_secs();
if remaining_secs <= 20 {
std::time::Duration::from_secs(1) } else if remaining_secs <= 60 {
std::time::Duration::from_secs(5) } else if remaining_secs <= 300 {
std::time::Duration::from_secs(10) } else if remaining_secs <= 600 {
std::time::Duration::from_secs(15) } else {
std::time::Duration::from_secs(60) }
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
use crate::cli::{Cli, Commands};
#[test]
fn test_verbose_wait() {
let total_wait = Duration::from_millis(100);
let update_period = Duration::from_millis(10);
let mut call_count = 0;
verbose_wait(total_wait, update_period, |_| {
call_count += 1;
});
assert!(call_count > 0);
}
#[test]
fn test_adaptive_verbose_wait() {
let total_wait = Duration::from_secs(5);
let mut call_count = 0;
adaptive_verbose_wait(total_wait, |_| {
call_count += 1;
});
assert!(call_count > 0);
}
#[test]
fn test_get_adaptive_update_period() {
assert_eq!(get_adaptive_update_period(Duration::from_secs(0)), Duration::from_secs(1));
assert_eq!(get_adaptive_update_period(Duration::from_secs(10)), Duration::from_secs(1));
assert_eq!(get_adaptive_update_period(Duration::from_secs(20)), Duration::from_secs(1));
assert_eq!(get_adaptive_update_period(Duration::from_secs(21)), Duration::from_secs(5));
assert_eq!(get_adaptive_update_period(Duration::from_secs(40)), Duration::from_secs(5));
assert_eq!(get_adaptive_update_period(Duration::from_secs(60)), Duration::from_secs(5));
assert_eq!(get_adaptive_update_period(Duration::from_secs(61)), Duration::from_secs(10));
assert_eq!(get_adaptive_update_period(Duration::from_secs(150)), Duration::from_secs(10));
assert_eq!(get_adaptive_update_period(Duration::from_secs(300)), Duration::from_secs(10));
assert_eq!(get_adaptive_update_period(Duration::from_secs(301)), Duration::from_secs(15));
assert_eq!(get_adaptive_update_period(Duration::from_secs(450)), Duration::from_secs(15));
assert_eq!(get_adaptive_update_period(Duration::from_secs(600)), Duration::from_secs(15));
assert_eq!(get_adaptive_update_period(Duration::from_secs(601)), Duration::from_secs(60));
assert_eq!(get_adaptive_update_period(Duration::from_secs(1000)), Duration::from_secs(60));
}
#[test]
fn test_run_with_args_duration() {
let cli = Cli {
command: Commands::Duration { time: Duration::from_secs(1) },
jitter: None,
verbose: None,
probability: None,
};
assert!(run_with_args(cli).is_ok());
}
#[test]
fn test_run_with_args_normal() {
let cli = Cli {
command: Commands::Normal { mean: Duration::from_secs(1), std_dev: 0.1 },
jitter: None,
verbose: None,
probability: None,
};
assert!(run_with_args(cli).is_ok());
}
}