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.
71 lines
2.5 KiB
Rust
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, ¬ified);
|
|
}
|
|
});
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
}
|