Files
ActivePush/src/app.rs
T
2026-11-08 12:57:19 +01:00

171 lines
5.6 KiB
Rust

//! Shared application state.
use std::net::SocketAddr;
use std::path::PathBuf;
use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Instant;
use arc_swap::ArcSwap;
use rand::RngCore;
use crate::backend::dav::DavClient;
use crate::backend::{ImapPool, Session};
use crate::config::Settings;
use crate::db::{self, Db};
use crate::logging::LogRing;
use crate::metrics::{AuthGuard, Metrics};
#[derive(Debug, Clone)]
pub struct Boot {
pub data_dir: PathBuf,
pub eas_listen: SocketAddr,
pub admin_listen: SocketAddr,
pub allow_remote_admin: bool,
pub admin_tls: Option<(String, String)>,
pub eas_tls: Option<(String, String)>,
}
pub struct App {
pub db: Db,
pub boot: Boot,
settings: ArcSwap<Settings>,
pub settings_rev: AtomicI64,
pub metrics: Metrics,
pub auth: AuthGuard,
pub pool: Arc<ImapPool>,
pub logs: Arc<LogRing>,
pub started: Instant,
pub started_ts: i64,
http: Mutex<(i64, Option<reqwest::Client>, Option<reqwest::Client>)>,
/// Random per-process key for HMACs (credential cache keys).
pub secret: [u8; 32],
loops: Mutex<std::collections::HashMap<String, (u32, i64)>>,
pub shutdown: tokio_util::sync::CancellationToken,
/// Collection ids a device still uses but that do not exist here (e.g. left over from
/// Z-Push). They are sent as FolderHierarchy Delete on the next FolderSync.
stale: Mutex<std::collections::HashMap<i64, std::collections::BTreeSet<String>>>,
/// Last Autodiscover requests with headers (no secrets) and bodies, for the panel.
ad_trace: Mutex<std::collections::VecDeque<serde_json::Value>>,
}
impl App {
pub fn ad_trace_push(&self, v: serde_json::Value) {
let mut t = self.ad_trace.lock().unwrap();
if t.len() >= 30 {
t.pop_front();
}
t.push_back(v);
}
pub fn ad_trace_list(&self) -> Vec<serde_json::Value> {
self.ad_trace
.lock()
.unwrap()
.iter()
.rev()
.cloned()
.collect()
}
pub async fn new(boot: Boot, logs: Arc<LogRing>) -> anyhow::Result<Arc<App>> {
let db = db::open(&boot.data_dir).await?;
let (settings, rev) = db::load_settings(&db).await?;
if let Err(e) = settings.validate() {
tracing::warn!("stored settings do not validate ({e}); running with them anyway");
}
logs.set_cap(settings.logging.ring_size);
let mut secret = [0u8; 32];
rand::rngs::OsRng.fill_bytes(&mut secret);
Ok(Arc::new(App {
db,
boot,
settings: ArcSwap::from_pointee(settings),
settings_rev: AtomicI64::new(rev),
metrics: Metrics::new(),
auth: AuthGuard::default(),
pool: Arc::new(ImapPool::new(secret)),
logs,
started: Instant::now(),
started_ts: crate::util::now(),
http: Mutex::new((-1, None, None)),
secret,
loops: Mutex::new(std::collections::HashMap::new()),
shutdown: tokio_util::sync::CancellationToken::new(),
stale: Mutex::new(std::collections::HashMap::new()),
ad_trace: Mutex::new(std::collections::VecDeque::new()),
}))
}
pub fn settings(&self) -> Arc<Settings> {
self.settings.load_full()
}
pub async fn update_settings(&self, s: Settings) -> anyhow::Result<i64> {
let rev = db::save_settings(&self.db, &s).await?;
self.logs.set_cap(s.logging.ring_size);
self.settings.store(Arc::new(s));
self.settings_rev.store(rev, Ordering::SeqCst);
// backend endpoints may have changed
self.pool.clear();
self.auth.clear_cache();
Ok(rev)
}
fn http_clients(&self, s: &Settings) -> (Option<reqwest::Client>, Option<reqwest::Client>) {
let rev = self.settings_rev.load(Ordering::SeqCst);
let mut g = self.http.lock().unwrap();
if g.0 != rev {
let cal = s
.caldav
.enabled
.then(|| DavClient::build_http(s.caldav.verify_tls, s.caldav.timeout_secs).ok())
.flatten();
let card = s
.carddav
.enabled
.then(|| DavClient::build_http(s.carddav.verify_tls, s.carddav.timeout_secs).ok())
.flatten();
*g = (rev, cal, card);
}
(g.1.clone(), g.2.clone())
}
pub fn stale_add(&self, device: i64, collection: &str) {
if collection.is_empty() || collection.len() > 64 {
return;
}
let mut g = self.stale.lock().unwrap();
let set = g.entry(device).or_default();
if set.len() < 500 {
set.insert(collection.to_string());
}
}
pub fn stale_take(&self, device: i64) -> Vec<String> {
self.stale
.lock()
.unwrap()
.remove(&device)
.map(|s| s.into_iter().collect())
.unwrap_or_default()
}
/// Count how often a (device, collection, old sync key) was retried within the last hour.
pub fn loop_hit(&self, key: &str) -> u32 {
let now = crate::util::now();
let mut g = self.loops.lock().unwrap();
g.retain(|_, (_, t)| *t > now - 3600);
let e = g.entry(key.to_string()).or_insert((0, now));
e.0 += 1;
e.1 = now;
e.0
}
pub fn session(&self, login: &str, password: &str) -> Session {
let s = self.settings();
let (cal, card) = self.http_clients(&s);
Session::new(s, self.pool.clone(), cal, card, login, password)
}
}