use crate::{BoxedError, source::OwnedDirEntry, utils::IdBuilder};
use std::{
fmt,
path::{self, Path, PathBuf},
};
#[cfg(doc)]
use crate::source::Source;
pub struct FsWatcherBuilder {
roots: Vec<PathBuf>,
watcher: notify::RecommendedWatcher,
payload_sender: crossbeam_channel::Sender<NotifyEventHandler>,
}
impl FsWatcherBuilder {
pub fn new() -> Result<Self, BoxedError> {
let (payload_sender, payload_receiver) = crossbeam_channel::unbounded();
let watcher = notify::recommended_watcher(EventHandlerPayload::new(payload_receiver))?;
Ok(Self {
roots: Vec::new(),
watcher,
payload_sender,
})
}
pub fn watch(&mut self, path: PathBuf) -> Result<(), BoxedError> {
notify::Watcher::watch(&mut self.watcher, &path, notify::RecursiveMode::Recursive)?;
self.roots.push(path);
Ok(())
}
pub fn build(self, events: super::EventSender) {
if self.roots.is_empty() {
return;
}
let event_handler = NotifyEventHandler {
roots: self.roots,
events,
id_builder: IdBuilder::default(),
watcher: Some(self.watcher),
};
let _ = self.payload_sender.send(event_handler);
}
}
impl fmt::Debug for FsWatcherBuilder {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("FsWatcherBuilder")
.field("roots", &self.roots)
.finish()
}
}
fn id_of_path(id_builder: &mut IdBuilder, root: &Path, path: &Path) -> Option<OwnedDirEntry> {
id_builder.reset();
for comp in path.parent()?.strip_prefix(root).ok()?.components() {
match comp {
path::Component::Normal(s) => id_builder.push(s.to_str()?)?,
path::Component::ParentDir => id_builder.pop()?,
path::Component::CurDir => continue,
_ => return None,
}
}
let (name, ext) = crate::utils::split_file_name(path)?;
id_builder.push(name)?;
let id = id_builder.join();
let entry = if path.is_dir() {
OwnedDirEntry::Directory(id)
} else {
OwnedDirEntry::File(id, ext.into())
};
Some(entry)
}
enum EventHandlerPayload<H> {
Waiting(crossbeam_channel::Receiver<H>),
Handler(H),
}
impl<H: notify::EventHandler> EventHandlerPayload<H> {
fn new(receiver: crossbeam_channel::Receiver<H>) -> Self {
Self::Waiting(receiver)
}
}
impl<H: notify::EventHandler> notify::EventHandler for EventHandlerPayload<H> {
fn handle_event(&mut self, event: notify::Result<notify::Event>) {
match self {
Self::Waiting(recv) => {
if let Ok(mut handler) = recv.try_recv() {
handler.handle_event(event);
*self = Self::Handler(handler);
}
}
Self::Handler(handler) => handler.handle_event(event),
}
}
}
struct NotifyEventHandler {
roots: Vec<PathBuf>,
events: super::EventSender,
id_builder: IdBuilder,
watcher: Option<notify::RecommendedWatcher>,
}
impl notify::EventHandler for NotifyEventHandler {
fn handle_event(&mut self, event: notify::Result<notify::Event>) {
match event {
Ok(event) => {
log::trace!("Received filesystem event: {event:?}");
for path in event.paths {
let paths: &[_] = match event.kind {
notify::EventKind::Any | notify::EventKind::Modify(_) => &[&*path],
notify::EventKind::Create(_) => match path.parent() {
Some(parent) => &[&path, parent],
None => &[&*path],
},
notify::EventKind::Remove(_) => match path.parent() {
Some(parent) => &[parent],
None => &[], },
notify::EventKind::Access(_) | notify::EventKind::Other => break,
};
let ids = paths
.iter()
.flat_map(|p| self.roots.iter().map(move |r| (p, r)))
.filter_map(|(path, root)| id_of_path(&mut self.id_builder, root, path));
if self.events.send_multiple(ids).is_err() {
drop(self.watcher.take());
return;
}
}
}
Err(err) => log::warn!("Error from notify: {err}"),
}
if self.events.is_disconnected() {
drop(self.watcher.take());
}
}
}