1use crate::error::{LogError, LogResult};
6use std::path::{Path, PathBuf};
7use std::sync::mpsc::{self, Receiver, Sender};
8use std::thread;
9use std::time::Duration;
10
11#[derive(Debug, Clone)]
15pub enum FileEvent {
16 Created(PathBuf),
18 Modified(PathBuf),
20 Deleted(PathBuf),
22 Renamed { from: PathBuf, to: PathBuf },
24 Error(String),
26}
27
28#[derive(Debug, Clone)]
30pub struct WatcherConfig {
31 pub paths: Vec<PathBuf>,
33 pub recursive: bool,
35 pub filters: Vec<String>,
37 pub poll_interval: Option<u64>,
39}
40
41impl Default for WatcherConfig {
42 fn default() -> Self {
43 Self {
44 paths: Vec::new(),
45 recursive: false,
46 filters: Vec::new(),
47 poll_interval: None,
48 }
49 }
50}
51
52pub struct FileWatcher {
54 config: WatcherConfig,
55 event_sender: Option<Sender<FileEvent>>,
56}
57
58impl FileWatcher {
59 pub fn new(config: WatcherConfig) -> Self {
61 Self {
62 config,
63 event_sender: None,
64 }
65 }
66
67 pub fn start(&mut self) -> LogResult<Receiver<FileEvent>> {
69 let (sender, receiver) = mpsc::channel();
70 self.event_sender = Some(sender.clone());
71
72 self.start_polling(sender);
74
75 Ok(receiver)
76 }
77
78 pub fn stop(&mut self) {
80 self.event_sender = None;
81 }
82
83 pub fn add_path<P: AsRef<Path>>(&mut self, path: P) -> LogResult<()> {
85 let path_buf = path.as_ref().to_path_buf();
86
87 if !self.config.paths.contains(&path_buf) {
88 self.config.paths.push(path_buf.clone());
89 }
90
91 Ok(())
92 }
93
94 pub fn remove_path<P: AsRef<Path>>(&mut self, path: P) -> LogResult<()> {
96 let path_buf = path.as_ref().to_path_buf();
97
98 if let Some(pos) = self.config.paths.iter().position(|p| p == &path_buf) {
99 self.config.paths.remove(pos);
100 }
101
102 Ok(())
103 }
104
105 fn start_polling(&self, sender: Sender<FileEvent>) {
107 let paths = self.config.paths.clone();
108 let interval = Duration::from_millis(self.config.poll_interval.unwrap_or(1000));
109
110 thread::spawn(move || {
111 let mut last_modified = std::collections::HashMap::new();
112
113 loop {
114 for path in &paths {
115 if let Ok(metadata) = std::fs::metadata(path) {
116 if let Ok(modified) = metadata.modified() {
117 if let Some(&last_time) = last_modified.get(path) {
118 if modified > last_time {
119 let _ = sender.send(FileEvent::Modified(path.clone()));
120 }
121 }
122 last_modified.insert(path.clone(), modified);
123 }
124 }
125 }
126
127 thread::sleep(interval);
128 }
129 });
130 }
131}
132
133pub struct WatcherBuilder {
135 config: WatcherConfig,
136}
137
138impl WatcherBuilder {
139 pub fn new() -> Self {
141 Self {
142 config: WatcherConfig::default(),
143 }
144 }
145
146 pub fn path<P: AsRef<Path>>(mut self, path: P) -> Self {
148 self.config.paths.push(path.as_ref().to_path_buf());
149 self
150 }
151
152 pub fn recursive(mut self, recursive: bool) -> Self {
154 self.config.recursive = recursive;
155 self
156 }
157
158 pub fn filter<S: Into<String>>(mut self, filter: S) -> Self {
160 self.config.filters.push(filter.into());
161 self
162 }
163
164 pub fn poll_interval(mut self, interval_ms: u64) -> Self {
166 self.config.poll_interval = Some(interval_ms);
167 self
168 }
169
170 pub fn build(self) -> FileWatcher {
172 FileWatcher::new(self.config)
173 }
174}
175
176pub struct LogRotationWatcher {
178 base_path: PathBuf,
179 max_size: u64,
180 current_size: u64,
181 max_files: u32,
182}
183
184impl LogRotationWatcher {
185 pub fn new<P: AsRef<Path>>(base_path: P, max_size: u64, max_files: u32) -> Self {
187 Self {
188 base_path: base_path.as_ref().to_path_buf(),
189 max_size,
190 current_size: 0,
191 max_files,
192 }
193 }
194
195 pub fn should_rotate(&mut self) -> LogResult<bool> {
197 if let Ok(metadata) = std::fs::metadata(&self.base_path) {
198 self.current_size = metadata.len();
199 Ok(self.current_size >= self.max_size)
200 } else {
201 Ok(false)
202 }
203 }
204
205 pub fn rotate(&mut self) -> LogResult<()> {
207 for i in (1..self.max_files).rev() {
209 let old_path = self.get_rotated_path(i);
210 let new_path = self.get_rotated_path(i + 1);
211
212 if old_path.exists() {
213 std::fs::rename(&old_path, &new_path)
214 .map_err(|e| LogError::file_operation(
215 old_path.to_string_lossy().to_string(),
216 format!("Failed to rotate file: {}", e)
217 ))?;
218 }
219 }
220
221 if self.base_path.exists() {
223 let rotated_path = self.get_rotated_path(1);
224 std::fs::rename(&self.base_path, &rotated_path)
225 .map_err(|e| LogError::file_operation(
226 self.base_path.to_string_lossy().to_string(),
227 format!("Failed to rotate current file: {}", e)
228 ))?;
229 }
230
231 let excess_path = self.get_rotated_path(self.max_files + 1);
233 if excess_path.exists() {
234 std::fs::remove_file(&excess_path)
235 .map_err(|e| LogError::file_operation(
236 excess_path.to_string_lossy().to_string(),
237 format!("Failed to remove excess file: {}", e)
238 ))?;
239 }
240
241 self.current_size = 0;
242 Ok(())
243 }
244
245 fn get_rotated_path(&self, index: u32) -> PathBuf {
247 let mut path = self.base_path.clone();
248 let file_name = path.file_name().unwrap().to_string_lossy();
249 let new_name = format!("{}.{}", file_name, index);
250 path.set_file_name(new_name);
251 path
252 }
253
254 pub fn update_size(&mut self, additional_bytes: u64) {
256 self.current_size += additional_bytes;
257 }
258
259 pub fn current_size(&self) -> u64 {
261 self.current_size
262 }
263}