aboutsummaryrefslogtreecommitdiffstats
path: root/src/reminder.rs
diff options
context:
space:
mode:
authorGustav Sörnäs <gustav@sornas.net>2020-12-01 07:46:29 +0100
committerGustav Sörnäs <gustav@sornas.net>2020-12-01 07:46:29 +0100
commit8b8c829793c026eee5fac474399a9a7179a10c80 (patch)
tree133d1102e7da0a1c89f0bf8b027e9461e4aaef59 /src/reminder.rs
parentc1b9d90f2f81f7acb0f4a88acf7056874e9cbe2f (diff)
parent31f5366ef857bc3e50f7a4485250cbe50d903e0b (diff)
downloadkodapa-8b8c829793c026eee5fac474399a9a7179a10c80.tar.gz
Merge branch 'reminders' into main
Diffstat (limited to 'src/reminder.rs')
-rw-r--r--src/reminder.rs106
1 files changed, 106 insertions, 0 deletions
diff --git a/src/reminder.rs b/src/reminder.rs
new file mode 100644
index 0000000..421720c
--- /dev/null
+++ b/src/reminder.rs
@@ -0,0 +1,106 @@
+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)));
+ }
+}