Rust好库-notify

notify 主要用于文件系统监控,可以让你的应用程序监听文件或目录的变化。当需要在文件发生变化(如创建、删除、修改等)时得到通知,这个库就非常有用。notify 支持多种操作系统,并且在内部使用了特定平台的文件系统监控机制,比如在 Linux 上使用 inotify,在 macOS 上使用 FSEvents,在 Windows 上则使用 ReadDirectoryChangesW API。

库地址: https://github.com/notify-rs/notify

1 版本features说明

版本 描述 使用场景
notify 核心模块,提供基本的文件系统事件监听功能。内建去抖动逻辑。 适合一般用途的文件系统监控,不需要额外的去抖动特性。
notify_debouncer_mini 轻量级去抖动实现,提供简单而有效的去抖动功能。 性能敏感的应用场景,希望保持依赖关系简单。
notify_debouncer_full 全功能去抖动解决方案,提供更复杂的逻辑来处理各种情况下的去抖动需求。 需要更强大、更灵活去抖动功能的情况,或在复杂环境中运行的应用程序。

说明:

  • 去抖动:减少由于短时间内频繁触发事件而导致的重复或不必要的通知。
  • 性能敏感:对于那些对执行效率有较高要求的应用程序。
  • 复杂环境:指涉及多种类型的文件系统事件以及需要正确合并或过滤这些事件的情况。

debouncer 的作用:

  • 如果有多次重命名, 仅发出单个重命名事件
  • 合并多个重命名事件
  • 考虑重命名事件并更新重命名事件之前发生但尚未发出的事件的路径
    (可选)跟踪文件系统 ID、所有文件和整合重命名事件(FSevents、Windows)
  • 删除目录时仅发出一个删除事件 (inotify)
  • 不发出重复的创建事件
  • 创建事件后不发出修改事件

2 实战案例

这里最简单的应用就是创建一个文件缓存索引, 当监听到文件变化时, 惰性建立缓存索引

完整的代码(包括客户端应用、数据库持久化、CRUD等)请参考 file_elf, 这里给出监听的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
mod util;
pub mod writer;

use std::path::PathBuf;
use std::sync::mpsc::Sender;
use std::{sync::mpsc, time::Duration};

use log::{debug, error, info, trace};
use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
use writer::DbAction;

use crate::db::DB;

use crate::util::is_blacklisted;

pub fn file_checker(target: &str, db_sender: mpsc::Sender<DbAction>) {
let (tx, rx) = mpsc::channel();
let mut watcher: RecommendedWatcher = Watcher::new(tx, Duration::from_secs(1)).unwrap(); // 制定监听器和通知间隔时间

// 监听指定目录 target
watcher
.watch(target, RecursiveMode::Recursive) // RecursiveMode表示递归监听子文件夹
.expect("Failed to watch directory");

info!("Watching directory: {} for changes...", target);

loop {
match rx.recv() {
Ok(event) => match event {
DebouncedEvent::Create(path) | DebouncedEvent::Write(path) => {
if is_blacklisted(&path) {
trace!("File: {:#?} is blacklisted, ignoring...", path);
continue;
}
new_event_handler(&path, &db_sender);
}
DebouncedEvent::Remove(path) => {
if is_blacklisted(&path) {
trace!("File: {:#?} is blacklisted, ignoring...", path);
continue;
}
del_event_handler(&path, &db_sender);
}
DebouncedEvent::Rename(old_path, new_path) => {
if is_blacklisted(&old_path) {
trace!("File: {:#?} is blacklisted, ignoring...", old_path);
continue;
}
info!("File renamed from {:?} to {:?}", old_path, new_path);
del_event_handler(&old_path, &db_sender);
new_event_handler(&new_path, &db_sender);
}
DebouncedEvent::NoticeWrite(path) => {
if is_blacklisted(&path) {
trace!("File: {:#?} is blacklisted, ignoring...", path);
continue;
}
info!("File notice write: {:?}", path);
}
DebouncedEvent::NoticeRemove(path) => {
if is_blacklisted(&path) {
trace!("File: {:#?} is blacklisted, ignoring...", path);
continue;
}
info!("File notice remove: {:?}", path);
}
DebouncedEvent::Chmod(path) => {
if is_blacklisted(&path) {
trace!("File: {:#?} is blacklisted, ignoring...", path);
continue;
}
info!("File permissions changed: {:?}", path);
}
DebouncedEvent::Rescan => {
info!("Directory rescan occurred.");
}
DebouncedEvent::Error(err, path) => {
error!("An error occurred: {:?}, related path {:#?}", err, path);
}
},
Err(e) => error!("watch error: {:?}", e),
}
}
}

pub fn new_event_handler(path: &PathBuf, db_sender: &Sender<DbAction>) {
// CRUD、持久化等操作
}

#[allow(unused)]
fn del_event_handler(path: &PathBuf, db_sender: &Sender<DbAction>) {
// CRUD、持久化等操作
}

3 去抖动器debouncer的使用

先看官方代码:

1
2
3
4
5
6
7
8
9
10
use notify_debouncer_full::{notify::*, new_debouncer, DebounceEventResult};

// Select recommended watcher for debouncer.
// Using a callback here, could also be a channel.
let mut debouncer = new_debouncer(Duration::from_secs(2), None, |result: DebounceEventResult| {
match result {
Ok(events) => events.iter().for_each(|event| println!("{event:?}")),
Err(errors) => errors.iter().for_each(|error| println!("{error:?}")),
}
}).unwrap();

具体进行分析:

  • DebounceEventResult 是去抖动结果的类型。
  • new_debouncer(Duration::from_secs(2), None, ...) 创建一个新的去抖动器实例,设置去抖动延迟时间为2秒,并传入一个回调函数。
  • 回调函数接收一个 DebounceEventResult 类型的结果,该结果表示去抖动后的事件集合或错误集合。
  • Ok(events) 表示成功处理事件,打印每个事件。
1
debouncer.watcher().watch(Path::new("."), RecursiveMode::Recursive).unwrap();

这行代码添加一个路径到去抖动器的监听列表中:

  • debouncer.watcher() 获取底层的 Watcher 实例。
  • watch(Path::new("."), RecursiveMode::Recursive) 监听当前目录及其所有子目录的文件系统变化。
  • unwrap() 确保监听操作成功,否则抛出错误。
1
debouncer.cache().add_root(Path::new("."), RecursiveMode::Recursive);

这行代码向去抖动器的文件 ID 缓存中添加相同的路径:

  • debouncer.cache() 获取去抖动器的缓存实例。
  • add_root(Path::new("."), RecursiveMode::Recursive) 将当前目录及其所有子目录添加到文件 ID 缓存中。
  • 文件 ID 缓存利用文件系统提供的唯一文件 ID 来组合重命名事件,特别是在通知后端不提供重命名标记的情况下。

这里对watcher()cache()进行特殊的区别说明

  • watcher() 方法返回的是底层的 Watcher 实例,它负责实际的文件系统监听工作。
  • cache() 方法返回的是文件 ID 缓存组件,它主要用于处理文件系统的重命名事件。帮助正确识别和处理重命名事件: