mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
implements notification debouncing
This commit is contained in:
committed by
Aiden McClelland
parent
ac9b97ee28
commit
d58a950762
@@ -27,6 +27,7 @@ use crate::hostname::{get_hostname, get_id};
|
||||
use crate::manager::ManagerMap;
|
||||
use crate::net::tor::os_key;
|
||||
use crate::net::NetController;
|
||||
use crate::notifications::NotificationManager;
|
||||
use crate::shutdown::Shutdown;
|
||||
use crate::system::launch_metrics_task;
|
||||
use crate::util::io::from_toml_async_reader;
|
||||
@@ -126,6 +127,7 @@ pub struct RpcContextSeed {
|
||||
pub logger: EmbassyLogger,
|
||||
pub log_epoch: Arc<AtomicU64>,
|
||||
pub tor_socks: SocketAddr,
|
||||
pub notification_manager: NotificationManager,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -165,6 +167,7 @@ impl RpcContext {
|
||||
.await?;
|
||||
let managers = ManagerMap::default();
|
||||
let metrics_cache = RwLock::new(None);
|
||||
let notification_manager = NotificationManager::new(secret_store.clone(), db.clone(), 3600);
|
||||
let seed = Arc::new(RpcContextSeed {
|
||||
bind_rpc: base.bind_rpc.unwrap_or(([127, 0, 0, 1], 5959).into()),
|
||||
bind_ws: base.bind_ws.unwrap_or(([127, 0, 0, 1], 5960).into()),
|
||||
@@ -186,6 +189,7 @@ impl RpcContext {
|
||||
Ipv4Addr::new(127, 0, 0, 1),
|
||||
9050,
|
||||
))),
|
||||
notification_manager,
|
||||
});
|
||||
let metrics_seed = seed.clone();
|
||||
tokio::spawn(async move {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use chrono::{DateTime, Utc};
|
||||
use futures::lock::Mutex;
|
||||
use patch_db::{PatchDb, Revision};
|
||||
use rpc_toolkit::command;
|
||||
use sqlx::SqlitePool;
|
||||
@@ -132,7 +133,7 @@ pub async fn delete_before(#[context] ctx: RpcContext, #[arg] before: u32) -> Re
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
||||
pub enum NotificationLevel {
|
||||
Success,
|
||||
Info,
|
||||
@@ -231,26 +232,43 @@ impl NotificationSubtype {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn notify(
|
||||
sqlite: &SqlitePool,
|
||||
patchdb: &PatchDb,
|
||||
package_id: Option<PackageId>,
|
||||
level: NotificationLevel,
|
||||
title: String,
|
||||
message: String,
|
||||
subtype: NotificationSubtype,
|
||||
) -> Result<(), Error> {
|
||||
let mut handle = patchdb.handle();
|
||||
let mut count = crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.unread_notification_count()
|
||||
.get_mut(&mut handle)
|
||||
.await?;
|
||||
let sql_package_id = package_id.map::<String, _>(|p| p.into());
|
||||
let sql_code = subtype.code();
|
||||
let sql_level = format!("{}", level);
|
||||
let sql_data = format!("{}", subtype.to_json());
|
||||
sqlx::query!(
|
||||
pub struct NotificationManager {
|
||||
sqlite: SqlitePool,
|
||||
patchdb: PatchDb,
|
||||
cache: Mutex<HashMap<(Option<PackageId>, NotificationLevel, String), i64>>,
|
||||
debounce_interval: u32,
|
||||
}
|
||||
impl NotificationManager {
|
||||
pub fn new(sqlite: SqlitePool, patchdb: PatchDb, debounce_interval: u32) -> Self {
|
||||
NotificationManager {
|
||||
sqlite,
|
||||
patchdb,
|
||||
cache: Mutex::new(HashMap::new()),
|
||||
debounce_interval,
|
||||
}
|
||||
}
|
||||
pub async fn notify(
|
||||
&self,
|
||||
package_id: Option<PackageId>,
|
||||
level: NotificationLevel,
|
||||
title: String,
|
||||
message: String,
|
||||
subtype: NotificationSubtype,
|
||||
) -> Result<(), Error> {
|
||||
if !self.should_notify(&package_id, &level, &title).await {
|
||||
return Ok(());
|
||||
}
|
||||
let mut handle = self.patchdb.handle();
|
||||
let mut count = crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.unread_notification_count()
|
||||
.get_mut(&mut handle)
|
||||
.await?;
|
||||
let sql_package_id = package_id.map::<String, _>(|p| p.into());
|
||||
let sql_code = subtype.code();
|
||||
let sql_level = format!("{}", level);
|
||||
let sql_data = format!("{}", subtype.to_json());
|
||||
sqlx::query!(
|
||||
"INSERT INTO notifications (package_id, code, level, title, message, data) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
sql_package_id,
|
||||
sql_code,
|
||||
@@ -258,10 +276,50 @@ pub async fn notify(
|
||||
title,
|
||||
message,
|
||||
sql_data
|
||||
).execute(sqlite).await?;
|
||||
*count += 1;
|
||||
count.save(&mut handle).await?;
|
||||
Ok(())
|
||||
).execute(&self.sqlite).await?;
|
||||
*count += 1;
|
||||
count.save(&mut handle).await?;
|
||||
Ok(())
|
||||
}
|
||||
async fn gc(&self) {
|
||||
let mut guard = self.cache.lock().await;
|
||||
let mut temp = HashMap::new();
|
||||
for (k, v) in (*guard).drain() {
|
||||
if v + self.debounce_interval as i64 > Utc::now().timestamp() {
|
||||
temp.insert(k, v);
|
||||
}
|
||||
}
|
||||
*guard = temp
|
||||
}
|
||||
async fn should_notify(
|
||||
&self,
|
||||
package_id: &Option<PackageId>,
|
||||
level: &NotificationLevel,
|
||||
title: &String,
|
||||
) -> bool {
|
||||
if level == &NotificationLevel::Error {
|
||||
return true;
|
||||
}
|
||||
self.gc();
|
||||
let mut guard = self.cache.lock().await;
|
||||
let k = (package_id.clone(), level.clone(), title.clone());
|
||||
let v = (*guard).get(&k);
|
||||
match v {
|
||||
None => {
|
||||
(*guard).insert(k, Utc::now().timestamp());
|
||||
true
|
||||
}
|
||||
Some(t) => {
|
||||
if t + self.debounce_interval as i64 > Utc::now().timestamp() {
|
||||
false
|
||||
} else {
|
||||
// this path should be very rare due to gc above
|
||||
(*guard).remove(&k);
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::dependencies::{
|
||||
break_transitive, DependencyError, DependencyErrors, TaggedDependencyError,
|
||||
};
|
||||
use crate::manager::{Manager, Status as ManagerStatus};
|
||||
use crate::notifications::{notify, NotificationLevel, NotificationSubtype};
|
||||
use crate::notifications::{NotificationLevel, NotificationSubtype};
|
||||
use crate::s9pk::manifest::{Manifest, PackageId};
|
||||
use crate::status::health_check::HealthCheckResult;
|
||||
use crate::Error;
|
||||
@@ -335,9 +335,7 @@ impl MainStatus {
|
||||
.map(|hc| hc.critical)
|
||||
.unwrap_or_default() =>
|
||||
{
|
||||
notify(
|
||||
&ctx.secret_store,
|
||||
&ctx.db,
|
||||
ctx.notification_manager.notify(
|
||||
Some(manifest.id.clone()),
|
||||
NotificationLevel::Error,
|
||||
String::from("Critical Health Check Failed"),
|
||||
|
||||
Reference in New Issue
Block a user