use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread::JoinHandle;
use std::time::{Duration, SystemTime};
use crate::change::ChangeType;
use crate::error::BuildError;
use crate::source::SourcePipeline;
const POLL_INTERVAL: Duration = Duration::from_millis(500);
pub struct WatchHandle {
stop: Arc<AtomicBool>,
thread: Option<JoinHandle<()>>,
}
impl WatchHandle {
pub fn stop(self) {
}
}
impl Drop for WatchHandle {
fn drop(&mut self) {
self.stop.store(true, Ordering::Relaxed);
if let Some(thread) = self.thread.take() {
let _ = thread.join();
}
}
}
pub(crate) fn start(
pipeline: SourcePipeline,
watched: Vec<PathBuf>,
mut on_error: impl FnMut(BuildError) + Send + 'static,
) -> WatchHandle {
let stop = Arc::new(AtomicBool::new(false));
let thread_stop = Arc::clone(&stop);
let mut seen = snapshot_mtimes(&watched);
let thread = std::thread::spawn(move || {
while !thread_stop.load(Ordering::Relaxed) {
std::thread::sleep(POLL_INTERVAL);
if thread_stop.load(Ordering::Relaxed) {
break;
}
let current = snapshot_mtimes(&watched);
for (path, mtime) in ¤t {
let unchanged = seen.get(path).is_some_and(|previous| previous == mtime);
if unchanged {
continue;
}
let change_type = ChangeType::from_path(path);
if let Err(e) = pipeline.process_change(path, &change_type) {
on_error(BuildError::Build(e));
}
}
seen = current;
}
});
WatchHandle {
stop,
thread: Some(thread),
}
}
fn snapshot_mtimes(roots: &[PathBuf]) -> HashMap<PathBuf, SystemTime> {
let mut mtimes = HashMap::new();
for root in roots {
for path in walk_files(root) {
if let Ok(mtime) = std::fs::metadata(&path).and_then(|meta| meta.modified()) {
mtimes.insert(path, mtime);
}
}
}
mtimes
}
fn walk_files(dir: &Path) -> Vec<PathBuf> {
let mut files = Vec::new();
let mut pending = vec![dir.to_path_buf()];
while let Some(current) = pending.pop() {
let Ok(entries) = std::fs::read_dir(¤t) else {
continue;
};
for entry in entries.flatten() {
let path = entry.path();
match entry.file_type() {
Ok(file_type) if file_type.is_dir() => pending.push(path),
Ok(_) => files.push(path),
Err(_) => {}
}
}
}
files
}
#[cfg(test)]
#[path = "../tests/unit/watch.rs"]
mod tests;