use std::collections::HashSet;
use std::sync::Mutex;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MutationKind {
TaskChanged,
DependencyChanged,
FileMarkChanged,
AgentChanged,
AttachmentChanged,
}
impl MutationKind {
pub fn affected_uris(&self) -> &'static [&'static str] {
match self {
MutationKind::TaskChanged => &[
"query://tasks/all",
"query://tasks/ready",
"query://tasks/blocked",
"query://tasks/claimed",
"query://stats/summary",
],
MutationKind::DependencyChanged => &[
"query://tasks/all",
"query://tasks/ready",
"query://tasks/blocked",
"query://stats/summary",
],
MutationKind::FileMarkChanged => &["query://files/marks"],
MutationKind::AgentChanged => &[
"query://agents/all",
"query://tasks/claimed",
"query://stats/summary",
],
MutationKind::AttachmentChanged => &["query://tasks/all", "query://stats/summary"],
}
}
}
pub struct SubscriptionManager {
subscribed: Mutex<HashSet<String>>,
}
impl SubscriptionManager {
pub fn new() -> Self {
Self {
subscribed: Mutex::new(HashSet::new()),
}
}
pub fn subscribe(&self, uri: &str) -> bool {
let mut set = self.subscribed.lock().unwrap();
set.insert(uri.to_string())
}
pub fn unsubscribe(&self, uri: &str) -> bool {
let mut set = self.subscribed.lock().unwrap();
set.remove(uri)
}
pub fn has_subscriptions(&self) -> bool {
let set = self.subscribed.lock().unwrap();
!set.is_empty()
}
pub fn affected_subscriptions(&self, mutations: &[MutationKind]) -> Vec<String> {
let set = self.subscribed.lock().unwrap();
if set.is_empty() {
return Vec::new();
}
let mut result = HashSet::new();
for kind in mutations {
for uri in kind.affected_uris() {
if set.contains(*uri) {
result.insert((*uri).to_string());
}
}
}
result.into_iter().collect()
}
}
impl Default for SubscriptionManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_subscribe_unsubscribe() {
let mgr = SubscriptionManager::new();
assert!(!mgr.has_subscriptions());
assert!(mgr.subscribe("query://tasks/all"));
assert!(mgr.has_subscriptions());
assert!(!mgr.subscribe("query://tasks/all"));
assert!(mgr.unsubscribe("query://tasks/all"));
assert!(!mgr.has_subscriptions());
assert!(!mgr.unsubscribe("query://tasks/all"));
}
#[test]
fn test_affected_subscriptions() {
let mgr = SubscriptionManager::new();
mgr.subscribe("query://tasks/all");
mgr.subscribe("query://files/marks");
let affected = mgr.affected_subscriptions(&[MutationKind::TaskChanged]);
assert!(affected.contains(&"query://tasks/all".to_string()));
assert!(!affected.contains(&"query://files/marks".to_string()));
let affected = mgr.affected_subscriptions(&[MutationKind::FileMarkChanged]);
assert!(affected.contains(&"query://files/marks".to_string()));
assert!(!affected.contains(&"query://tasks/all".to_string()));
let affected =
mgr.affected_subscriptions(&[MutationKind::TaskChanged, MutationKind::FileMarkChanged]);
assert!(affected.contains(&"query://tasks/all".to_string()));
assert!(affected.contains(&"query://files/marks".to_string()));
}
#[test]
fn test_no_subscriptions_returns_empty() {
let mgr = SubscriptionManager::new();
let affected = mgr.affected_subscriptions(&[MutationKind::TaskChanged]);
assert!(affected.is_empty());
}
#[test]
fn test_unsubscribed_uri_not_notified() {
let mgr = SubscriptionManager::new();
mgr.subscribe("query://files/marks");
let affected = mgr.affected_subscriptions(&[MutationKind::TaskChanged]);
assert!(affected.is_empty()); }
}