shinyframework_jobs 0.1.2

Shiny Jobs
Documentation
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)
    }
}