use crate::error::GlobError;
use lru::LruCache;
use std::{
fs,
num::NonZeroUsize,
path::{Path, PathBuf},
sync::Mutex,
time::{Duration, Instant},
};
const METADATA_CACHE_TTL: Duration = Duration::from_secs(30);
#[derive(Debug, Clone)]
struct CachedMetadata {
metadata: fs::Metadata,
expires_at: Instant,
}
#[derive(Debug)]
pub struct BatchIO {
metadata_cache: Mutex<LruCache<PathBuf, CachedMetadata>>,
follow_symlinks: bool,
}
impl BatchIO {
pub fn new(cache_size: usize, follow_symlinks: bool) -> Self {
Self {
metadata_cache: Mutex::new(LruCache::new(NonZeroUsize::new(cache_size).unwrap())),
follow_symlinks,
}
}
pub fn stat(&self, path: &Path) -> Result<fs::Metadata, GlobError> {
let mut cache = self.metadata_cache.lock().unwrap();
if let Some(cached) = cache.get(path) {
if cached.expires_at > Instant::now() {
return Ok(cached.metadata.clone());
}
cache.pop(path);
}
if !self.follow_symlinks && path.is_symlink() {
return Err(GlobError::Other("Symlinks not allowed".into()));
}
let meta = fs::metadata(path).map_err(GlobError::Io)?;
if meta.permissions().readonly() {
return Err(GlobError::PermissionDenied);
}
let cached_meta = CachedMetadata {
metadata: meta.clone(),
expires_at: Instant::now() + METADATA_CACHE_TTL,
};
cache.put(path.to_path_buf(), cached_meta);
Ok(meta)
}
pub fn stat_symlink(&self, path: &Path) -> Result<fs::Metadata, GlobError> {
fs::symlink_metadata(path).map_err(GlobError::Io)
}
pub fn clear_cache(&self) {
let mut cache = self.metadata_cache.lock().unwrap();
cache.clear();
}
}