use std::cmp::Ordering;
use std::str::FromStr;
use std::sync::Arc;
use chrono::{DateTime, Utc};
use thiserror::Error;
use shiny_common::clock::Clock;
use crate::job_trigger::JobTrigger;
pub struct CronTrigger {
schedule: saffron::Cron,
clock: Arc<dyn Clock>,
}
impl CronTrigger {
pub fn new(schedule: &str, clock: Arc<dyn Clock>) -> Result<Self, NewCronTriggerError> {
Ok(Self {
schedule: saffron::Cron::from_str(schedule).map_err(NewCronTriggerError)?,
clock,
})
}
}
#[async_trait::async_trait]
impl JobTrigger for CronTrigger {
async fn next(&self) {
let now = self.clock.now();
let next = self.schedule
.next_after(now)
.unwrap_or(DateTime::<Utc>::MAX_UTC);
let sleep_time = (next - now).max(chrono::Duration::zero())
.to_std()
.expect("duration should be at least zero");
tracing::debug!("next scheduled launch at {}, waiting {}ms", next, sleep_time.as_millis());
tokio::time::sleep(sleep_time).await;
}
}
#[derive(Debug, Error)]
#[error(transparent)]
pub struct NewCronTriggerError(saffron::parse::CronParseError);
#[derive(Clone, Debug, Eq, PartialEq)]
struct ScheduledJobRun {
run_at: DateTime<Utc>,
identifier: String,
}
impl PartialOrd<Self> for ScheduledJobRun {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ScheduledJobRun {
fn cmp(&self, other: &Self) -> Ordering {
self.run_at.cmp(&other.run_at)
}
}