first commit
This commit is contained in:
commit
19dfc51360
85 files changed
+25504
No files matched your search
+149
@@ -0,0 +1,149 @@
|
||||
//! 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>>>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
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()),
|
||||
}))
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user