use crate::error::{LogError, LogResult};
use std::path::{Path, PathBuf};
use std::sync::mpsc::{self, Receiver, Sender};
use std::thread;
use std::time::Duration;
#[derive(Debug, Clone)]
pub enum FileEvent {
Created(PathBuf),
Modified(PathBuf),
Deleted(PathBuf),
Renamed { from: PathBuf, to: PathBuf },
Error(String),
}
#[derive(Debug, Clone)]
pub struct WatcherConfig {
pub paths: Vec<PathBuf>,
pub recursive: bool,
pub filters: Vec<String>,
pub poll_interval: Option<u64>,
}
impl Default for WatcherConfig {
fn default() -> Self {
Self {
paths: Vec::new(),
recursive: false,
filters: Vec::new(),
poll_interval: None,
}
}
}
pub struct FileWatcher {
config: WatcherConfig,
event_sender: Option<Sender<FileEvent>>,
}
impl FileWatcher {
pub fn new(config: WatcherConfig) -> Self {
Self {
config,
event_sender: None,
}
}
pub fn start(&mut self) -> LogResult<Receiver<FileEvent>> {
let (sender, receiver) = mpsc::channel();
self.event_sender = Some(sender.clone());
self.start_polling(sender);
Ok(receiver)
}
pub fn stop(&mut self) {
self.event_sender = None;
}
pub fn add_path<P: AsRef<Path>>(&mut self, path: P) -> LogResult<()> {
let path_buf = path.as_ref().to_path_buf();
if !self.config.paths.contains(&path_buf) {
self.config.paths.push(path_buf.clone());
}
Ok(())
}
pub fn remove_path<P: AsRef<Path>>(&mut self, path: P) -> LogResult<()> {
let path_buf = path.as_ref().to_path_buf();
if let Some(pos) = self.config.paths.iter().position(|p| p == &path_buf) {
self.config.paths.remove(pos);
}
Ok(())
}
fn start_polling(&self, sender: Sender<FileEvent>) {
let paths = self.config.paths.clone();
let interval = Duration::from_millis(self.config.poll_interval.unwrap_or(1000));
thread::spawn(move || {
let mut last_modified = std::collections::HashMap::new();
loop {
for path in &paths {
if let Ok(metadata) = std::fs::metadata(path) {
if let Ok(modified) = metadata.modified() {
if let Some(&last_time) = last_modified.get(path) {
if modified > last_time {
let _ = sender.send(FileEvent::Modified(path.clone()));
}
}
last_modified.insert(path.clone(), modified);
}
}
}
thread::sleep(interval);
}
});
}
}
pub struct WatcherBuilder {
config: WatcherConfig,
}
impl WatcherBuilder {
pub fn new() -> Self {
Self {
config: WatcherConfig::default(),
}
}
pub fn path<P: AsRef<Path>>(mut self, path: P) -> Self {
self.config.paths.push(path.as_ref().to_path_buf());
self
}
pub fn recursive(mut self, recursive: bool) -> Self {
self.config.recursive = recursive;
self
}
pub fn filter<S: Into<String>>(mut self, filter: S) -> Self {
self.config.filters.push(filter.into());
self
}
pub fn poll_interval(mut self, interval_ms: u64) -> Self {
self.config.poll_interval = Some(interval_ms);
self
}
pub fn build(self) -> FileWatcher {
FileWatcher::new(self.config)
}
}
pub struct LogRotationWatcher {
base_path: PathBuf,
max_size: u64,
current_size: u64,
max_files: u32,
}
impl LogRotationWatcher {
pub fn new<P: AsRef<Path>>(base_path: P, max_size: u64, max_files: u32) -> Self {
Self {
base_path: base_path.as_ref().to_path_buf(),
max_size,
current_size: 0,
max_files,
}
}
pub fn should_rotate(&mut self) -> LogResult<bool> {
if let Ok(metadata) = std::fs::metadata(&self.base_path) {
self.current_size = metadata.len();
Ok(self.current_size >= self.max_size)
} else {
Ok(false)
}
}
pub fn rotate(&mut self) -> LogResult<()> {
for i in (1..self.max_files).rev() {
let old_path = self.get_rotated_path(i);
let new_path = self.get_rotated_path(i + 1);
if old_path.exists() {
std::fs::rename(&old_path, &new_path)
.map_err(|e| LogError::file_operation(
old_path.to_string_lossy().to_string(),
format!("Failed to rotate file: {}", e)
))?;
}
}
if self.base_path.exists() {
let rotated_path = self.get_rotated_path(1);
std::fs::rename(&self.base_path, &rotated_path)
.map_err(|e| LogError::file_operation(
self.base_path.to_string_lossy().to_string(),
format!("Failed to rotate current file: {}", e)
))?;
}
let excess_path = self.get_rotated_path(self.max_files + 1);
if excess_path.exists() {
std::fs::remove_file(&excess_path)
.map_err(|e| LogError::file_operation(
excess_path.to_string_lossy().to_string(),
format!("Failed to remove excess file: {}", e)
))?;
}
self.current_size = 0;
Ok(())
}
fn get_rotated_path(&self, index: u32) -> PathBuf {
let mut path = self.base_path.clone();
let file_name = path.file_name().unwrap().to_string_lossy();
let new_name = format!("{}.{}", file_name, index);
path.set_file_name(new_name);
path
}
pub fn update_size(&mut self, additional_bytes: u64) {
self.current_size += additional_bytes;
}
pub fn current_size(&self) -> u64 {
self.current_size
}
}