1use std::collections::{HashMap, HashSet};
44use std::hash::Hash;
45use std::sync::Arc;
46
47use tokio::sync::RwLock;
48
49pub trait Schedulable {
56 type Id: Eq + Hash + Into<i64> + Copy;
58
59 type Interval: Eq + Hash + Into<i64> + Copy;
61
62 fn get_id(&self) -> Self::Id;
64
65 fn get_interval(&self) -> Self::Interval;
67}
68
69pub struct Schedule<Item: Schedulable> {
85 items: RwLock<HashMap<Item::Id, Arc<Item>>>,
86 intervals: RwLock<HashMap<Item::Interval, HashSet<Item::Id>>>,
87}
88
89impl<Item: Schedulable> Schedule<Item> {
90 pub fn new() -> Self {
92 Self {
93 items: RwLock::new(HashMap::new()),
94 intervals: RwLock::new(HashMap::new()),
95 }
96 }
97
98 pub async fn is_empty(&self) -> bool {
100 self.items.read().await.is_empty() && self.intervals.read().await.is_empty()
101 }
102
103 pub async fn get(&self, id: Item::Id) -> Option<Arc<Item>> {
105 self.items.read().await.get(&id).cloned()
106 }
107
108 pub async fn get_due(&self, from: i64, to: i64) -> Vec<Arc<Item>> {
116 let mut result = Vec::new();
117 let intervals = self.intervals.read().await;
118
119 for (interval, ids) in intervals.iter() {
120 let interval = (*interval).into();
121 let next_check = ((from + interval - 1) / interval) * interval;
122
123 if next_check <= to {
124 let guard = self.items.read().await;
125
126 for id in ids {
127 if let Some(item) = guard.get(id) {
128 result.push(item.clone());
129 }
130 }
131 }
132 }
133
134 result
135 }
136
137 pub async fn insert(&self, item: Item) {
141 let id = item.get_id();
142 let interval = item.get_interval();
143
144 {
145 let mut intervals = self.intervals.write().await;
146
147 if let Some(ids_set) = intervals.get_mut(&interval) {
148 ids_set.insert(id);
149 } else {
150 let mut set = HashSet::new();
151 set.insert(id);
152
153 intervals.insert(interval, set);
154 }
155 }
156
157 {
158 let mut items = self.items.write().await;
159
160 items.insert(id, Arc::new(item));
161 }
162 }
163
164 pub async fn remove(&self, id: Item::Id) {
166 if let Some(item) = self.items.write().await.remove(&id) {
167 let interval = item.get_interval();
168 let mut intervals = self.intervals.write().await;
169
170 if let Some(set) = intervals.get_mut(&interval) {
171 if set.remove(&id) && set.is_empty() {
172 intervals.remove(&interval);
173 }
174 }
175 }
176 }
177
178 pub async fn clear(&self) {
181 self.items.write().await.clear();
182 self.intervals.write().await.clear();
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use tokio::sync::RwLockReadGuard;
189
190 use super::*;
191
192 #[derive(Debug, PartialEq)]
193 struct Task {
194 id: i64,
195 interval: i64,
196 updated: bool,
197 }
198
199 impl<Item: Schedulable> Schedule<Item> {
200 pub async fn items_ref(&self) -> RwLockReadGuard<'_, HashMap<Item::Id, Arc<Item>>> {
201 self.items.read().await
202 }
203
204 pub async fn intervals_ref(
205 &self,
206 ) -> RwLockReadGuard<'_, HashMap<Item::Interval, HashSet<Item::Id>>> {
207 self.intervals.read().await
208 }
209 }
210
211 impl From<(i64, i64)> for Task {
212 fn from(args: (i64, i64)) -> Self {
213 Task {
214 id: args.0,
215 interval: args.1,
216 updated: false,
217 }
218 }
219 }
220
221 impl Schedulable for Task {
222 type Id = i64;
223 type Interval = i64;
224
225 fn get_id(&self) -> Self::Id {
226 self.id
227 }
228
229 fn get_interval(&self) -> Self::Interval {
230 self.interval
231 }
232 }
233
234 #[tokio::test]
235 async fn empty_schedule() {
236 let schedule: Schedule<Task> = Schedule::new();
237
238 assert!(
239 schedule.items_ref().await.is_empty(),
240 "schedule items shouldn't be empty"
241 );
242 assert!(
243 schedule.intervals_ref().await.is_empty(),
244 "schedule intervals shouldn't be empty"
245 );
246 }
247
248 #[tokio::test]
249 async fn test_empty_schedule() {
250 let schedule: Schedule<Task> = Schedule::new();
251
252 assert!(
253 schedule.get_due(1, 100).await.is_empty(),
254 "empty schedule shouldn't return due items"
255 );
256 }
257
258 #[tokio::test]
259 async fn get_due_on_boundary() {
260 let schedule: Schedule<Task> = Schedule::new();
261
262 schedule.insert(Task::from((1, 10))).await;
263
264 assert_eq!(
265 schedule.get_due(1, 10).await.len(),
266 1,
267 "schedule should return item on boundary"
268 );
269 assert_eq!(
270 schedule.get_due(10, 10).await.len(),
271 1,
272 "schedule should return item on boundary equals"
273 );
274 }
275
276 #[tokio::test]
277 async fn get_due_before_boundary() {
278 let schedule: Schedule<Task> = Schedule::new();
279
280 schedule.insert(Task::from((1, 10))).await;
281
282 assert!(
283 schedule.get_due(1, 9).await.is_empty(),
284 "schedule shouldn't return due items before boundary"
285 );
286 }
287
288 #[tokio::test]
289 async fn test_multiple_intervals() {
290 let schedule: Schedule<Task> = Schedule::new();
291
292 schedule.insert(Task::from((1, 5))).await;
293 schedule.insert(Task::from((2, 10))).await;
294
295 let ids: Vec<i64> = schedule.get_due(1, 10).await.iter().map(|t| t.id).collect();
296
297 assert!(
298 ids.contains(&1),
299 "schedule should return item with interval 5"
300 );
301 assert!(
302 ids.contains(&2),
303 "schedule should return item with interval 10"
304 );
305 }
306
307 #[tokio::test]
308 async fn test_skip_multiple_intervals() {
309 let schedule: Schedule<Task> = Schedule::new();
310
311 schedule.insert(Task::from((1, 10))).await;
312
313 assert_eq!(
314 schedule.get_due(1, 35).await.len(),
315 1,
316 "schedule should return due item even if multiple intervals were passed"
317 );
318 }
319
320 #[tokio::test]
321 async fn insert_single_item_into_schedule() {
322 let schedule: Schedule<Task> = Schedule::new();
323
324 schedule.insert(Task::from((1, 30))).await;
325
326 assert!(
327 schedule.items_ref().await.contains_key(&1),
328 "schedule items should contain entry"
329 );
330 assert!(
331 schedule.intervals_ref().await.contains_key(&30),
332 "schedule intervals should contain entry"
333 );
334 assert_eq!(
335 schedule.get(1).await,
336 Some(Arc::new(Task::from((1, 30)))),
337 "schedule should return entry by id"
338 );
339 }
340
341 #[tokio::test]
342 async fn insert_multiple_items_into_schedule() {
343 let schedule: Schedule<Task> = Schedule::new();
344
345 schedule.insert(Task::from((1, 30))).await;
346 schedule.insert(Task::from((2, 30))).await;
347
348 assert!(
349 schedule.items_ref().await.contains_key(&1),
350 "schedule items should contain entry"
351 );
352 assert!(
353 schedule.items_ref().await.contains_key(&2),
354 "schedule items should contain entry"
355 );
356 assert!(
357 schedule.intervals_ref().await.contains_key(&30),
358 "schedule intervals should contain entry"
359 );
360 assert_eq!(
361 schedule.get(1).await,
362 Some(Arc::new(Task::from((1, 30)))),
363 "schedule should return entry by id"
364 );
365 assert_eq!(
366 schedule.get(2).await,
367 Some(Arc::new(Task::from((2, 30)))),
368 "schedule should return entry by id"
369 );
370 }
371
372 #[tokio::test]
373 async fn insert_the_sane_item_twice() {
374 let schedule: Schedule<Task> = Schedule::new();
375
376 schedule.insert(Task::from((1, 30))).await;
377 schedule.insert(Task::from((1, 30))).await;
378
379 assert_eq!(
380 schedule.items_ref().await.len(),
381 1,
382 "schedule items shouldn't be empty"
383 );
384 assert_eq!(
385 schedule.intervals_ref().await.len(),
386 1,
387 "schedule intervals shouldn't be empty"
388 );
389 }
390
391 #[tokio::test]
392 async fn remove_item_from_schedule() {
393 let schedule: Schedule<Task> = Schedule::new();
394
395 schedule.insert(Task::from((1, 30))).await;
396 schedule.remove(1).await;
397
398 assert!(
399 schedule.items_ref().await.is_empty(),
400 "schedule items should be empty"
401 );
402 assert!(
403 schedule.intervals_ref().await.is_empty(),
404 "schedule intervals should be empty"
405 );
406 }
407
408 #[tokio::test]
409 async fn clear() {
410 let schedule: Schedule<Task> = Schedule::new();
411
412 schedule.insert(Task::from((1, 10))).await;
413 schedule.insert(Task::from((2, 20))).await;
414
415 assert!(!schedule.is_empty().await, "schedule shouldn't be empty");
416
417 schedule.clear().await;
418 assert!(schedule.is_empty().await, "schedule should be empty");
419 }
420}