use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, RwLock};
use super::events::FileSystemEvent;
pub type SubscriptionId = u64;
pub type EventCallback = Arc<dyn Fn(&FileSystemEvent) + Send + Sync>;
pub struct CallbackRegistry {
callbacks: RwLock<HashMap<SubscriptionId, EventCallback>>,
next_id: AtomicU64,
}
impl CallbackRegistry {
pub fn new() -> Self {
Self {
callbacks: RwLock::new(HashMap::new()),
next_id: AtomicU64::new(1),
}
}
pub fn subscribe(&self, callback: EventCallback) -> SubscriptionId {
let id = self.next_id.fetch_add(1, Ordering::SeqCst);
let mut callbacks = self.callbacks.write().unwrap();
callbacks.insert(id, callback);
id
}
pub fn unsubscribe(&self, id: SubscriptionId) -> bool {
let mut callbacks = self.callbacks.write().unwrap();
callbacks.remove(&id).is_some()
}
pub fn emit(&self, event: &FileSystemEvent) {
let callbacks = self.callbacks.read().unwrap();
for callback in callbacks.values() {
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
callback(event);
}));
}
}
pub fn subscriber_count(&self) -> usize {
let callbacks = self.callbacks.read().unwrap();
callbacks.len()
}
pub fn has_subscribers(&self) -> bool {
let callbacks = self.callbacks.read().unwrap();
!callbacks.is_empty()
}
pub fn clear(&self) {
let mut callbacks = self.callbacks.write().unwrap();
callbacks.clear();
}
}
impl Default for CallbackRegistry {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for CallbackRegistry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let callbacks = self.callbacks.read().unwrap();
f.debug_struct("CallbackRegistry")
.field("subscriber_count", &callbacks.len())
.field("next_id", &self.next_id.load(Ordering::SeqCst))
.finish()
}
}
unsafe impl Send for CallbackRegistry {}
unsafe impl Sync for CallbackRegistry {}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
use std::sync::atomic::{AtomicUsize, Ordering};
#[test]
fn test_subscribe_and_emit() {
let registry = CallbackRegistry::new();
let counter = Arc::new(AtomicUsize::new(0));
let counter_clone = Arc::clone(&counter);
let _id = registry.subscribe(Arc::new(move |_event| {
counter_clone.fetch_add(1, Ordering::SeqCst);
}));
assert_eq!(registry.subscriber_count(), 1);
let event = FileSystemEvent::file_created(PathBuf::from("test.md"));
registry.emit(&event);
assert_eq!(counter.load(Ordering::SeqCst), 1);
}
#[test]
fn test_unsubscribe() {
let registry = CallbackRegistry::new();
let counter = Arc::new(AtomicUsize::new(0));
let counter_clone = Arc::clone(&counter);
let id = registry.subscribe(Arc::new(move |_event| {
counter_clone.fetch_add(1, Ordering::SeqCst);
}));
assert_eq!(registry.subscriber_count(), 1);
let result = registry.unsubscribe(id);
assert!(result);
assert_eq!(registry.subscriber_count(), 0);
let event = FileSystemEvent::file_created(PathBuf::from("test.md"));
registry.emit(&event);
assert_eq!(counter.load(Ordering::SeqCst), 0);
}
#[test]
fn test_unsubscribe_nonexistent() {
let registry = CallbackRegistry::new();
let result = registry.unsubscribe(999);
assert!(!result);
}
#[test]
fn test_multiple_subscribers() {
let registry = CallbackRegistry::new();
let counter1 = Arc::new(AtomicUsize::new(0));
let counter2 = Arc::new(AtomicUsize::new(0));
let c1 = Arc::clone(&counter1);
registry.subscribe(Arc::new(move |_event| {
c1.fetch_add(1, Ordering::SeqCst);
}));
let c2 = Arc::clone(&counter2);
registry.subscribe(Arc::new(move |_event| {
c2.fetch_add(1, Ordering::SeqCst);
}));
assert_eq!(registry.subscriber_count(), 2);
let event = FileSystemEvent::file_created(PathBuf::from("test.md"));
registry.emit(&event);
assert_eq!(counter1.load(Ordering::SeqCst), 1);
assert_eq!(counter2.load(Ordering::SeqCst), 1);
}
#[test]
fn test_unique_subscription_ids() {
let registry = CallbackRegistry::new();
let id1 = registry.subscribe(Arc::new(|_| {}));
let id2 = registry.subscribe(Arc::new(|_| {}));
let id3 = registry.subscribe(Arc::new(|_| {}));
assert_ne!(id1, id2);
assert_ne!(id2, id3);
assert_ne!(id1, id3);
}
#[test]
fn test_clear() {
let registry = CallbackRegistry::new();
registry.subscribe(Arc::new(|_| {}));
registry.subscribe(Arc::new(|_| {}));
assert_eq!(registry.subscriber_count(), 2);
registry.clear();
assert_eq!(registry.subscriber_count(), 0);
assert!(!registry.has_subscribers());
}
#[test]
fn test_callback_panic_isolation() {
let registry = CallbackRegistry::new();
let counter = Arc::new(AtomicUsize::new(0));
registry.subscribe(Arc::new(|_| {
panic!("Test panic");
}));
let counter_clone = Arc::clone(&counter);
registry.subscribe(Arc::new(move |_| {
counter_clone.fetch_add(1, Ordering::SeqCst);
}));
let event = FileSystemEvent::file_created(PathBuf::from("test.md"));
registry.emit(&event);
assert_eq!(counter.load(Ordering::SeqCst), 1);
}
}