Files
mail-client/src-tauri/src/reminders.rs
T
KanjiG 20bfd2cf6b Initial commit: Outlook-competitor desktop mail client
Tauri v2 (Rust backend, React/TypeScript frontend). IMAP/POP3/SMTP core,
Google OAuth (mail/calendar/contacts sync), CalDAV, local calendar with
recurrence, rules engine, contacts, tasks, unified inbox, context menus,
drag & drop, snooze, templates, and more.
2026-07-28 20:56:05 +02:00

71 lines
2.5 KiB
Rust

use crate::db::Db;
use chrono::TimeZone;
use std::collections::HashSet;
use std::sync::Mutex;
use std::time::Duration;
use tauri::{AppHandle, Manager};
use tauri_plugin_notification::NotificationExt;
const POLL_INTERVAL_SECS: u64 = 30;
/// Only fire a reminder whose trigger moment is this recent — avoids a flood of
/// stale notifications for reminders that elapsed while the app was closed.
const FRESHNESS_WINDOW_SECS: i64 = 300;
const LOOKAHEAD_SECS: i64 = 2 * 86400;
pub fn spawn_poller(app: AppHandle) {
tauri::async_runtime::spawn(async move {
let notified: Mutex<HashSet<String>> = Mutex::new(HashSet::new());
let mut interval = tokio::time::interval(Duration::from_secs(POLL_INTERVAL_SECS));
loop {
interval.tick().await;
poll_once(&app, &notified);
}
});
}
fn poll_once(app: &AppHandle, notified: &Mutex<HashSet<String>>) {
let db = app.state::<Db>();
let Ok(events) = db.list_events_with_reminders() else { return };
let now = chrono::Utc::now().timestamp();
for base in events {
let Some(reminder_min) = base.reminder_minutes else { continue };
let occurrences = if let Some(rule) = &base.rrule {
let exdates: Vec<i64> = base.exdates.as_deref().unwrap_or("").split(',').filter_map(|s| s.trim().parse().ok()).collect();
crate::recurrence::expand(base.start_ts, base.end_ts, rule, now - 3600, now + LOOKAHEAD_SECS, &exdates)
} else {
vec![(base.start_ts, base.end_ts)]
};
for (occ_start, _) in occurrences {
let trigger_ts = occ_start - i64::from(reminder_min) * 60;
if trigger_ts > now || now - trigger_ts >= FRESHNESS_WINDOW_SECS {
continue;
}
let key = format!("{}#{}", base.id, occ_start);
{
let mut set = notified.lock().unwrap();
if !set.insert(key) {
continue;
}
}
let when = chrono::Local
.timestamp_opt(occ_start, 0)
.single()
.map(|d| d.format("um %H:%M").to_string())
.unwrap_or_default();
let body = if base.all_day {
"Heute".to_string()
} else {
format!("Beginnt {when}")
};
let _ = app
.notification()
.builder()
.title(format!("Erinnerung: {}", base.title))
.body(body)
.show();
}
}
}