use super::Scheduler;
use crate::{datetime::DateTime, Map, Uuid};
use chrono::Local;
use cron::Schedule;
use std::{str::FromStr, time::Duration};
pub type CronJob = fn(id: Uuid, data: &mut Map, last_tick: DateTime);
pub struct Job {
id: Uuid,
data: Map,
disabled: bool,
immediate: bool,
schedule: Schedule,
run: CronJob,
last_tick: Option<chrono::DateTime<Local>>,
}
impl Job {
#[inline]
pub fn new(cron_expr: &str, exec: CronJob) -> Self {
let schedule = Schedule::from_str(cron_expr)
.unwrap_or_else(|err| panic!("invalid cron expression `{cron_expr}`: {err}"));
Self {
id: Uuid::now_v7(),
data: Map::new(),
disabled: false,
immediate: false,
schedule,
run: exec,
last_tick: None,
}
}
#[inline]
pub fn disable(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
#[inline]
pub fn immediate(mut self, immediate: bool) -> Self {
self.immediate = immediate;
self
}
#[inline]
pub fn id(&self) -> Uuid {
self.id
}
#[inline]
pub fn data(&self) -> &Map {
&self.data
}
#[inline]
pub fn data_mut(&mut self) -> &mut Map {
&mut self.data
}
#[inline]
pub fn is_disabled(&self) -> bool {
self.disabled
}
#[inline]
pub fn is_immediate(&self) -> bool {
self.immediate
}
#[inline]
pub fn pause(&mut self) {
self.disabled = true;
}
#[inline]
pub fn resume(&mut self) {
self.disabled = false;
}
#[inline]
pub fn set_last_tick(&mut self, last_tick: Option<DateTime>) {
self.last_tick = last_tick.map(|dt| dt.into());
}
pub fn tick(&mut self) {
let now = Local::now();
let disabled = self.disabled;
let run = self.run;
if let Some(last_tick) = self.last_tick {
for event in self.schedule.after(&last_tick) {
if event > now {
break;
}
if !disabled {
run(self.id, &mut self.data, last_tick.into());
}
}
} else if !disabled && self.immediate {
run(self.id, &mut self.data, now.into());
}
self.last_tick = Some(now);
}
pub fn execute(&mut self) {
let now = Local::now();
let run = self.run;
run(self.id, &mut self.data, now.into());
self.last_tick = Some(now);
}
}
#[derive(Default)]
pub struct JobScheduler {
jobs: Vec<Job>,
}
impl JobScheduler {
#[inline]
pub fn new() -> Self {
Self { jobs: Vec::new() }
}
pub fn add(&mut self, job: Job) -> Uuid {
let job_id = job.id;
self.jobs.push(job);
job_id
}
pub fn remove(&mut self, job_id: Uuid) -> bool {
let position = self.jobs.iter().position(|job| job.id == job_id);
if let Some(index) = position {
self.jobs.remove(index);
true
} else {
false
}
}
#[inline]
pub fn get(&self, job_id: Uuid) -> Option<&Job> {
self.jobs.iter().find(|job| job.id == job_id)
}
#[inline]
pub fn get_mut(&mut self, job_id: Uuid) -> Option<&mut Job> {
self.jobs.iter_mut().find(|job| job.id == job_id)
}
pub fn time_till_next_job(&self) -> Duration {
if self.jobs.is_empty() {
Duration::from_millis(500)
} else {
let mut duration = chrono::Duration::zero();
let now = Local::now();
for job in self.jobs.iter() {
for event in job.schedule.after(&now).take(1) {
let interval = event - now;
if duration.is_zero() || interval < duration {
duration = interval;
}
}
}
duration
.to_std()
.unwrap_or_else(|_| Duration::from_millis(500))
}
}
#[inline]
pub fn tick(&mut self) {
for job in &mut self.jobs {
job.tick();
}
}
pub async fn execute(&mut self) {
for job in &mut self.jobs {
job.execute();
}
}
}
impl Scheduler for JobScheduler {
#[inline]
fn is_ready(&self) -> bool {
!self.jobs.is_empty()
}
#[inline]
fn time_till_next_job(&self) -> Duration {
self.time_till_next_job()
}
#[inline]
fn tick(&mut self) {
self.tick();
}
}