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}