zino_core/schedule/
async_job.rs1use super::{AsyncScheduler, DEFAULT_TICK_INTERVAL, JobContext};
4use crate::{BoxFuture, Uuid, datetime::DateTime, extension::TomlTableExt};
5use chrono::Local;
6use cron::Schedule;
7use std::{io, str::FromStr, time::Duration};
8use toml::Table;
9
10pub type AsyncCronJob = for<'a> fn(ctx: &'a mut JobContext) -> BoxFuture<'a>;
12
13pub struct AsyncJob {
15 context: JobContext,
17 schedule: Schedule,
19 run: AsyncCronJob,
21}
22
23impl AsyncJob {
24 #[inline]
30 pub fn new(cron_expr: &str, exec: AsyncCronJob) -> Self {
31 let schedule = Schedule::from_str(cron_expr)
32 .unwrap_or_else(|err| panic!("invalid cron expression `{cron_expr}`: {err}"));
33 let mut context = JobContext::new();
34 context.set_source(cron_expr);
35 Self {
36 context,
37 schedule,
38 run: exec,
39 }
40 }
41
42 pub fn with_config(config: &Table, exec: AsyncCronJob) -> Self {
48 let cron_expr = config.get_str("cron").unwrap_or_default();
49 let schedule = Schedule::from_str(cron_expr)
50 .unwrap_or_else(|err| panic!("invalid cron expression `{cron_expr}`: {err}"));
51 let mut context = JobContext::new();
52 if let Some(disabled) = config.get_bool("disable") {
53 context.set_disabled_status(disabled);
54 }
55 if let Some(immediate) = config.get_bool("immediate") {
56 context.set_immediate_mode(immediate);
57 }
58 if let Some(ticks) = config
59 .get_bool("once")
60 .and_then(|b| b.then_some(1))
61 .or_else(|| config.get_usize("max-ticks"))
62 {
63 context.set_remaining_ticks(ticks);
64 }
65 Self {
66 context,
67 schedule,
68 run: exec,
69 }
70 }
71
72 #[inline]
74 pub fn name(mut self, name: &'static str) -> Self {
75 self.context.set_name(name);
76 self
77 }
78
79 #[inline]
81 pub fn data<T: Send + 'static>(mut self, data: T) -> Self {
82 self.context.set_data(data);
83 self
84 }
85
86 #[inline]
88 pub fn max_ticks(mut self, ticks: usize) -> Self {
89 self.context.set_remaining_ticks(ticks);
90 self
91 }
92
93 #[inline]
95 pub fn once(mut self) -> Self {
96 self.context.set_remaining_ticks(1);
97 self
98 }
99
100 #[inline]
102 pub fn disable(mut self, disabled: bool) -> Self {
103 self.context.set_disabled_status(disabled);
104 self
105 }
106
107 #[inline]
109 pub fn immediate(mut self, immediate: bool) -> Self {
110 self.context.set_immediate_mode(immediate);
111 self
112 }
113
114 #[inline]
116 pub fn pause(&mut self) {
117 self.context.set_disabled_status(true);
118 }
119
120 #[inline]
122 pub fn resume(&mut self) {
123 self.context.set_disabled_status(false);
124 }
125
126 pub async fn tick(&mut self) {
128 let now = Local::now();
129 let upcoming = self.upcoming();
130 let ctx = &mut self.context;
131 let run = self.run;
132 if ctx.is_immediate() && !ctx.is_disabled() && !ctx.is_fused() {
133 ctx.start();
134 ctx.set_next_tick(upcoming);
135 run(ctx).await;
136 ctx.finish();
137 } else if let Some(last_tick) = ctx.last_tick().map(|dt| dt.into()) {
138 for event in self.schedule.after(&last_tick) {
139 if event > now || ctx.is_fused() {
140 break;
141 }
142 if !ctx.is_disabled() {
143 ctx.start();
144 ctx.set_next_tick(upcoming);
145 run(ctx).await;
146 ctx.finish();
147 }
148 }
149 } else {
150 ctx.set_last_tick(now.into());
151 }
152 }
153
154 pub async fn execute(&mut self) {
156 let upcoming = self.upcoming();
157 let ctx = &mut self.context;
158 let run = self.run;
159 ctx.start();
160 ctx.set_next_tick(upcoming);
161 run(ctx).await;
162 ctx.finish();
163 }
164
165 #[inline]
167 pub fn context(&self) -> &JobContext {
168 &self.context
169 }
170
171 #[inline]
173 pub fn context_mut(&mut self) -> &mut JobContext {
174 &mut self.context
175 }
176
177 #[inline]
179 pub fn upcoming(&self) -> Option<DateTime> {
180 self.schedule.upcoming(Local).next().map(|dt| dt.into())
181 }
182}
183
184#[derive(Default)]
186pub struct AsyncJobScheduler {
187 jobs: Vec<AsyncJob>,
189}
190
191impl AsyncJobScheduler {
192 #[inline]
194 pub fn new() -> Self {
195 Self { jobs: Vec::new() }
196 }
197
198 #[inline]
200 pub fn add(&mut self, job: AsyncJob) -> Uuid {
201 let job_id = job.context().job_id();
202 self.jobs.push(job);
203 job_id
204 }
205
206 pub fn remove(&mut self, job_id: Uuid) -> bool {
208 let position = self
209 .jobs
210 .iter()
211 .position(|job| job.context().job_id() == job_id);
212 if let Some(index) = position {
213 self.jobs.remove(index);
214 true
215 } else {
216 false
217 }
218 }
219
220 #[inline]
222 pub fn get(&self, job_id: Uuid) -> Option<&AsyncJob> {
223 self.jobs
224 .iter()
225 .find(|job| job.context().job_id() == job_id)
226 }
227
228 #[inline]
230 pub fn get_mut(&mut self, job_id: Uuid) -> Option<&mut AsyncJob> {
231 self.jobs
232 .iter_mut()
233 .find(|job| job.context().job_id() == job_id)
234 }
235
236 pub fn time_till_next_job(&self) -> Duration {
238 if self.jobs.is_empty() {
239 DEFAULT_TICK_INTERVAL
240 } else {
241 let mut duration = Duration::ZERO;
242 let now = Local::now();
243 for job in self.jobs.iter() {
244 if let Some(interval) = job
245 .context()
246 .next_tick()
247 .and_then(|dt| dt.span_after_now())
248 .filter(|interval| duration.is_zero() || interval < &duration)
249 {
250 duration = interval;
251 }
252 for event in job.schedule.after(&now).take(1) {
253 let interval = event - now;
254 if let Ok(interval) = interval.to_std()
255 && (duration.is_zero() || interval < duration)
256 {
257 duration = interval;
258 }
259 }
260 }
261 duration.max(DEFAULT_TICK_INTERVAL)
262 }
263 }
264
265 pub async fn tick(&mut self) {
268 let mut fused_jobs = Vec::new();
269 for job in &mut self.jobs {
270 job.tick().await;
271
272 let ctx = job.context();
273 if ctx.is_fused() {
274 fused_jobs.push(ctx.job_id());
275 }
276 }
277 for job_id in fused_jobs {
278 self.remove(job_id);
279 }
280 }
281
282 #[inline]
284 pub async fn execute(&mut self) {
285 for job in &mut self.jobs {
286 job.execute().await;
287 }
288 }
289}
290
291impl AsyncScheduler for AsyncJobScheduler {
292 #[inline]
293 fn is_ready(&self) -> bool {
294 !self.jobs.is_empty()
295 }
296
297 #[inline]
298 fn is_blocking(&self) -> bool {
299 false
300 }
301
302 #[inline]
303 fn time_till_next_job(&self) -> Option<Duration> {
304 Some(self.time_till_next_job())
305 }
306
307 #[inline]
308 async fn tick(&mut self) {
309 self.tick().await;
310 }
311
312 #[inline]
313 async fn run(self) -> io::Result<()> {
314 Ok(())
315 }
316}