78 lines
3.4 KiB
Rust
78 lines
3.4 KiB
Rust
//! Keyword alert settings, native side, for every app that sets [`crate::app::AppConfig::alerts`]:
|
|
//! the shell-only `alerts_get` / `alerts_set` ([`crate::commands::ALERTS`]) over the owner's
|
|
//! [`AlertSettings`] (saved in app data, validated by [`crate::alerts`]). Site tabs gain no command
|
|
//! here: native code hands the page runtime the keywords to watch ([`script`]) at every page load
|
|
//! and whenever they change. What a match looks like and the notification are the app's.
|
|
|
|
use crate::alerts::AlertSettings;
|
|
use crate::app::config;
|
|
use crate::host::{eval_in_site_tabs, read_json, shell_only, write_json};
|
|
use crate::tabs::deliver_script;
|
|
use std::sync::{Mutex, MutexGuard};
|
|
use tauri::{AppHandle, Manager, Webview};
|
|
|
|
pub use crate::alerts::AlertsConfig;
|
|
|
|
struct AlertState(Mutex<AlertSettings>);
|
|
|
|
/// Loads the saved settings. Does nothing unless the app set [`crate::app::AppConfig::alerts`].
|
|
pub(crate) fn init(app: &AppHandle) {
|
|
let Some(alerts) = config(app).alerts else { return };
|
|
let settings = read_json::<AlertSettings>(app, alerts.file).and_then(|settings| settings.validated().ok()).unwrap_or_default();
|
|
app.manage(AlertState(Mutex::new(settings)));
|
|
}
|
|
|
|
fn state(app: &AppHandle) -> Result<MutexGuard<'_, AlertSettings>, String> {
|
|
app.try_state::<AlertState>()
|
|
.ok_or("DATABASE: alert state unavailable; restart safely")?
|
|
.inner()
|
|
.0
|
|
.lock()
|
|
.map_err(|_| "DATABASE: alert state unavailable; restart safely".into())
|
|
}
|
|
|
|
/// The owner's alert settings.
|
|
pub fn settings(app: &AppHandle) -> Result<AlertSettings, String> {
|
|
Ok(state(app)?.clone())
|
|
}
|
|
|
|
/// The script that hands the page runtime the keywords to watch (empty = alerts off).
|
|
pub fn script(app: &AppHandle) -> String {
|
|
let (Some(alerts), Ok(settings)) = (config(app).alerts, settings(app)) else { return String::new() };
|
|
script_for(app, alerts, &settings)
|
|
}
|
|
|
|
fn script_for(app: &AppHandle, alerts: &AlertsConfig, settings: &AlertSettings) -> String {
|
|
let keywords = serde_json::to_string(settings.active_keywords()).unwrap_or_else(|_| "[]".into());
|
|
deliver_script(config(app).site.runtime_global, alerts.member, "set", alerts.queue, &keywords)
|
|
}
|
|
|
|
/// Validates, saves and keeps the settings, and does not touch the tabs: for the app's own
|
|
/// writers (a backup import), which then evaluate [`script`] themselves.
|
|
pub fn store(app: &AppHandle, settings: AlertSettings) -> Result<AlertSettings, String> {
|
|
let alerts = config(app).alerts.ok_or_else(|| "UNAVAILABLE: this app has no alerts".to_owned())?;
|
|
let settings = settings.validated()?;
|
|
// The lock is held across the write so concurrent saves cannot leave disk and memory disagreeing.
|
|
let mut current = state(app)?;
|
|
write_json(app, alerts.file, &settings)?;
|
|
*current = settings.clone();
|
|
Ok(settings)
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub(crate) async fn alerts_get(webview: Webview, app: AppHandle) -> Result<AlertSettings, String> {
|
|
shell_only(&webview)?;
|
|
settings(&app)
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub(crate) async fn alerts_set(webview: Webview, app: AppHandle, settings: AlertSettings) -> Result<AlertSettings, String> {
|
|
shell_only(&webview)?;
|
|
let settings = store(&app, settings)?;
|
|
eval_in_site_tabs(&app, config(&app).site, &script(&app));
|
|
Ok(settings)
|
|
}
|
|
|
|
pub(crate) fn handler() -> impl Fn(tauri::ipc::Invoke<tauri::Wry>) -> bool + Send + Sync + 'static {
|
|
tauri::generate_handler![alerts_get, alerts_set]
|
|
}
|