librecollect/
event.rs

1use chrono::{DateTime, Utc};
2use cron::Schedule;
3use notify_rust::Notification;
4use serde::{Deserialize, Serialize};
5use std::{fmt::Display, str::FromStr};
6
7use crate::RecollectError as Error;
8
9#[derive(Serialize, Deserialize, Debug)]
10pub struct Event {
11    schedule: String,
12    pub summary: String,
13    pub body: String,
14    pub disabled: bool,
15    pub upcoming: Option<DateTime<Utc>>,
16}
17
18#[derive(Debug)]
19pub struct Summary<'a> {
20    schedule: &'a str,
21    summary: &'a str,
22}
23
24impl Display for Event {
25    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26        write!(
27            f,
28            "Schedule: {schedule}\nSummary: {summary}\nBody: {body}\nDisabled: {disabled}",
29            schedule = self.schedule,
30            summary = self.summary,
31            body = self.body,
32            disabled = self.disabled,
33        )
34    }
35}
36
37impl<'a> Display for Summary<'a> {
38    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39        write!(
40            f,
41            "Schedule: {schedule}, Summary: {summary}",
42            schedule = self.schedule,
43            summary = self.summary,
44        )
45    }
46}
47
48impl PartialEq for Event {
49    fn eq(&self, other: &Self) -> bool {
50        self.schedule == other.schedule && self.summary == other.summary && self.body == other.body
51    }
52}
53
54/// Test if the schedule string is valid in cron format.
55pub fn validate_schedule<Sched: AsRef<str>>(schedule: Sched) -> Result<(), Error> {
56    let schedule = schedule.as_ref();
57
58    Schedule::from_str(schedule).map_err(|_| Error::ParseSchedError(schedule.to_owned()))?;
59
60    Ok(())
61}
62
63impl Event {
64    /// Creates a new event.
65    ///
66    /// # Errors
67    ///
68    /// When the schedule string is invalid in cron format, `RecollectError::ParseSchedError` will
69    /// be returned.
70    pub fn new<Sched, Sum, Body>(
71        schedule: Sched,
72        summary: Sum,
73        body: Body,
74        disabled: bool,
75    ) -> Result<Self, Error>
76    where
77        Sched: Into<String>,
78        Sum: Into<String>,
79        Body: Into<String>,
80    {
81        let schedule = schedule.into();
82        validate_schedule(&schedule)?;
83
84        Ok(Self {
85            schedule,
86            summary: summary.into(),
87            body: body.into(),
88            disabled,
89            upcoming: None,
90        })
91    }
92
93    /// Returns the cron formatted `Schedule` object.
94    pub fn schedule(&self) -> Schedule {
95        // Should not panic as we've already validated the schedule string.
96        Schedule::from_str(&self.schedule).unwrap()
97    }
98
99    /// Returns the next time the notification should be sent.
100    ///
101    /// If `self.upcoming` is None, this will return the next approaching time and update the
102    /// `upcoming` field, otherwise it will return the time stored in `self.upcoming` to show the
103    /// missed time.
104    pub fn upcoming(&mut self) -> DateTime<Utc> {
105        self.upcoming.unwrap_or_else(|| self.update_upcoming())
106    }
107
108    /// Returns the timeline of upcoming events
109    pub fn upcoming_timeline(&self, n: usize) -> Vec<DateTime<Utc>> {
110        self.schedule().upcoming(Utc).take(n).collect()
111    }
112
113    /// Updates the upcoming time the notification should be sent and returns it.
114    pub fn update_upcoming(&mut self) -> DateTime<Utc> {
115        let upcoming = self.schedule().upcoming(Utc).take(1).next().unwrap();
116        self.upcoming = Some(upcoming);
117        upcoming
118    }
119
120    /// Returns the notification to be sent.
121    pub fn notification(&self) -> Notification {
122        Notification::new()
123            .summary(&self.summary)
124            .body(&self.body)
125            .finalize()
126    }
127
128    /// Validates the schedule string and update the schedule if it is valid.
129    ///
130    /// # Errors
131    ///
132    /// When the schedule string is invalid in cron format, `RecollectError::ParseSchedError` will
133    /// be returned.
134    pub fn update_schedule<S: Into<String>>(&mut self, schedule: S) -> Result<(), Error> {
135        let sched = schedule.into();
136
137        Schedule::from_str(&sched)
138            .map(|_| self.schedule = sched.clone())
139            .map_err(|_| Error::ParseSchedError(sched))?;
140
141        Ok(())
142    }
143
144    /// Returns the summary of the event.
145    pub fn summary(&self) -> Summary {
146        Summary {
147            schedule: &self.schedule,
148            summary: &self.summary,
149        }
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    #[test]
158    fn test_schedule() {
159        let event = Event::new("* * * * * * *", "summary", "body", false).unwrap();
160
161        assert_eq!(
162            event.schedule(),
163            Schedule::from_str("* * * * * * *").unwrap()
164        );
165    }
166}