mod job_store;
mod threading;
use crate::job_store::JobStore;
use crate::threading::SchedulerThread;
use std::fmt::Debug;
use std::num::NonZeroUsize;
use std::sync::Arc;
use std::time::{Duration, SystemTime};
pub struct Scheduler {
job_store: Arc<JobStore>,
scheduler_thread: SchedulerThread,
}
pub struct SchedulerBuilder {
workers: NonZeroUsize,
job_store: JobStore,
}
impl SchedulerBuilder {
pub fn with_workers(self, workers: NonZeroUsize) -> Self {
Self { workers, ..self }
}
pub fn build(self) -> Scheduler {
let job_store = Arc::new(self.job_store);
Scheduler {
job_store: job_store.clone(),
scheduler_thread: SchedulerThread::new(self.workers, job_store),
}
}
}
impl Scheduler {
pub fn builder() -> SchedulerBuilder {
SchedulerBuilder {
workers: NonZeroUsize::new(1).unwrap(),
job_store: JobStore::new(),
}
}
pub fn new() -> Self {
let job_store = Arc::new(JobStore::new());
let scheduler_thread = SchedulerThread::new(NonZeroUsize::new(2).unwrap(), Arc::clone(&job_store));
Self {
job_store,
scheduler_thread,
}
}
pub fn schedule_job(&self, job: Job, trigger: Trigger) {
self.job_store.add(job, trigger);
}
pub fn shutdown(self) {
self.scheduler_thread.shutdown();
}
}
impl Default for Scheduler {
fn default() -> Self {
Self::new()
}
}
pub struct Job {
id: String,
group: String,
target_fn: Box<dyn Fn() + Send + Sync>,
}
impl Debug for Job {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
write!(fmt, "Job {}::{}", self.group, self.id)
}
}
impl Job {
pub fn with_identity<S: Into<String>>(id: S, group: S, target: impl Fn() + Send + Sync + 'static) -> Self {
Self {
id: id.into(),
group: group.into(),
target_fn: Box::new(target),
}
}
pub fn id(&self) -> &str {
&self.id
}
pub fn group(&self) -> &str {
&self.group
}
pub fn execute(&self) {
(self.target_fn)();
}
}
impl PartialEq for Job {
fn eq(&self, other: &Self) -> bool {
self.id.eq(&other.id) && self.group.eq(&other.group)
}
}
#[derive(Debug, PartialEq)]
pub struct Trigger {
id: String,
group: String,
start_time: Option<SystemTime>,
end_time: Option<SystemTime>,
interval: Option<Duration>,
repeat_count: Option<u32>,
}
impl Trigger {
pub fn with_identity<S: Into<String>>(id: S, group: S) -> Self {
Self {
id: id.into(),
group: group.into(),
start_time: None,
end_time: None,
interval: None,
repeat_count: None,
}
}
pub fn start_at(self, start_time: SystemTime) -> Self {
Self {
start_time: Some(start_time),
..self
}
}
pub fn end_at(self, end_time: SystemTime) -> Self {
Self {
end_time: Some(end_time),
..self
}
}
pub fn every(self, interval: Duration) -> Self {
Self {
interval: Some(interval),
..self
}
}
pub fn repeat(self, count: u32) -> Self {
Self {
repeat_count: Some(count),
..self
}
}
pub fn next_fire(&self) -> SystemTime {
self.start_time.unwrap_or_else(SystemTime::now)
}
}
#[cfg(test)]
mod tests {
use crate::{Job, Scheduler, Trigger};
use std::thread;
use std::time::{Duration, SystemTime};
const JOB_ID: &str = "job1";
#[test]
fn test_basic_api() {
let sched = Scheduler::builder().with_workers(1.try_into().unwrap()).build();
let run_time = SystemTime::now() + Duration::from_millis(600);
println!("------- Scheduling Job -------------------");
let job = Job::with_identity(JOB_ID, "group1", || println!("Hello, world from {JOB_ID}!"));
let trigger = Trigger::with_identity("trigger1", "group1")
.start_at(run_time)
.repeat(2)
.every(Duration::from_millis(100));
sched.schedule_job(job, trigger);
println!("{JOB_ID} will run at: {run_time:?}");
println!("------- Waiting 1 second... -------------");
thread::sleep(Duration::from_secs(1));
println!("------- Shutting Down ---------------------");
sched.shutdown();
}
}