file_update_monitor/lib.rs
1//! # File Update Monitor
2//!
3//! `file_update_monitor` is a library for monitoring file changes. It provides a simple interface to watch
4//! file changes in directories and execute custom callback functions when file content changes.
5//!
6//! ## Main Features
7//!
8//! - Monitor file changes in specified directories and subdirectories
9//! - Support debouncing to avoid too frequent updates
10//! - Asynchronous handling of file change events
11//! - Customizable logic for handling file changes
12//!
13//! ## Example
14//!
15//! ```rust
16//! use file_update_monitor::Monitor;
17//! use std::error::Error;
18//!
19//! #[tokio::main]
20//! async fn main() -> Result<(), Box<dyn Error>> {
21//! // 创建一个监控器实例,监控当前目录,更新间隔为1秒
22//! let monitor = Monitor::new("./", 1000, |path| {
23//! println!("检测到文件变化: {}", path);
24//! Ok(())
25//! });
26//!
27//! // 启动监控
28//! monitor.start().await;
29//! Ok(())
30//! }
31//! ```
32
33use debounce::EventDebouncer;
34use futures::{
35 channel::mpsc::{channel, Receiver},
36 SinkExt, StreamExt,
37};
38use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher};
39use std::error;
40use std::{path::Path, sync::Arc, time::Duration};
41
42/// Generic result type for error handling
43type Result<T> = std::result::Result<T, Box<dyn error::Error>>;
44
45/// Main struct for file monitoring
46pub struct Monitor {
47 /// Directory path to monitor
48 dir: String,
49 /// Update interval in milliseconds
50 update_interval: u64,
51 /// Callback function for file changes
52 on_change: Arc<Box<dyn Fn(String) -> Result<()> + Send + Sync>>,
53}
54
55impl Monitor {
56 /// Create a new monitor instance
57 ///
58 /// # Arguments
59 ///
60 /// * `dir` - Directory path to monitor
61 /// * `update_interval` - Update interval in milliseconds
62 /// * `on_change` - Callback function called when files change
63 ///
64 /// # Example
65 ///
66 /// ```
67 /// use file_update_monitor::Monitor;
68 ///
69 /// let monitor = Monitor::new("./", 1000, |path| {
70 /// println!("File changed: {}", path);
71 /// Ok(())
72 /// });
73 /// ```
74 pub fn new<F>(dir: &str, update_interval: u64, on_change: F) -> Self
75 where
76 F: Fn(String) -> Result<()> + Send + Sync + 'static,
77 {
78 Self {
79 dir: dir.to_string(),
80 update_interval,
81 on_change: Arc::new(Box::new(on_change)),
82 }
83 }
84
85 /// Start file monitoring
86 ///
87 /// This is an async method that will continue monitoring the specified directory until program termination
88 pub async fn start(&self) {
89 if let Err(e) = self.watch_directory().await {
90 eprintln!("File monitoring error: {:?}", e);
91 }
92 }
93
94 /// Internal method: implements core directory monitoring logic
95 async fn watch_directory(&self) -> Result<()> {
96 let delay = Duration::from_millis(self.update_interval);
97 let on_change = self.on_change.clone();
98 let debouncer = EventDebouncer::new(delay, move |path: String| on_change(path).unwrap());
99
100 let (mut watcher, mut rx) = self.create_watcher()?;
101 watcher.watch(Path::new(&self.dir), RecursiveMode::Recursive)?;
102
103 while let Some(res) = rx.next().await {
104 match res {
105 Ok(event) => {
106 if let Some(paths) = self.get_valid_paths(event) {
107 for path in paths {
108 debouncer.put(path);
109 }
110 }
111 }
112 Err(e) => eprintln!("File monitoring error: {:?}", e),
113 }
114 }
115
116 Ok(())
117 }
118
119 /// Create filesystem event watcher
120 fn create_watcher(
121 &self,
122 ) -> notify::Result<(RecommendedWatcher, Receiver<notify::Result<Event>>)> {
123 let (mut tx, rx) = channel(1);
124
125 let watcher = RecommendedWatcher::new(
126 move |res| {
127 futures::executor::block_on(async {
128 if let Err(e) = tx.send(res).await {
129 eprintln!("Error sending event: {:?}", e);
130 }
131 })
132 },
133 Config::default(),
134 )?;
135
136 Ok((watcher, rx))
137 }
138
139 /// Extract valid file path from filesystem event
140 ///
141 /// Only processes file content modification events, ignores other types of events
142 fn get_valid_paths(&self, event: Event) -> Option<Vec<String>> {
143 if !matches!(
144 event.kind,
145 notify::EventKind::Modify(notify::event::ModifyKind::Data(
146 notify::event::DataChange::Content
147 ))
148 ) {
149 return None;
150 }
151
152 Some(
153 event
154 .paths
155 .iter()
156 .map(|path| path.to_str().unwrap().to_string())
157 .collect(),
158 )
159 }
160}