aboutsummaryrefslogtreecommitdiffstats
path: root/src/reminder.rs
blob: 2dc6eb05a65cbde614daa0c877e86800ba7610d1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
use chrono::{DateTime, Datelike, Duration, Local, NaiveTime, Weekday};
use serde::{Deserialize, Serialize};
use std::fs;
use tokio::sync::watch;

#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ReminderType {
    // Different types of reminders are possible.
    // e.g. different reminders for the day before and one hour before.
    Void,
    OneHour, //TODO struct instead
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Reminder {
    reminder_type: ReminderType,
    last_fire: DateTime<Local>,
}

#[derive(Serialize, Deserialize)]
pub struct Reminders {
    reminders: Vec<Reminder>,
}

impl Reminders {
    fn write(&self) {
        fs::write(
            std::path::Path::new("reminders.json"),
            serde_json::to_string_pretty(&self).expect("Can't serialize reminders"),
        )
        .expect("Can't write reminders.json")
    }
}

pub async fn handle(sender: watch::Sender<ReminderType>) {
    let mut interval = tokio::time::interval(tokio::time::Duration::from_millis(1000));

    loop {
        let now = Local::now();
        let next = next_meeting();
        let mut reminders = read_reminders();
        for mut reminder in &mut reminders.reminders {
            match reminder.reminder_type {
                ReminderType::OneHour => {
                    if in_remind_zone(now, next) && !in_remind_zone(reminder.last_fire, next) {
                        sender.broadcast(ReminderType::OneHour).unwrap();
                        reminder.last_fire = now;
                    }
                }
                _ => {}
            }
        }
        reminders.write();
        interval.tick().await;
    }
}

fn read_reminders() -> Reminders {
    match fs::read_to_string("reminders.json") {
        Ok(s) => serde_json::from_str(&s).expect("Error parsing reminders.json"),
        Err(_) => Reminders {
            reminders: vec![Reminder {
                reminder_type: ReminderType::OneHour,
                last_fire: Local::now(),
            }],
        },
    }
}

fn in_remind_zone(dt: DateTime<Local>, meeting: DateTime<Local>) -> bool {
    // Wether we're in a "send reminder"-zone.
    // Currently implemented as "are we 1 hour before?".
    ((meeting - Duration::hours(1))..meeting).contains(&dt)
}

fn next_meeting() -> DateTime<Local> {
    // Check current datetime and calculate when the next meeting is.
    let now = Local::now();
    let meeting_time = NaiveTime::from_hms(12, 15, 00);
    let meeting = match Datelike::weekday(&now) {
        Weekday::Thu => {
            // same day as meeting.
            // next week if meeting has occured.
            let date_delta = Duration::weeks(if now.time() < meeting_time { 0 } else { 1 });
            (now.date() + date_delta).and_time(meeting_time).unwrap()
        }
        _ => {
            let dow_index: i64 = now.date().weekday().num_days_from_monday().into();
            let date_delta = Duration::days((3 - dow_index).rem_euclid(7));
            (now.date() + date_delta).and_time(meeting_time).unwrap()
        }
    };
    assert!(meeting.weekday() == Weekday::Thu);
    meeting
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn in_remind_zone() {
        let now = Local::now();
        assert!(super::in_remind_zone(now, now + Duration::minutes(30)));
        assert!(!super::in_remind_zone(now, now + Duration::hours(2)));
        assert!(!super::in_remind_zone(now, now - Duration::minutes(30)));
    }
}