mod emit;
mod types;
mod watch;
pub use types::{EventMask, EventType, FsEvent, NotifyError, WatchDescriptor, WatchOptions};
pub use watch::{
WatchCallback, WatchInfo, add_watch, get_watch_info, remove_watch, watch, watch_count,
watch_recursive,
};
pub use emit::{
EventLogStats, clear_event_log, clear_pending, emit_attrib, emit_create, emit_delete,
emit_dir_create, emit_dir_delete, emit_event, emit_event_with_debounce, emit_modify,
emit_rename, get_event_log_stats, get_events_for_path, get_events_since_timestamp,
get_events_since_txg, get_last_events, is_emit_enabled, pending_count, poll_event, poll_events,
set_debounce_interval, set_emit_enabled, set_event_log_max_size, wait_for_events,
};
pub fn init(max_log_size: usize, debounce_ms: u64) {
set_event_log_max_size(max_log_size);
set_debounce_interval(debounce_ms * 1000); }
pub fn shutdown() {
set_emit_enabled(false);
clear_pending();
clear_event_log();
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::boxed::Box;
use alloc::sync::Arc;
use alloc::vec;
use alloc::vec::Vec;
use core::sync::atomic::{AtomicU64, Ordering};
fn setup() {
set_emit_enabled(true);
set_debounce_interval(0);
clear_pending();
clear_event_log();
}
#[test]
fn test_exports_accessible() {
let _ = EventType::Create;
let _ = EventMask::ALL;
let _ = WatchOptions::default();
let _ = FsEvent::new(EventType::Create, "tank", "/path");
}
#[test]
fn test_watch_and_emit() {
setup();
let counter = Arc::new(AtomicU64::new(0));
let counter_clone = counter.clone();
let callback: WatchCallback = Box::new(move |_event| {
counter_clone.fetch_add(1, Ordering::SeqCst);
});
let wd = watch("tank/data", "/watched", &[EventType::Create], callback).unwrap();
emit_create("tank/data", "/watched/file.txt", 1, 100);
assert_eq!(counter.load(Ordering::SeqCst), 1);
remove_watch(wd).unwrap();
}
#[test]
fn test_poll_integration() {
setup();
let ds = "poll_int_unique_7788998877";
let base_txg = 99880077u64;
emit_create(ds, "/poll_file1.txt", 1, base_txg);
emit_modify(ds, "/poll_file2.txt", 2, 1024, base_txg + 1);
let logged = get_events_since_txg(base_txg);
let our_logged: Vec<_> = logged.into_iter().filter(|e| e.dataset == ds).collect();
let polled = poll_events();
let our_polled: Vec<_> = polled.into_iter().filter(|e| e.dataset == ds).collect();
let total = our_logged.len() + our_polled.len();
assert!(
total >= 1,
"Expected at least 1 event in log or pending, got {} (log={}, polled={})",
total,
our_logged.len(),
our_polled.len()
);
}
#[test]
fn test_event_log_integration() {
setup();
for i in 0..5 {
emit_create(
"log_int",
&alloc::format!("/file{}.txt", i),
i as u64,
10000 + i as u64,
);
}
let since = get_events_since_txg(10002);
let our_events: Vec<_> = since
.into_iter()
.filter(|e| e.dataset == "log_int")
.collect();
assert_eq!(our_events.len(), 2); }
#[test]
fn test_recursive_watch() {
setup();
let counter = Arc::new(AtomicU64::new(0));
let counter_clone = counter.clone();
let _wd = watch_recursive(
"tank",
"/root",
&[EventType::Create],
Box::new(move |_| {
counter_clone.fetch_add(1, Ordering::SeqCst);
}),
)
.unwrap();
emit_create("tank", "/root/file.txt", 1, 100);
emit_create("tank", "/root/sub/file.txt", 2, 101);
emit_create("tank", "/root/sub/deep/file.txt", 3, 102);
assert_eq!(counter.load(Ordering::SeqCst), 3);
}
#[test]
fn test_watch_info() {
setup();
let wd = add_watch(
"tank",
"/path",
EventMask::ALL,
WatchOptions::recursive(),
None,
)
.unwrap();
let info = get_watch_info(wd).unwrap();
assert_eq!(info.dataset, "tank");
assert_eq!(info.path, "/path");
assert!(info.recursive);
assert!(info.active);
remove_watch(wd).unwrap();
assert!(get_watch_info(wd).is_none());
}
#[test]
fn test_init_shutdown() {
let ds = "init_shutdown_test_unique_11223344";
set_emit_enabled(true);
set_debounce_interval(0);
clear_pending();
init(5000, 100);
let before = pending_count();
emit_create(ds, "/file.txt", 1, 100);
let after_emit = pending_count();
assert!(
after_emit > before,
"Event should have been emitted: before={}, after={}",
before,
after_emit
);
shutdown();
let before_shutdown_emit = pending_count();
emit_create(ds, "/file2.txt", 2, 101);
let after_shutdown_emit = pending_count();
assert_eq!(
after_shutdown_emit, before_shutdown_emit,
"No events should be emitted after shutdown"
);
set_emit_enabled(true);
}
}