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.
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user