wei_job_scheduler/lib.rs
1//! # JobScheduler
2//!
3//! A simple cron-like job scheduling library for Rust.
4//!
5//! ## Usage
6//!
7//! Be sure to add the job_scheduler crate to your `Cargo.toml`:
8//!
9//! ```toml
10//! [dependencies]
11//! job_scheduler = "*"
12//! ```
13//!
14//! Creating a schedule for a job is done using the `FromStr` impl for the
15//! `Schedule` type of the [cron](https://github.com/zslayton/cron) library.
16//!
17//! The scheduling format is as follows:
18//!
19//! ```text
20//! sec min hour day of month month day of week year
21//! * * * * * * *
22//! ```
23//!
24//! Note that the year may be omitted.
25//!
26//! Comma separated values such as `5,8,10` represent more than one time
27//! value. So for example, a schedule of `0 2,14,26 * * * *` would execute
28//! on the 2nd, 14th, and 26th minute of every hour.
29//!
30//! Ranges can be specified with a dash. A schedule of `0 0 * 5-10 * *`
31//! would execute once per hour but only on day 5 through 10 of the month.
32//!
33//! Day of the week can be specified as an abbreviation or the full name.
34//! A schedule of `0 0 6 * * Sun,Sat` would execute at 6am on Sunday and
35//! Saturday.
36//!
37//! A simple usage example:
38//!
39//! ```rust,ignore
40//! extern crate job_scheduler;
41//! use job_scheduler::{JobScheduler, Job};
42//! use std::time::Duration;
43//!
44//! fn main() {
45//! let mut sched = JobScheduler::new();
46//!
47//! sched.add(Job::new("1/10 * * * * *".parse().unwrap(), || {
48//! println!("I get executed every 10 seconds!");
49//! }));
50//!
51//! loop {
52//! sched.tick();
53//!
54//! std::thread::sleep(Duration::from_millis(500));
55//! }
56//! }
57//! ```
58
59extern crate chrono;
60extern crate cron;
61extern crate uuid;
62
63use chrono::{offset, DateTime, Duration, Utc};
64pub use cron::Schedule;
65pub use uuid::Uuid;
66
67/// A schedulable `Job`.
68pub struct Job<'a> {
69 schedule: Schedule,
70 run: Box<dyn (FnMut() -> ()) + 'a>,
71 last_tick: Option<DateTime<Utc>>,
72 limit_missed_runs: usize,
73 job_id: Uuid,
74}
75
76impl<'a> Job<'a> {
77 /// Create a new job.
78 ///
79 /// ```rust,ignore
80 /// // Run at second 0 of the 15th minute of the 6th, 8th, and 10th hour
81 /// // of any day in March and June that is a Friday of the year 2017.
82 /// let s: Schedule = "0 15 6,8,10 * Mar,Jun Fri 2017".into().unwrap();
83 /// Job::new(s, || println!("I have a complex schedule...") );
84 /// ```
85 pub fn new<T>(schedule: Schedule, run: T) -> Job<'a>
86 where
87 T: 'a,
88 T: FnMut() -> (),
89 {
90 Job {
91 schedule,
92 run: Box::new(run),
93 last_tick: None,
94 limit_missed_runs: 1,
95 job_id: Uuid::new_v4(),
96 }
97 }
98
99 fn tick(&mut self) {
100 let now = Utc::now();
101 if self.last_tick.is_none() {
102 self.last_tick = Some(now);
103 return;
104 }
105 if self.limit_missed_runs > 0 {
106 for event in self
107 .schedule
108 .after(&self.last_tick.unwrap())
109 .take(self.limit_missed_runs)
110 {
111 if event > now {
112 break;
113 }
114 (self.run)();
115 }
116 } else {
117 for event in self.schedule.after(&self.last_tick.unwrap()) {
118 if event > now {
119 break;
120 }
121 (self.run)();
122 }
123 }
124
125 self.last_tick = Some(now);
126 }
127
128 /// Set the limit for missed jobs in the case of delayed runs. Setting to 0 means unlimited.
129 ///
130 /// ```rust,ignore
131 /// let mut job = Job::new("0/1 * * * * *".parse().unwrap(), || {
132 /// println!("I get executed every 1 seconds!");
133 /// });
134 /// job.limit_missed_runs(99);
135 /// ```
136 pub fn limit_missed_runs(&mut self, limit: usize) {
137 self.limit_missed_runs = limit;
138 }
139
140 /// Set last tick to force re-running of missed runs.
141 ///
142 /// ```rust,ignore
143 /// let mut job = Job::new("0/1 * * * * *".parse().unwrap(), || {
144 /// println!("I get executed every 1 seconds!");
145 /// });
146 /// job.last_tick(Some(Utc::now()));
147 /// ```
148 pub fn last_tick(&mut self, last_tick: Option<DateTime<Utc>>) {
149 self.last_tick = last_tick;
150 }
151}
152
153#[derive(Default)]
154/// The JobScheduler contains and executes the scheduled jobs.
155pub struct JobScheduler<'a> {
156 jobs: Vec<Job<'a>>,
157}
158
159impl<'a> JobScheduler<'a> {
160 /// Create a new `JobScheduler`.
161 pub fn new() -> JobScheduler<'a> {
162 JobScheduler { jobs: Vec::new() }
163 }
164
165 /// Add a job to the `JobScheduler`
166 ///
167 /// ```rust,ignore
168 /// let mut sched = JobScheduler::new();
169 /// sched.add(Job::new("1/10 * * * * *".parse().unwrap(), || {
170 /// println!("I get executed every 10 seconds!");
171 /// }));
172 /// ```
173 pub fn add(&mut self, job: Job<'a>) -> Uuid {
174 let job_id = job.job_id;
175 self.jobs.push(job);
176
177 job_id
178 }
179
180 /// Remove a job from the `JobScheduler`
181 ///
182 /// ```rust,ignore
183 /// let mut sched = JobScheduler::new();
184 /// let job_id = sched.add(Job::new("1/10 * * * * *".parse().unwrap(), || {
185 /// println!("I get executed every 10 seconds!");
186 /// }));
187 /// sched.remove(job_id);
188 /// ```
189 pub fn remove(&mut self, job_id: Uuid) -> bool {
190 let mut found_index = None;
191 for (i, job) in self.jobs.iter().enumerate() {
192 if job.job_id == job_id {
193 found_index = Some(i);
194 break;
195 }
196 }
197
198 if found_index.is_some() {
199 self.jobs.remove(found_index.unwrap());
200 }
201
202 found_index.is_some()
203 }
204
205 /// The `tick` method increments time for the JobScheduler and executes
206 /// any pending jobs. It is recommended to sleep for at least 500
207 /// milliseconds between invocations of this method.
208 ///
209 /// ```rust,ignore
210 /// loop {
211 /// sched.tick();
212 /// std::thread::sleep(Duration::from_millis(500));
213 /// }
214 /// ```
215 pub fn tick(&mut self) {
216 for job in &mut self.jobs {
217 job.tick();
218 }
219 }
220
221 /// The `time_till_next_job` method returns the duration till the next job
222 /// is supposed to run. This can be used to sleep until then without waking
223 /// up at a fixed interval.AsMut
224 ///
225 /// ```rust, ignore
226 /// loop {
227 /// sched.tick();
228 /// std::thread::sleep(sched.time_till_next_job());
229 /// }
230 /// ```
231 pub fn time_till_next_job(&self) -> std::time::Duration {
232 if self.jobs.is_empty() {
233 // Take a guess if there are no jobs.
234 return std::time::Duration::from_millis(500);
235 }
236 let mut duration = Duration::zero();
237 let now = Utc::now();
238 for job in self.jobs.iter() {
239 for event in job.schedule.upcoming(offset::Utc).take(1) {
240 let d = event - now;
241 if duration.is_zero() || d < duration {
242 duration = d;
243 }
244 }
245 }
246 duration.to_std().unwrap()
247 }
248}