use std::ffi::OsStr;
use std::sync::Arc;
use std::sync::atomic::{self, AtomicBool};
use std::{io, thread};
mod sys;
use hashbrown::DefaultHashBuilder;
use mio::{Poll, Waker};
use papaya::HashMap;
use crate::inotify::sys::{EventFlags, Inotify, Watch};
use crate::path::CannonicalPathBuf;
use crate::pending::{self, PendingChangesLock};
#[derive(Debug)]
pub(crate) struct InotifyWatcher {
waker: mio::Waker,
shutdown: AtomicBool,
notify: Inotify,
watches: HashMap<Watch, CannonicalPathBuf, DefaultHashBuilder>,
pub changes: PendingChangesLock,
}
impl InotifyWatcher {
pub fn shutdown(&self) {
self.shutdown.store(true, atomic::Ordering::Relaxed);
let _ = self.waker.wake();
self.changes.notify();
}
pub fn is_shutdown(&self) -> bool {
self.shutdown.load(atomic::Ordering::Relaxed)
}
pub fn new(#[cfg(test)] slow: bool) -> io::Result<Arc<Self>> {
let mut poll = Poll::new()?;
let waker = Waker::new(poll.registry(), sys::MESSAGE)?;
let watcher = Arc::new(Self {
waker,
notify: Inotify::new()?,
watches: HashMap::with_capacity_and_hasher(1024, DefaultHashBuilder::default()),
changes: PendingChangesLock::default(),
shutdown: AtomicBool::new(false),
});
let watcher_ = watcher.clone();
thread::spawn(move || {
watcher_.notify.event_loop(
&mut poll,
|path, event /* , timestamp */| {
watcher_.handle_event(path, event )
},
|| watcher_.changes.notify(),
|| true,
#[cfg(test)]
slow,
)
});
Ok(watcher)
}
pub fn watch_dir(&self, path: CannonicalPathBuf) -> io::Result<()> {
let watch = self.notify.add_directory_watch(&*path)?;
self.watches.pin().insert(watch, path);
Ok(())
}
fn handle_event(&self, child: &OsStr, event: &sys::Event ) {
if event.flags.contains(EventFlags::Q_OVERFLOW) {
self.changes.lock().recrawl();
return;
}
let watches = self.watches.pin();
let Some(dir) = watches.get(&event.wd) else {
if event.wd.is_invalid()
|| event
.flags
.intersects(EventFlags::MOVE_SELF | EventFlags::IGNORED)
{
self.changes.lock().recrawl();
}
return;
};
let watch_deleted = event.flags.intersects(
EventFlags::IGNORED
| EventFlags::MOVE_SELF
| EventFlags::DELETE_SELF
| EventFlags::UNMOUNT,
);
if child.is_empty() || watch_deleted {
if event.flags.contains(EventFlags::IGNORED) {
watches.remove(&event.wd);
}
let path = dir.clone();
self.changes
.lock()
.add_watcher(path, pending::Flags::empty());
} else {
let path = dir.join(child);
let mut pending = self.changes.lock();
if event
.flags
.intersects(EventFlags::CREATE | EventFlags::DELETE)
{
pending.add_watcher(
path,
pending::Flags::NEEDS_RECURSIVE_CRAWL,
);
} else {
pending.add_watcher(path, pending::Flags::empty());
}
}
}
}