use std::collections::BTreeSet;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex, MutexGuard};
use anyhow::Result;
use notify::event::{EventKind, ModifyKind};
use notify::{INotifyWatcher, RecursiveMode, Watcher};
use super::{Handler, WatchSet};
pub struct Platform {
watcher: INotifyWatcher,
folders: Arc<Mutex<BTreeSet<PathBuf>>>,
}
impl Platform {
pub fn new(_served: &Path, handler: Handler) -> Result<Self> {
let folders = Arc::new(Mutex::new(BTreeSet::new()));
let watched = folders.clone();
let watcher = INotifyWatcher::new(
move |event: notify::Result<notify::Event>| {
if let Ok(event) = &event {
lost(&watched, event);
}
handler(event)
},
notify::Config::default(),
)?;
Ok(Self { watcher, folders })
}
pub fn apply(&mut self, set: WatchSet) -> Vec<(PathBuf, bool)> {
let mut planned = set.shallow;
for deep in &set.deep {
every_folder(deep, &mut planned);
}
let watched = lock(&self.folders).clone();
for gone in watched.difference(&planned) {
let _ = self.watcher.unwatch(gone);
}
let mut added = Vec::new();
for folder in planned.difference(&watched) {
if self
.watcher
.watch(folder, RecursiveMode::NonRecursive)
.is_ok()
{
added.push(folder.clone());
}
}
let mut folders = lock(&self.folders);
folders.retain(|folder| planned.contains(folder));
folders.extend(added.iter().cloned());
added.into_iter().map(|folder| (folder, false)).collect()
}
pub fn covers(&self, folder: &Path) -> bool {
lock(&self.folders).contains(folder)
}
pub fn watches_within(&self, path: &Path) -> bool {
lock(&self.folders)
.iter()
.any(|folder| folder.starts_with(path))
}
}
fn lost(folders: &Mutex<BTreeSet<PathBuf>>, event: ¬ify::Event) {
if !matches!(
event.kind,
EventKind::Remove(_) | EventKind::Modify(ModifyKind::Name(_))
) {
return;
}
let mut folders = lock(folders);
for path in &event.paths {
folders.retain(|folder| !folder.starts_with(path));
}
}
fn lock(folders: &Mutex<BTreeSet<PathBuf>>) -> MutexGuard<'_, BTreeSet<PathBuf>> {
folders
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
fn every_folder(folder: &Path, folders: &mut BTreeSet<PathBuf>) {
folders.insert(folder.to_path_buf());
for entry in std::fs::read_dir(folder).into_iter().flatten().flatten() {
if entry.file_type().is_ok_and(|kind| kind.is_dir()) {
every_folder(&entry.path(), folders);
}
}
}