directory_indexer/indexing/
monitor.rs1use log::{info, warn};
2use std::path::PathBuf;
3use std::time::Duration;
4use tokio::time::interval;
5
6use crate::error::Result;
7
8pub struct FileMonitor {
9 watched_directories: Vec<PathBuf>,
10 check_interval: Duration,
11}
12
13impl FileMonitor {
14 pub fn new(watched_directories: Vec<PathBuf>, check_interval: Duration) -> Self {
15 Self {
16 watched_directories,
17 check_interval,
18 }
19 }
20
21 pub async fn start_monitoring<F>(&self, _on_change: F) -> Result<()>
22 where
23 F: Fn(FileChangeEvent) + Send + Sync + 'static,
24 {
25 let dir_count = self.watched_directories.len();
26 info!("Starting file monitoring for {dir_count} directories");
27
28 let mut interval = interval(self.check_interval);
37
38 loop {
39 interval.tick().await;
40
41 warn!("File monitoring not yet implemented - this is a placeholder");
48
49 for dir in &self.watched_directories {
51 info!("Checking directory for changes: {dir:?}");
52 }
53 }
54 }
55
56 pub fn add_directory(&mut self, path: PathBuf) {
57 if !self.watched_directories.contains(&path) {
58 self.watched_directories.push(path);
59 }
60 }
61
62 pub fn remove_directory(&mut self, path: &PathBuf) {
63 self.watched_directories.retain(|p| p != path);
64 }
65}
66
67#[derive(Debug, Clone)]
68pub enum FileChangeEvent {
69 Created(PathBuf),
70 Modified(PathBuf),
71 Deleted(PathBuf),
72}
73
74impl FileChangeEvent {
75 pub fn path(&self) -> &PathBuf {
76 match self {
77 FileChangeEvent::Created(path) => path,
78 FileChangeEvent::Modified(path) => path,
79 FileChangeEvent::Deleted(path) => path,
80 }
81 }
82
83 pub fn event_type(&self) -> &'static str {
84 match self {
85 FileChangeEvent::Created(_) => "created",
86 FileChangeEvent::Modified(_) => "modified",
87 FileChangeEvent::Deleted(_) => "deleted",
88 }
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use std::time::Duration;
96
97 #[test]
98 fn test_file_monitor_creation() {
99 let directories = vec![PathBuf::from("/test/dir1"), PathBuf::from("/test/dir2")];
100 let interval = Duration::from_secs(5);
101
102 let monitor = FileMonitor::new(directories.clone(), interval);
103
104 assert_eq!(monitor.watched_directories, directories);
105 assert_eq!(monitor.check_interval, interval);
106 }
107
108 #[test]
109 fn test_file_monitor_add_directory() {
110 let initial_dirs = vec![PathBuf::from("/test/dir1")];
111 let mut monitor = FileMonitor::new(initial_dirs, Duration::from_secs(1));
112
113 let new_dir = PathBuf::from("/test/dir2");
114 monitor.add_directory(new_dir.clone());
115
116 assert_eq!(monitor.watched_directories.len(), 2);
117 assert!(monitor.watched_directories.contains(&new_dir));
118 }
119
120 #[test]
121 fn test_file_monitor_add_duplicate_directory() {
122 let dir = PathBuf::from("/test/dir");
123 let initial_dirs = vec![dir.clone()];
124 let mut monitor = FileMonitor::new(initial_dirs, Duration::from_secs(1));
125
126 monitor.add_directory(dir.clone());
128
129 assert_eq!(monitor.watched_directories.len(), 1);
130 assert_eq!(monitor.watched_directories[0], dir);
131 }
132
133 #[test]
134 fn test_file_monitor_remove_directory() {
135 let dir1 = PathBuf::from("/test/dir1");
136 let dir2 = PathBuf::from("/test/dir2");
137 let initial_dirs = vec![dir1.clone(), dir2.clone()];
138 let mut monitor = FileMonitor::new(initial_dirs, Duration::from_secs(1));
139
140 monitor.remove_directory(&dir1);
141
142 assert_eq!(monitor.watched_directories.len(), 1);
143 assert!(!monitor.watched_directories.contains(&dir1));
144 assert!(monitor.watched_directories.contains(&dir2));
145 }
146
147 #[test]
148 fn test_file_monitor_remove_nonexistent_directory() {
149 let dir1 = PathBuf::from("/test/dir1");
150 let initial_dirs = vec![dir1.clone()];
151 let mut monitor = FileMonitor::new(initial_dirs, Duration::from_secs(1));
152
153 let nonexistent = PathBuf::from("/test/nonexistent");
154 monitor.remove_directory(&nonexistent);
155
156 assert_eq!(monitor.watched_directories.len(), 1);
158 assert!(monitor.watched_directories.contains(&dir1));
159 }
160
161 #[test]
162 fn test_file_change_event_created() {
163 let path = PathBuf::from("/test/file.txt");
164 let event = FileChangeEvent::Created(path.clone());
165
166 assert_eq!(event.path(), &path);
167 assert_eq!(event.event_type(), "created");
168 }
169
170 #[test]
171 fn test_file_change_event_modified() {
172 let path = PathBuf::from("/test/file.txt");
173 let event = FileChangeEvent::Modified(path.clone());
174
175 assert_eq!(event.path(), &path);
176 assert_eq!(event.event_type(), "modified");
177 }
178
179 #[test]
180 fn test_file_change_event_deleted() {
181 let path = PathBuf::from("/test/file.txt");
182 let event = FileChangeEvent::Deleted(path.clone());
183
184 assert_eq!(event.path(), &path);
185 assert_eq!(event.event_type(), "deleted");
186 }
187
188 #[test]
189 fn test_file_change_event_clone() {
190 let path = PathBuf::from("/test/file.txt");
191 let event = FileChangeEvent::Created(path.clone());
192 let cloned = event.clone();
193
194 assert_eq!(event.path(), cloned.path());
195 assert_eq!(event.event_type(), cloned.event_type());
196 }
197
198 #[test]
199 fn test_file_change_event_debug() {
200 let path = PathBuf::from("/test/file.txt");
201 let event = FileChangeEvent::Modified(path);
202
203 let debug_output = format!("{event:?}");
204 assert!(debug_output.contains("Modified"));
205 assert!(debug_output.contains("file.txt"));
206 }
207
208 #[test]
209 fn test_empty_monitor() {
210 let monitor = FileMonitor::new(vec![], Duration::from_millis(100));
211
212 assert!(monitor.watched_directories.is_empty());
213 assert_eq!(monitor.check_interval, Duration::from_millis(100));
214 }
215
216 #[test]
217 fn test_monitor_configuration_variations() {
218 let configs = vec![
220 Duration::from_millis(100),
221 Duration::from_secs(1),
222 Duration::from_secs(60),
223 ];
224
225 for interval in configs {
226 let monitor = FileMonitor::new(vec![], interval);
227 assert_eq!(monitor.check_interval, interval);
228 }
229 }
230
231 #[test]
232 fn test_path_operations() {
233 let mut monitor = FileMonitor::new(vec![], Duration::from_secs(1));
234
235 let paths = vec![
236 PathBuf::from("/home/user/documents"),
237 PathBuf::from("/var/log"),
238 PathBuf::from("/tmp/test"),
239 ];
240
241 for path in &paths {
243 monitor.add_directory(path.clone());
244 }
245
246 assert_eq!(monitor.watched_directories.len(), 3);
247
248 monitor.remove_directory(&paths[1]);
250 assert_eq!(monitor.watched_directories.len(), 2);
251 assert!(!monitor.watched_directories.contains(&paths[1]));
252
253 assert!(monitor.watched_directories.contains(&paths[0]));
255 assert!(monitor.watched_directories.contains(&paths[2]));
256 }
257
258 #[test]
259 fn test_file_change_event_pattern_matching() {
260 let path = PathBuf::from("/test/file.txt");
261
262 let events = vec![
263 FileChangeEvent::Created(path.clone()),
264 FileChangeEvent::Modified(path.clone()),
265 FileChangeEvent::Deleted(path.clone()),
266 ];
267
268 for event in events {
269 match &event {
270 FileChangeEvent::Created(p) => {
271 assert_eq!(p, &path);
272 assert_eq!(event.event_type(), "created");
273 }
274 FileChangeEvent::Modified(p) => {
275 assert_eq!(p, &path);
276 assert_eq!(event.event_type(), "modified");
277 }
278 FileChangeEvent::Deleted(p) => {
279 assert_eq!(p, &path);
280 assert_eq!(event.event_type(), "deleted");
281 }
282 }
283 }
284 }
285
286 #[tokio::test]
288 async fn test_monitoring_placeholder() {
289 let monitor = FileMonitor::new(vec![PathBuf::from("/test")], Duration::from_millis(10));
290
291 let result = tokio::time::timeout(
294 Duration::from_millis(50),
295 monitor.start_monitoring(|_event| {
296 }),
298 )
299 .await;
300
301 assert!(result.is_err());
303 }
304
305 #[test]
306 fn test_monitor_with_relative_paths() {
307 let relative_paths = vec![
308 PathBuf::from("./documents"),
309 PathBuf::from("../parent"),
310 PathBuf::from("subdir/nested"),
311 ];
312
313 let monitor = FileMonitor::new(relative_paths.clone(), Duration::from_secs(1));
314
315 assert_eq!(monitor.watched_directories, relative_paths);
316 }
317
318 #[test]
319 fn test_monitor_with_unicode_paths() {
320 let unicode_paths = vec![
321 PathBuf::from("/test/файл.txt"),
322 PathBuf::from("/test/文件.txt"),
323 PathBuf::from("/test/🦀.rs"),
324 ];
325
326 let monitor = FileMonitor::new(unicode_paths.clone(), Duration::from_secs(1));
327
328 assert_eq!(monitor.watched_directories, unicode_paths);
329 }
330}