use std::path::Path;
use std::sync::mpsc;
use std::time::SystemTime;
use notify::{Event, RecommendedWatcher, RecursiveMode, Watcher};
use smol::channel::{self, Receiver};
pub struct FileWatcher {
watcher: RecommendedWatcher,
rx: Receiver<()>,
}
impl std::fmt::Debug for FileWatcher {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FileWatcher").finish_non_exhaustive()
}
}
impl FileWatcher {
pub fn new(project_path: &Path) -> notify::Result<Self> {
let (tx, rx) = channel::unbounded();
let (sync_tx, sync_rx) = mpsc::channel::<notify::Result<Event>>();
let tx_clone = tx;
let started_at = SystemTime::now();
std::thread::spawn(move || {
while let Ok(event) = sync_rx.recv() {
if let Ok(event) = event {
if is_relevant_change(&event, started_at) {
let _ = tx_clone.send_blocking(());
}
}
}
});
let watcher = notify::recommended_watcher(move |res| {
let _ = sync_tx.send(res);
})?;
let mut file_watcher = Self { watcher, rx };
let src_path = project_path.join("src");
if src_path.exists() {
file_watcher
.watcher
.watch(&src_path, RecursiveMode::Recursive)?;
}
Ok(file_watcher)
}
#[must_use]
pub const fn receiver(&self) -> &Receiver<()> {
&self.rx
}
}
fn is_relevant_change(event: &Event, started_at: SystemTime) -> bool {
use notify::{EventKind, event::ModifyKind};
let kind = &event.kind;
let is_relevant_kind = match kind {
EventKind::Create(_) | EventKind::Remove(_) => true,
EventKind::Modify(modify_kind) => !matches!(modify_kind, ModifyKind::Metadata(_)),
_ => false,
};
if !is_relevant_kind {
return false;
}
event
.paths
.iter()
.any(|path| is_relevant_path(path, *kind, started_at))
}
fn is_relevant_path(path: &Path, kind: notify::EventKind, started_at: SystemTime) -> bool {
use notify::{EventKind, event::ModifyKind};
if !path
.extension()
.is_some_and(|ext| ext == "rs" || ext == "toml")
{
return false;
}
if matches!(kind, EventKind::Remove(_)) {
return true;
}
if matches!(kind, EventKind::Modify(ModifyKind::Name(_))) {
return true;
}
std::fs::metadata(path)
.and_then(|m| m.modified())
.map_or(true, |modified| modified > started_at)
}