use anyhow::{anyhow, Result}; use chrono::NaiveDate; use flate2::read::GzDecoder; use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::PathBuf; /// Discovers log files for a given date range pub struct LogFileDiscovery { base_dir: PathBuf, filename: String, } impl LogFileDiscovery { pub fn new(base_dir: PathBuf, filename: String) -> Self { Self { base_dir, filename } } /// Returns an iterator over all log files in the date range pub fn discover(&self, from: NaiveDate, to: NaiveDate) -> Result> { let mut files = Vec::new(); let mut current = from; while current <= to { if let Some(log_file) = self.find_log_for_date(current)? { files.push(log_file); } current = current .succ_opt() .ok_or_else(|| anyhow!("Date overflow"))?; } Ok(files) } fn find_log_for_date(&self, date: NaiveDate) -> Result> { // Build path: /yyyy/mm/dd/.gz or let date_path = self .base_dir .join(date.format("%Y").to_string()) .join(date.format("%m").to_string()) .join(date.format("%d").to_string()); // Try gzipped first let gz_path = date_path.join(format!("{}.gz", self.filename)); if gz_path.exists() { return Ok(Some(LogFile { path: gz_path, compressed: true, })); } // Try uncompressed let plain_path = date_path.join(&self.filename); if plain_path.exists() { return Ok(Some(LogFile { path: plain_path, compressed: false, })); } // No file found for this date Ok(None) } } #[derive(Debug)] pub struct LogFile { pub path: PathBuf, pub compressed: bool, } impl LogFile { /// Returns a buffered reader for this log file, handling compression transparently pub fn reader(&self) -> Result> { let file = File::open(&self.path)?; if self.compressed { let decoder = GzDecoder::new(file); Ok(Box::new(BufReader::new(decoder))) } else { Ok(Box::new(BufReader::new(file))) } } } /// For reading a single file directly (e.g., for testing) pub fn read_log_file(path: &str) -> Result> { let file = File::open(path)?; if path.ends_with(".gz") { let decoder = GzDecoder::new(file); Ok(Box::new(BufReader::new(decoder))) } else { Ok(Box::new(BufReader::new(file))) } }