use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{RecvTimeoutError, Sender};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use anyhow::{Context, Result};
use notify::event::{ModifyKind, RemoveKind, RenameMode};
use notify::{Event, EventKind, RecursiveMode, Watcher};
use crate::events::{Debouncer, QUIET_WINDOW_MS, WatchEvent};
use crate::updater::{BatchOutcome, IndexUpdater};
pub trait EventSource {
fn watch(&self, roots: &[PathBuf], tx: Sender<WatchEvent>) -> Result<Box<dyn WatchGuard>>;
}
pub trait WatchGuard: Send {}
pub struct NotifyEventSource;
impl EventSource for NotifyEventSource {
fn watch(&self, roots: &[PathBuf], tx: Sender<WatchEvent>) -> Result<Box<dyn WatchGuard>> {
let mut watcher =
notify::recommended_watcher(move |res: notify::Result<Event>| match res {
Ok(event) => translate_event(&event, &tx),
Err(err) => eprintln!("文件监听回调出错: {err}"),
})
.context("创建文件监听器失败")?;
for root in roots {
watcher
.watch(root, RecursiveMode::Recursive)
.with_context(|| format!("监听目录失败: {}", root.display()))?;
}
Ok(Box::new(NotifyGuard { _watcher: watcher }))
}
}
struct NotifyGuard {
_watcher: notify::RecommendedWatcher,
}
impl WatchGuard for NotifyGuard {}
fn translate_event(event: &Event, tx: &Sender<WatchEvent>) {
match &event.kind {
EventKind::Create(_) => {
for p in &event.paths {
emit_upsert(p, tx);
}
}
EventKind::Modify(ModifyKind::Name(mode)) => {
translate_rename(*mode, &event.paths, tx);
}
EventKind::Modify(_) => {
for p in &event.paths {
emit_upsert(p, tx);
}
}
EventKind::Remove(kind) => {
for p in &event.paths {
emit_remove(*kind, p, tx);
}
}
EventKind::Any => {
for p in &event.paths {
emit_best_effort(p, tx);
}
}
EventKind::Access(_) | EventKind::Other => {}
}
}
fn emit_upsert(path: &Path, tx: &Sender<WatchEvent>) {
if path.is_dir() {
let _ = tx.send(WatchEvent::UpsertDir(path.to_path_buf()));
} else {
let _ = tx.send(WatchEvent::Upsert(path.to_path_buf()));
}
}
fn emit_remove(kind: RemoveKind, path: &Path, tx: &Sender<WatchEvent>) {
match kind {
RemoveKind::File => {
let _ = tx.send(WatchEvent::Remove(path.to_path_buf()));
}
_ => {
let _ = tx.send(WatchEvent::RemoveDir(path.to_path_buf()));
}
}
}
fn translate_rename(mode: RenameMode, paths: &[PathBuf], tx: &Sender<WatchEvent>) {
match mode {
RenameMode::Both if paths.len() == 2 => {
let (from, to) = (&paths[0], &paths[1]);
if to.is_dir() {
let _ = tx.send(WatchEvent::RemoveDir(from.to_path_buf()));
emit_upsert(to, tx);
} else {
let _ = tx.send(WatchEvent::Rename {
from: from.to_path_buf(),
to: to.to_path_buf(),
});
}
}
RenameMode::From => {
for p in paths {
let _ = tx.send(WatchEvent::RemoveDir(p.to_path_buf()));
}
}
RenameMode::To => {
for p in paths {
emit_upsert(p, tx);
}
}
_ => {
if paths.len() == 2 {
translate_rename(RenameMode::Both, paths, tx);
} else {
for p in paths {
emit_best_effort(p, tx);
}
}
}
}
}
fn emit_best_effort(path: &Path, tx: &Sender<WatchEvent>) {
if path.exists() {
emit_upsert(path, tx);
} else {
let _ = tx.send(WatchEvent::RemoveDir(path.to_path_buf()));
}
}
#[derive(Debug, Clone)]
pub enum WatchProgress {
Received(WatchEvent),
Committed {
batch_size: usize,
outcome: BatchOutcome,
},
CommitFailed(String),
}
pub fn run_watch(
source: impl EventSource,
roots: &[PathBuf],
updater: Arc<Mutex<IndexUpdater>>,
stop: Arc<AtomicBool>,
mut on_progress: impl FnMut(WatchProgress),
) -> Result<()> {
let (tx, rx) = std::sync::mpsc::channel::<WatchEvent>();
let _guard = source.watch(roots, tx)?;
let mut debouncer = Debouncer::new(roots.to_vec());
let window = Duration::from_millis(QUIET_WINDOW_MS);
while !stop.load(Ordering::Relaxed) {
match rx.recv_timeout(window) {
Ok(event) => {
on_progress(WatchProgress::Received(event.clone()));
if debouncer.push(event) {
flush_batch(&mut debouncer, &updater, &mut on_progress);
}
}
Err(RecvTimeoutError::Timeout) => {
flush_batch(&mut debouncer, &updater, &mut on_progress);
}
Err(RecvTimeoutError::Disconnected) => break,
}
}
flush_batch(&mut debouncer, &updater, &mut on_progress);
Ok(())
}
fn flush_batch(
debouncer: &mut Debouncer,
updater: &Mutex<IndexUpdater>,
on_progress: &mut dyn FnMut(WatchProgress),
) {
if debouncer.is_empty() {
return;
}
let batch = debouncer.drain();
let batch_size = batch.len();
let mut guard = updater.lock().unwrap_or_else(|e| e.into_inner());
match guard.apply(&batch) {
Ok(outcome) => {
drop(guard);
on_progress(WatchProgress::Committed {
batch_size,
outcome,
});
}
Err(err) => {
drop(guard);
debouncer.requeue(batch);
on_progress(WatchProgress::CommitFailed(err.to_string()));
}
}
}
pub fn watch_roots_auto(
index_dir: &Path,
roots: &[PathBuf],
updater: Arc<Mutex<IndexUpdater>>,
stop: Arc<AtomicBool>,
on_progress: impl Fn(WatchProgress) + Send + Sync + 'static,
) -> Result<()> {
{
let mut guard = updater.lock().unwrap_or_else(|e| e.into_inner());
if let Err(err) = crate::reconcile::reconcile_orphans(roots, &mut guard) {
eprintln!("孤儿文档清理失败: {err}");
}
}
let mut fast_roots = Vec::new();
let mut slow_roots = Vec::new();
for root in roots {
match crate::volume::probe_root_capability(root) {
crate::volume::RootCapability::Fast { .. } => fast_roots.push(root.clone()),
crate::volume::RootCapability::Fallback { .. } => slow_roots.push(root.clone()),
}
}
#[cfg(windows)]
let volume_starts = if fast_roots.is_empty() {
None
} else {
match crate::usn::bootstrap_fast_roots(index_dir, &fast_roots, &updater) {
Ok(starts) => Some(starts),
Err(err) => {
eprintln!("快速路径初始化失败,本次运行整体退回 walkdir + notify: {err}");
slow_roots.append(&mut fast_roots);
None
}
}
};
#[cfg(not(windows))]
let volume_starts: Option<()> = None;
if fast_roots.is_empty() || volume_starts.is_none() {
for root in &slow_roots {
let mut guard = updater.lock().unwrap_or_else(|e| e.into_inner());
if let Err(err) = crate::reconcile::reconcile(root, &mut guard) {
eprintln!("启动对账 {} 失败: {err}", root.display());
}
}
return run_watch(NotifyEventSource, &slow_roots, updater, stop, move |p| {
on_progress(p)
});
}
#[cfg(not(windows))]
unreachable!("非 Windows 平台上面的 if 恒真,已在此之前 return");
#[cfg(windows)]
{
let on_progress = Arc::new(on_progress);
let slow_handle = if slow_roots.is_empty() {
None
} else {
let slow_roots = slow_roots.clone();
let updater = updater.clone();
let stop = stop.clone();
let on_progress = on_progress.clone();
Some(std::thread::spawn(move || {
for root in &slow_roots {
let mut guard = updater.lock().unwrap_or_else(|e| e.into_inner());
if let Err(err) = crate::reconcile::reconcile(root, &mut guard) {
eprintln!("启动对账 {} 失败: {err}", root.display());
}
}
let on_progress = on_progress.clone();
if let Err(err) =
run_watch(NotifyEventSource, &slow_roots, updater, stop, move |p| {
on_progress(p)
})
{
eprintln!("慢车道监听退出: {err}");
}
}))
};
let updater_for_fallback = updater.clone();
let stop_for_fallback = stop.clone();
let on_progress_for_fallback = on_progress.clone();
let fast_roots_for_fallback = fast_roots.clone();
let result = crate::usn::run_fast_lane(
index_dir,
&fast_roots,
volume_starts.expect("上面已经判空"),
updater,
stop,
on_progress,
);
let result = match result {
Ok(()) => Ok(()),
Err(err) => {
eprintln!("快车道启动失败,本次运行把这些根并入慢车道继续: {err}");
for root in &fast_roots_for_fallback {
let mut guard = updater_for_fallback
.lock()
.unwrap_or_else(|e| e.into_inner());
if let Err(err) = crate::reconcile::reconcile(root, &mut guard) {
eprintln!("启动对账 {} 失败: {err}", root.display());
}
}
run_watch(
NotifyEventSource,
&fast_roots_for_fallback,
updater_for_fallback,
stop_for_fallback,
move |p| on_progress_for_fallback(p),
)
}
};
if let Some(handle) = slow_handle {
let _ = handle.join();
}
result
}
}