use crate::config::Job;
use crate::ticker;
use crate::exec::execute_job;
use chrono::{DateTime, Local};
use std::sync::atomic::{AtomicBool, Ordering};
pub struct Cron {
shutdown: AtomicBool,
jobs: Vec<Job>,
}
impl Cron {
pub fn new(jobs: Vec<Job>) -> Cron {
Cron {
jobs, shutdown: AtomicBool::new(false)
}
}
fn len(&self) -> usize {
self.jobs.len()
}
pub fn run(&self) {
if 0 == self.len() {
return;
}
let mut now = ticker::minute_sync();
while ! self.shutdown.load(Ordering::Relaxed) {
self.current_jobs(now).iter().for_each(|j| {
execute_job(j);
} );
now = ticker::sleep_sync();
}
}
pub fn shutdown(&self) {
self.shutdown.store(true, Ordering::Relaxed);
}
pub fn current_jobs(&self, now: DateTime<Local>) -> Vec<&Job> {
let mut current_jobs = vec![];
for job in &self.jobs {
if job.is_time(now) {
current_jobs.push(job);
}
}
current_jobs
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::process::Command;
use crate::config::Config;
use chrono::{Timelike, TimeZone};
use chrono::format::Fixed::TimezoneOffsetZ;
fn tz() -> String {
let output = Command::new("date").arg("+%:z").output().expect("failed to fetch timezone");
String::from(String::from_utf8_lossy(&output.stdout).trim())
}
fn test_time(iso8601: &str) -> DateTime<Local> {
let mut date_str = String::from(iso8601);
date_str.push_str(tz().as_str());
println!("date_str='{}'", date_str);
let dt = DateTime::parse_from_rfc3339(date_str.as_str()).unwrap().with_timezone(&Local);
println!("dt='{:?}'", dt);
dt
}
#[test]
fn test_minutes() {
let data = "# simple\n35 * * * * teknopaul /bin/foo arg";
let mut cfg = Config::read_lines(data.split('\n').map(|s| String::from(s)));
let cron = Cron::new(cfg.take_cron_jobs());
assert_eq!(1, cron.len());
assert_eq!(0, cron.current_jobs(test_time("2020-01-05T23:34:30")).len());
assert_eq!(1, cron.current_jobs(test_time("2020-01-05T23:35:30")).len());
assert_eq!(0, cron.current_jobs(test_time("2020-01-05T23:36:30")).len());
}
#[test]
fn test_days() {
let data = "# simple\n0 * 10 * * teknopaul /bin/foo arg";
let mut cfg = Config::read_lines(data.split('\n').map(|s| String::from(s)));
let cron = Cron::new(cfg.take_cron_jobs());
assert_eq!(1, cron.len());
assert_eq!(0, cron.current_jobs(test_time("2020-06-09T00:00:30")).len());
assert_eq!(1, cron.current_jobs(test_time("2020-06-10T00:00:30")).len());
assert_eq!(0, cron.current_jobs(test_time("2020-06-11T00:00:30")).len());
}
#[test]
fn test_months() {
let data = "# simple\n0 * * 6 * teknopaul /bin/foo arg";
let mut cfg = Config::read_lines(data.split('\n').map(|s| String::from(s)));
let cron = Cron::new(cfg.take_cron_jobs());
assert_eq!(1, cron.len());
assert_eq!(0, cron.current_jobs(test_time("2020-05-10T00:00:30")).len());
assert_eq!(1, cron.current_jobs(test_time("2020-06-10T00:00:30")).len());
assert_eq!(0, cron.current_jobs(test_time("2020-07-10T00:00:30")).len());
}
#[test]
fn test_parser_simple_cron_alias() {
let data = "# simple\n@daily teknopaul /bin/foo arg";
let mut cfg = Config::read_lines(data.split('\n').map(|s| String::from(s)));
let cron = Cron::new(cfg.take_cron_jobs());
assert_eq!(1, cron.len());
assert_eq!(String::from("/bin/foo"), cron.jobs[0].command().as_str());
assert_eq!(String::from("arg"), cron.jobs[0].args()[0].as_str());
}
}