use std::collections::HashMap;
use chrono::{DateTime, Utc};
use crate::schedule::compute_next_run;
use crate::types::{JobDefinition, JobState, JobUpdate};
pub struct CronStorage {
jobs: HashMap<String, JobDefinition>,
}
impl CronStorage {
pub fn new() -> Self {
Self {
jobs: HashMap::new(),
}
}
pub fn len(&self) -> usize {
self.jobs.len()
}
pub fn is_empty(&self) -> bool {
self.jobs.is_empty()
}
pub fn add_job(&mut self, job: JobDefinition) -> Result<(), String> {
if self.jobs.contains_key(&job.id) {
return Err(format!("Job already exists: {}", job.id));
}
self.jobs.insert(job.id.clone(), job);
Ok(())
}
pub fn get_job(&self, id: &str) -> Option<&JobDefinition> {
self.jobs.get(id)
}
pub fn get_job_mut(&mut self, id: &str) -> Option<&mut JobDefinition> {
self.jobs.get_mut(id)
}
pub fn update_job(&mut self, id: &str, updates: JobUpdate) -> Result<(), String> {
let job = self
.jobs
.get_mut(id)
.ok_or_else(|| format!("Job not found: {}", id))?;
let now = Utc::now();
if let Some(name) = updates.name {
job.name = name;
}
if let Some(desc) = updates.description {
job.description = desc;
}
if let Some(schedule) = updates.schedule {
job.schedule = schedule;
job.next_run = compute_next_run(&job.schedule, now);
}
if let Some(payload) = updates.payload {
job.payload = payload;
}
if let Some(state) = updates.state {
job.state = state;
}
if let Some(max_runs) = updates.max_runs {
job.max_runs = max_runs;
}
if let Some(room_id) = updates.room_id {
job.room_id = room_id;
}
job.updated_at = now;
Ok(())
}
pub fn delete_job(&mut self, id: &str) -> Result<bool, String> {
Ok(self.jobs.remove(id).is_some())
}
pub fn list_jobs(&self, filter: Option<JobState>) -> Vec<&JobDefinition> {
let mut result: Vec<&JobDefinition> = match filter {
Some(ref state) => self.jobs.values().filter(|j| j.state == *state).collect(),
None => self.jobs.values().collect(),
};
result.sort_by(|a, b| {
let a_next = a.next_run.unwrap_or(DateTime::<Utc>::MAX_UTC);
let b_next = b.next_run.unwrap_or(DateTime::<Utc>::MAX_UTC);
a_next.cmp(&b_next)
});
result
}
pub fn get_due_jobs(&self, now: DateTime<Utc>) -> Vec<&JobDefinition> {
let mut due: Vec<&JobDefinition> = self
.jobs
.values()
.filter(|j| {
j.state == JobState::Active
&& j.next_run.map(|nr| nr <= now).unwrap_or(false)
})
.collect();
due.sort_by_key(|j| j.next_run);
due
}
pub fn find_by_name(&self, name: &str) -> Option<&JobDefinition> {
let lower = name.to_lowercase();
self.jobs
.values()
.find(|j| j.name.to_lowercase() == lower)
}
}
impl Default for CronStorage {
fn default() -> Self {
Self::new()
}
}