fix(watcher): filter Remove events by path to prevent false removed reports (closes #15)

This commit is contained in:
dailz
2026-06-09 13:18:23 +08:00
parent 7852e92ecc
commit 420b853cb9

View File

@@ -35,6 +35,43 @@ struct WatchState {
last_inode: u64,
}
fn process_event(event: Event, watch_path: &Path, state: &mut WatchState) -> Option<FileEvent> {
if !event.paths.iter().any(|p| p == watch_path) {
return None;
}
match event.kind {
EventKind::Modify(_) | EventKind::Create(_) | EventKind::Any => {}
EventKind::Remove(_) => return Some(FileEvent::Removed),
_ => return None,
}
let current_inode = get_inode(watch_path).unwrap_or(0);
let current_size = std::fs::metadata(watch_path).map(|m| m.len()).unwrap_or(0);
if current_inode != 0 && state.last_inode != 0 && current_inode != state.last_inode {
state.last_inode = current_inode;
state.last_size = current_size;
Some(FileEvent::Rotated {
new_inode: current_inode,
})
} else if current_size > state.last_size {
state.last_size = current_size;
state.last_inode = current_inode;
Some(FileEvent::Appended {
new_size: current_size,
})
} else if current_size < state.last_size {
state.last_size = current_size;
state.last_inode = current_inode;
Some(FileEvent::Truncated {
new_size: current_size,
})
} else {
None
}
}
// ─── FileWatcher ────────────────────────────────────────────────────────────
pub struct FileWatcher {
rx: Receiver<FileEvent>,
@@ -65,47 +102,14 @@ impl FileWatcher {
}
};
match event.kind {
EventKind::Modify(_) | EventKind::Create(_) | EventKind::Any => {}
EventKind::Remove(_) => {
let _ = tx.try_send(FileEvent::Removed);
return;
}
_ => return,
}
if !event.paths.iter().any(|p| p == &watch_path) {
return;
}
let current_inode = get_inode(&watch_path).unwrap_or(0);
let current_size = std::fs::metadata(&watch_path).map(|m| m.len()).unwrap_or(0);
let mut st = state.lock().unwrap_or_else(|poison| {
// Recover from poisoned mutex — state only tracks last_size
// and last_inode for event dedup. Stale values at worst
// cause a duplicate event, which is harmless.
poison.into_inner()
});
if current_inode != 0 && st.last_inode != 0 && current_inode != st.last_inode {
let _ = tx.try_send(FileEvent::Rotated {
new_inode: current_inode,
});
st.last_inode = current_inode;
st.last_size = current_size;
} else if current_size > st.last_size {
let _ = tx.try_send(FileEvent::Appended {
new_size: current_size,
});
st.last_size = current_size;
st.last_inode = current_inode;
} else if current_size < st.last_size {
let _ = tx.try_send(FileEvent::Truncated {
new_size: current_size,
});
st.last_size = current_size;
st.last_inode = current_inode;
if let Some(fe) = process_event(event, &watch_path, &mut st) {
let _ = tx.try_send(fe);
}
})?;
@@ -150,6 +154,56 @@ mod tests {
events
}
#[test]
fn test_remove_wrong_path_ignored() {
let dir = tempfile::tempdir().expect("create temp dir");
let watched = dir.path().join("watched.log");
let other = dir.path().join("other.log");
std::fs::write(&watched, b"hello\n").expect("write watched");
std::fs::write(&other, b"other\n").expect("write other");
let mut state = WatchState {
last_size: 6,
last_inode: get_inode(&watched).unwrap_or(0),
};
let event = Event {
kind: EventKind::Remove(notify::event::RemoveKind::File),
paths: vec![other.clone()],
attrs: Default::default(),
};
let result = process_event(event, &watched, &mut state);
assert_eq!(
result, None,
"Remove for non-watched path should be ignored"
);
}
#[test]
fn test_remove_correct_path_emits_removed() {
let dir = tempfile::tempdir().expect("create temp dir");
let watched = dir.path().join("watched.log");
std::fs::write(&watched, b"hello\n").expect("write watched");
let mut state = WatchState {
last_size: 6,
last_inode: get_inode(&watched).unwrap_or(0),
};
let event = Event {
kind: EventKind::Remove(notify::event::RemoveKind::File),
paths: vec![watched.clone()],
attrs: Default::default(),
};
let result = process_event(event, &watched, &mut state);
assert!(
matches!(result, Some(FileEvent::Removed)),
"Remove for watched path should emit Removed"
);
}
#[test]
fn test_watcher_append() {
let dir = tempfile::tempdir().expect("create temp dir");
@@ -253,4 +307,4 @@ mod tests {
}
);
}
}
}