use std::collections::BTreeSet;
use std::sync::{Arc, Mutex, PoisonError};
use crate::application::registry::Fanout;
use crate::application::session::{DynFrameSink, SinkError};
struct Watch {
sink: Arc<dyn DynFrameSink>,
uris: BTreeSet<String>,
meta: Option<serde_json::Value>,
}
#[derive(Default)]
pub(super) struct Watchers {
entries: Mutex<Vec<Watch>>,
}
impl Watchers {
pub(super) fn watch(
&self,
sink: &Arc<dyn DynFrameSink>,
uri: &str,
meta: Option<&serde_json::Value>,
) {
let mut entries = self.entries.lock().unwrap_or_else(PoisonError::into_inner);
if let Some(entry) = entries
.iter_mut()
.find(|entry| Arc::ptr_eq(&entry.sink, sink))
{
entry.uris.insert(uri.to_owned());
return;
}
entries.push(Watch {
sink: Arc::clone(sink),
uris: BTreeSet::from([uri.to_owned()]),
meta: meta.cloned(),
});
}
pub(super) fn unwatch(&self, sink: &Arc<dyn DynFrameSink>, uri: &str) {
let mut entries = self.entries.lock().unwrap_or_else(PoisonError::into_inner);
for entry in entries.iter_mut() {
if Arc::ptr_eq(&entry.sink, sink) {
entry.uris.remove(uri);
}
}
entries.retain(|entry| !entry.uris.is_empty());
}
pub(super) fn forget(&self, sink: &Arc<dyn DynFrameSink>) {
self.entries
.lock()
.unwrap_or_else(PoisonError::into_inner)
.retain(|entry| !Arc::ptr_eq(&entry.sink, sink));
}
pub(super) fn len(&self) -> usize {
self.entries
.lock()
.unwrap_or_else(PoisonError::into_inner)
.len()
}
pub(super) async fn notify(&self, uri: &str) -> Fanout {
let targets: Vec<(Arc<dyn DynFrameSink>, String)> = {
let entries = self.entries.lock().unwrap_or_else(PoisonError::into_inner);
entries
.iter()
.filter(|entry| entry.uris.contains(uri))
.map(|entry| (Arc::clone(&entry.sink), frame(uri, entry.meta.as_ref())))
.collect()
};
let mut report = Fanout::default();
for (sink, frame) in targets {
match sink.send(&frame).await {
Ok(()) => report.sent += 1,
Err(SinkError) => report.failed += 1,
}
}
report
}
}
fn frame(uri: &str, meta: Option<&serde_json::Value>) -> String {
let mut params = serde_json::json!({ "uri": uri });
if let (Some(meta), Some(object)) = (meta, params.as_object_mut()) {
object.insert("_meta".to_owned(), meta.clone());
}
serde_json::json!({
"jsonrpc": "2.0",
"method": "notifications/resources/updated",
"params": params,
})
.to_string()
}