1use std::collections::HashSet;
4use std::path::{Path, PathBuf};
5
6use notify::Config as NotifyConfig;
7use notify::{Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
8
9use crate::error::{Error, Result};
10use crate::path_utils;
11
12pub struct WatcherConfig {
13 pub debounce_ms: u64,
14 pub packages_dir: PathBuf,
15}
16
17impl Default for WatcherConfig {
18 fn default() -> Self {
19 Self {
20 debounce_ms: 300,
21 packages_dir: PathBuf::from("./packages"),
22 }
23 }
24}
25
26pub struct FileWatcher {
27 watcher: RecommendedWatcher,
28 receiver: std::sync::mpsc::Receiver<notify::Result<Event>>,
29 config: WatcherConfig,
30}
31
32impl FileWatcher {
33 pub fn new(config: WatcherConfig) -> Result<Self> {
34 let (tx, rx) = std::sync::mpsc::channel();
35 let notify_config = NotifyConfig::default();
36
37 let watcher = RecommendedWatcher::new(
38 move |res| {
39 if let Err(e) = tx.send(res) {
40 eprintln!("Failed to send watcher event: {}", e);
41 }
42 },
43 notify_config,
44 )
45 .map_err(|e| Error::Adapter {
46 package: "watcher".to_string(),
47 message: format!("Failed to create watcher: {}", e),
48 })?;
49
50 let mut file_watcher = Self {
51 watcher,
52 receiver: rx,
53 config,
54 };
55
56 file_watcher.watch_packages_dir()?;
57
58 Ok(file_watcher)
59 }
60
61 fn watch_packages_dir(&mut self) -> Result<()> {
62 self.watcher
63 .watch(&self.config.packages_dir, RecursiveMode::Recursive)
64 .map_err(|e| Error::Adapter {
65 package: "watcher".to_string(),
66 message: format!("Failed to watch directory: {}", e),
67 })?;
68 Ok(())
69 }
70
71 pub fn next_event(&mut self) -> Result<Option<Event>> {
72 match self.receiver.try_recv() {
73 Ok(Ok(event)) => Ok(Some(event)),
74 Ok(Err(e)) => Err(Error::Adapter {
75 package: "watcher".to_string(),
76 message: format!("Watcher error: {}", e),
77 }),
78 Err(std::sync::mpsc::TryRecvError::Empty) => Ok(None),
79 Err(std::sync::mpsc::TryRecvError::Disconnected) => Err(Error::Adapter {
80 package: "watcher".to_string(),
81 message: "Watcher channel disconnected".to_string(),
82 }),
83 }
84 }
85
86 pub fn wait_for_event(&mut self) -> Result<Event> {
87 self.receiver
88 .recv()
89 .map_err(|_| Error::Adapter {
90 package: "watcher".to_string(),
91 message: "Watcher channel disconnected".to_string(),
92 })?
93 .map_err(|e| Error::Adapter {
94 package: "watcher".to_string(),
95 message: format!("Watcher error: {}", e),
96 })
97 }
98
99 pub fn get_affected_packages(&self, event: &Event) -> HashSet<String> {
100 let mut affected = HashSet::new();
101
102 match &event.kind {
103 EventKind::Any | EventKind::Other => {
104 for path in &event.paths {
105 if let Some(package_name) =
106 Self::file_to_package(path, &self.config.packages_dir)
107 {
108 affected.insert(package_name);
109 }
110 }
111 }
112 _ => {
113 for path in &event.paths {
114 if let Some(package_name) =
115 Self::file_to_package(path, &self.config.packages_dir)
116 {
117 affected.insert(package_name);
118 }
119 }
120 }
121 }
122
123 affected
124 }
125
126 fn file_to_package(file_path: &Path, packages_dir: &Path) -> Option<String> {
127 path_utils::file_to_package(file_path, packages_dir)
128 }
129}