ricecoder_hooks/events/
monitor.rs

1//! File system monitoring for file operation events
2//!
3//! This module provides file system monitoring capabilities to detect and emit events
4//! for file operations including create, modify, delete, rename, move, and read operations.
5//!
6//! # Examples
7//!
8//! Creating a file monitor:
9//! ```ignore
10//! use ricecoder_hooks::events::FileSystemMonitor;
11//! use std::path::PathBuf;
12//!
13//! let monitor = FileSystemMonitor::new();
14//! monitor.start_monitoring(PathBuf::from("/path/to/watch")).await?;
15//! ```
16
17use super::file_operations::{DirectoryOperationEvent, FileOperationEvent};
18use std::collections::HashMap;
19use std::path::PathBuf;
20use std::sync::Arc;
21use std::time::SystemTime;
22use tokio::sync::RwLock;
23
24/// File system monitor for detecting file operations
25///
26/// Monitors file system for create, modify, delete, rename, move, and read operations.
27/// Emits events when operations are detected.
28#[derive(Debug, Clone)]
29pub struct FileSystemMonitor {
30    /// Tracked file hashes for detecting modifications
31    file_hashes: Arc<RwLock<HashMap<PathBuf, String>>>,
32}
33
34impl FileSystemMonitor {
35    /// Create a new file system monitor
36    pub fn new() -> Self {
37        Self {
38            file_hashes: Arc::new(RwLock::new(HashMap::new())),
39        }
40    }
41
42    /// Emit a file created event
43    ///
44    /// # Arguments
45    ///
46    /// * `path` - Path to the created file
47    /// * `size` - Size of the file in bytes
48    ///
49    /// # Returns
50    ///
51    /// A `FileOperationEvent::Created` event
52    pub fn emit_file_created(&self, path: PathBuf, size: u64) -> FileOperationEvent {
53        FileOperationEvent::Created {
54            path,
55            size,
56            timestamp: SystemTime::now(),
57        }
58    }
59
60    /// Emit a file modified event
61    ///
62    /// # Arguments
63    ///
64    /// * `path` - Path to the modified file
65    /// * `old_hash` - Hash of the file before modification
66    /// * `new_hash` - Hash of the file after modification
67    ///
68    /// # Returns
69    ///
70    /// A `FileOperationEvent::Modified` event
71    pub fn emit_file_modified(
72        &self,
73        path: PathBuf,
74        old_hash: String,
75        new_hash: String,
76    ) -> FileOperationEvent {
77        FileOperationEvent::Modified {
78            path,
79            old_hash,
80            new_hash,
81            timestamp: SystemTime::now(),
82        }
83    }
84
85    /// Emit a file deleted event
86    ///
87    /// # Arguments
88    ///
89    /// * `path` - Path to the deleted file
90    ///
91    /// # Returns
92    ///
93    /// A `FileOperationEvent::Deleted` event
94    pub fn emit_file_deleted(&self, path: PathBuf) -> FileOperationEvent {
95        FileOperationEvent::Deleted {
96            path,
97            timestamp: SystemTime::now(),
98        }
99    }
100
101    /// Emit a file renamed event
102    ///
103    /// # Arguments
104    ///
105    /// * `old_path` - Original path of the file
106    /// * `new_path` - New path of the file
107    ///
108    /// # Returns
109    ///
110    /// A `FileOperationEvent::Renamed` event
111    pub fn emit_file_renamed(&self, old_path: PathBuf, new_path: PathBuf) -> FileOperationEvent {
112        FileOperationEvent::Renamed {
113            old_path,
114            new_path,
115            timestamp: SystemTime::now(),
116        }
117    }
118
119    /// Emit a file moved event
120    ///
121    /// # Arguments
122    ///
123    /// * `old_path` - Original path of the file
124    /// * `new_path` - New path of the file
125    ///
126    /// # Returns
127    ///
128    /// A `FileOperationEvent::Moved` event
129    pub fn emit_file_moved(&self, old_path: PathBuf, new_path: PathBuf) -> FileOperationEvent {
130        FileOperationEvent::Moved {
131            old_path,
132            new_path,
133            timestamp: SystemTime::now(),
134        }
135    }
136
137    /// Emit a file read event
138    ///
139    /// # Arguments
140    ///
141    /// * `path` - Path to the file that was read
142    ///
143    /// # Returns
144    ///
145    /// A `FileOperationEvent::Read` event
146    pub fn emit_file_read(&self, path: PathBuf) -> FileOperationEvent {
147        FileOperationEvent::Read {
148            path,
149            timestamp: SystemTime::now(),
150        }
151    }
152
153    /// Emit a directory created event
154    ///
155    /// # Arguments
156    ///
157    /// * `path` - Path to the created directory
158    ///
159    /// # Returns
160    ///
161    /// A `DirectoryOperationEvent::Created` event
162    pub fn emit_directory_created(&self, path: PathBuf) -> DirectoryOperationEvent {
163        DirectoryOperationEvent::Created {
164            path,
165            timestamp: SystemTime::now(),
166        }
167    }
168
169    /// Emit a directory deleted event
170    ///
171    /// # Arguments
172    ///
173    /// * `path` - Path to the deleted directory
174    ///
175    /// # Returns
176    ///
177    /// A `DirectoryOperationEvent::Deleted` event
178    pub fn emit_directory_deleted(&self, path: PathBuf) -> DirectoryOperationEvent {
179        DirectoryOperationEvent::Deleted {
180            path,
181            timestamp: SystemTime::now(),
182        }
183    }
184
185    /// Track a file hash for modification detection
186    ///
187    /// # Arguments
188    ///
189    /// * `path` - Path to the file
190    /// * `hash` - Hash of the file content
191    pub async fn track_file_hash(&self, path: PathBuf, hash: String) {
192        let mut hashes = self.file_hashes.write().await;
193        hashes.insert(path, hash);
194    }
195
196    /// Get the tracked hash for a file
197    ///
198    /// # Arguments
199    ///
200    /// * `path` - Path to the file
201    ///
202    /// # Returns
203    ///
204    /// The tracked hash if it exists
205    pub async fn get_file_hash(&self, path: &PathBuf) -> Option<String> {
206        let hashes = self.file_hashes.read().await;
207        hashes.get(path).cloned()
208    }
209
210    /// Remove a tracked file hash
211    ///
212    /// # Arguments
213    ///
214    /// * `path` - Path to the file
215    pub async fn remove_file_hash(&self, path: &PathBuf) {
216        let mut hashes = self.file_hashes.write().await;
217        hashes.remove(path);
218    }
219
220    /// Clear all tracked file hashes
221    pub async fn clear_file_hashes(&self) {
222        let mut hashes = self.file_hashes.write().await;
223        hashes.clear();
224    }
225}
226
227impl Default for FileSystemMonitor {
228    fn default() -> Self {
229        Self::new()
230    }
231}
232
233#[cfg(test)]
234mod tests {
235    use super::*;
236
237    #[test]
238    fn test_emit_file_created() {
239        let monitor = FileSystemMonitor::new();
240        let path = PathBuf::from("/path/to/file.rs");
241        let event = monitor.emit_file_created(path.clone(), 1024);
242
243        match event {
244            FileOperationEvent::Created { path: p, size, .. } => {
245                assert_eq!(p, path);
246                assert_eq!(size, 1024);
247            }
248            _ => panic!("Expected Created event"),
249        }
250    }
251
252    #[test]
253    fn test_emit_file_modified() {
254        let monitor = FileSystemMonitor::new();
255        let path = PathBuf::from("/path/to/file.rs");
256        let event =
257            monitor.emit_file_modified(path.clone(), "abc123".to_string(), "def456".to_string());
258
259        match event {
260            FileOperationEvent::Modified {
261                path: p,
262                old_hash,
263                new_hash,
264                ..
265            } => {
266                assert_eq!(p, path);
267                assert_eq!(old_hash, "abc123");
268                assert_eq!(new_hash, "def456");
269            }
270            _ => panic!("Expected Modified event"),
271        }
272    }
273
274    #[test]
275    fn test_emit_file_deleted() {
276        let monitor = FileSystemMonitor::new();
277        let path = PathBuf::from("/path/to/file.rs");
278        let event = monitor.emit_file_deleted(path.clone());
279
280        match event {
281            FileOperationEvent::Deleted { path: p, .. } => {
282                assert_eq!(p, path);
283            }
284            _ => panic!("Expected Deleted event"),
285        }
286    }
287
288    #[test]
289    fn test_emit_file_renamed() {
290        let monitor = FileSystemMonitor::new();
291        let old_path = PathBuf::from("/path/to/old.rs");
292        let new_path = PathBuf::from("/path/to/new.rs");
293        let event = monitor.emit_file_renamed(old_path.clone(), new_path.clone());
294
295        match event {
296            FileOperationEvent::Renamed {
297                old_path: op,
298                new_path: np,
299                ..
300            } => {
301                assert_eq!(op, old_path);
302                assert_eq!(np, new_path);
303            }
304            _ => panic!("Expected Renamed event"),
305        }
306    }
307
308    #[test]
309    fn test_emit_file_moved() {
310        let monitor = FileSystemMonitor::new();
311        let old_path = PathBuf::from("/old/path/file.rs");
312        let new_path = PathBuf::from("/new/path/file.rs");
313        let event = monitor.emit_file_moved(old_path.clone(), new_path.clone());
314
315        match event {
316            FileOperationEvent::Moved {
317                old_path: op,
318                new_path: np,
319                ..
320            } => {
321                assert_eq!(op, old_path);
322                assert_eq!(np, new_path);
323            }
324            _ => panic!("Expected Moved event"),
325        }
326    }
327
328    #[test]
329    fn test_emit_file_read() {
330        let monitor = FileSystemMonitor::new();
331        let path = PathBuf::from("/path/to/file.rs");
332        let event = monitor.emit_file_read(path.clone());
333
334        match event {
335            FileOperationEvent::Read { path: p, .. } => {
336                assert_eq!(p, path);
337            }
338            _ => panic!("Expected Read event"),
339        }
340    }
341
342    #[test]
343    fn test_emit_directory_created() {
344        let monitor = FileSystemMonitor::new();
345        let path = PathBuf::from("/path/to/dir");
346        let event = monitor.emit_directory_created(path.clone());
347
348        match event {
349            DirectoryOperationEvent::Created { path: p, .. } => {
350                assert_eq!(p, path);
351            }
352            _ => panic!("Expected Created event"),
353        }
354    }
355
356    #[test]
357    fn test_emit_directory_deleted() {
358        let monitor = FileSystemMonitor::new();
359        let path = PathBuf::from("/path/to/dir");
360        let event = monitor.emit_directory_deleted(path.clone());
361
362        match event {
363            DirectoryOperationEvent::Deleted { path: p, .. } => {
364                assert_eq!(p, path);
365            }
366            _ => panic!("Expected Deleted event"),
367        }
368    }
369
370    #[tokio::test]
371    async fn test_track_file_hash() {
372        let monitor = FileSystemMonitor::new();
373        let path = PathBuf::from("/path/to/file.rs");
374        let hash = "abc123".to_string();
375
376        monitor.track_file_hash(path.clone(), hash.clone()).await;
377        let tracked = monitor.get_file_hash(&path).await;
378
379        assert_eq!(tracked, Some(hash));
380    }
381
382    #[tokio::test]
383    async fn test_remove_file_hash() {
384        let monitor = FileSystemMonitor::new();
385        let path = PathBuf::from("/path/to/file.rs");
386        let hash = "abc123".to_string();
387
388        monitor.track_file_hash(path.clone(), hash).await;
389        monitor.remove_file_hash(&path).await;
390        let tracked = monitor.get_file_hash(&path).await;
391
392        assert_eq!(tracked, None);
393    }
394
395    #[tokio::test]
396    async fn test_clear_file_hashes() {
397        let monitor = FileSystemMonitor::new();
398        let path1 = PathBuf::from("/path/to/file1.rs");
399        let path2 = PathBuf::from("/path/to/file2.rs");
400
401        monitor
402            .track_file_hash(path1.clone(), "hash1".to_string())
403            .await;
404        monitor
405            .track_file_hash(path2.clone(), "hash2".to_string())
406            .await;
407
408        monitor.clear_file_hashes().await;
409
410        assert_eq!(monitor.get_file_hash(&path1).await, None);
411        assert_eq!(monitor.get_file_hash(&path2).await, None);
412    }
413
414    #[test]
415    fn test_file_system_monitor_default() {
416        let monitor = FileSystemMonitor::default();
417        let path = PathBuf::from("/path/to/file.rs");
418        let event = monitor.emit_file_created(path.clone(), 512);
419
420        match event {
421            FileOperationEvent::Created { size, .. } => {
422                assert_eq!(size, 512);
423            }
424            _ => panic!("Expected Created event"),
425        }
426    }
427}