tale_ndjson/multiplexed/
watcher.rs1use std::path::{Path, PathBuf};
7use std::time::Duration;
8
9use miette::Result;
10use notify::{Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
11use tokio::sync::mpsc;
12use tokio::task::JoinHandle;
13
14use super::file_state::FileStateManager;
15use crate::errors::*;
16
17#[derive(Debug, Clone)]
19pub enum WatchEvent {
20 FileModified(PathBuf),
22 FileCreated(PathBuf),
24 FileDeleted(PathBuf),
26 FileMoved { from: PathBuf, to: PathBuf },
28 Error(String),
30}
31
32#[derive(Debug, Clone)]
34pub struct WatcherConfig {
35 pub poll_interval: Duration,
37 pub event_buffer_size: usize,
39 pub use_polling: bool,
41}
42
43impl Default for WatcherConfig {
44 fn default() -> Self {
45 Self {
46 poll_interval: Duration::from_millis(100),
47 event_buffer_size: 1000,
48 use_polling: false,
49 }
50 }
51}
52
53pub struct MultiFileWatcher {
55 _config: WatcherConfig,
57 file_manager: FileStateManager,
59 event_sender: Option<mpsc::UnboundedSender<WatchEvent>>,
61 _watcher: Option<RecommendedWatcher>,
63 _task_handle: Option<JoinHandle<()>>,
65}
66
67impl MultiFileWatcher {
68 pub fn new(_config: WatcherConfig) -> Self {
70 Self {
71 _config,
72 file_manager: FileStateManager::new(),
73 event_sender: None,
74 _watcher: None,
75 _task_handle: None,
76 }
77 }
78
79 pub async fn add_files<I, P>(&mut self, paths: I) -> Result<(), TaleError>
81 where
82 I: IntoIterator<Item = P>,
83 P: AsRef<Path>,
84 {
85 for path in paths {
86 self.file_manager.add_file_for_tailing(path)?;
87 }
88
89 Ok(())
90 }
91
92 pub async fn watch(&mut self) -> Result<mpsc::UnboundedReceiver<WatchEvent>, TaleError> {
94 let (event_sender, event_receiver) = mpsc::unbounded_channel();
95
96 let (notify_sender, notify_receiver) = std::sync::mpsc::channel();
98
99 let mut watcher = notify::recommended_watcher(move |result: notify::Result<Event>| {
101 if let Err(e) = notify_sender.send(result) {
102 eprintln!("Failed to send notify event: {e}");
103 }
104 })?;
105
106 for file_path in self.file_manager.tracked_files() {
108 watcher
109 .watch(file_path, RecursiveMode::NonRecursive)
110 .map_err(TaleError::NotifyError)?;
111 }
112
113 let event_sender_clone = event_sender.clone();
115 let task_handle = tokio::spawn(async move {
116 while let Ok(result) = notify_receiver.recv() {
117 match result {
118 Ok(event) => {
119 if let Some(watch_event) = Self::convert_notify_event(event)
120 && event_sender_clone.send(watch_event).is_err()
121 {
122 break; }
124 }
125 Err(e) => {
126 let error_event = WatchEvent::Error(format!("Notify error: {e}"));
127 if event_sender_clone.send(error_event).is_err() {
128 break; }
130 }
131 }
132 }
133 });
134
135 self.event_sender = Some(event_sender);
136 self._watcher = Some(watcher);
137 self._task_handle = Some(task_handle);
138
139 Ok(event_receiver)
140 }
141
142 pub async fn stop(&mut self) -> Result<(), TaleError> {
144 self._watcher = None;
146
147 self.event_sender = None;
149
150 if let Some(handle) = self._task_handle.take() {
152 let _ = handle.await;
153 }
154
155 Ok(())
156 }
157
158 fn convert_notify_event(event: Event) -> Option<WatchEvent> {
160 match event.kind {
161 EventKind::Modify(_) => {
162 event.paths.first().map(|path| WatchEvent::FileModified(path.clone()))
164 }
165 EventKind::Create(_) => {
166 event.paths.first().map(|path| WatchEvent::FileCreated(path.clone()))
168 }
169 EventKind::Remove(_) => {
170 event.paths.first().map(|path| WatchEvent::FileDeleted(path.clone()))
172 }
173 _ => {
174 None
176 }
177 }
178 }
179
180 pub fn file_manager(&self) -> &FileStateManager {
182 &self.file_manager
183 }
184
185 pub fn file_manager_mut(&mut self) -> &mut FileStateManager {
187 &mut self.file_manager
188 }
189}
190
191pub fn create_watcher() -> MultiFileWatcher {
193 MultiFileWatcher::new(WatcherConfig::default())
194}
195
196pub fn create_watcher_with_config(config: WatcherConfig) -> MultiFileWatcher {
198 MultiFileWatcher::new(config)
199}