use crate::error::{Error, Result};
use parking_lot::RwLock;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
struct LruCache<K, V> {
map: HashMap<K, V>,
max_size: usize,
}
impl<K: Eq + std::hash::Hash + Clone, V> LruCache<K, V> {
fn new(max_size: usize) -> Self {
Self {
map: HashMap::with_capacity(max_size),
max_size,
}
}
fn get(&self, key: &K) -> Option<&V> {
self.map.get(key)
}
fn put(&mut self, key: K, value: V) {
if self.map.len() >= self.max_size {
self.map.clear();
}
self.map.insert(key, value);
}
}
pub struct FileOps {
symlink_cache: RwLock<LruCache<PathBuf, bool>>,
_cache_size: usize,
}
impl FileOps {
pub fn new() -> Self {
Self::with_cache_size(1024)
}
pub fn with_cache_size(cache_size: usize) -> Self {
Self {
symlink_cache: RwLock::new(LruCache::new(cache_size)),
_cache_size: cache_size,
}
}
pub async fn is_symlink(&self, path: &Path) -> Result<bool> {
let path_buf = path.to_path_buf();
{
let cache = self.symlink_cache.read();
if let Some(&cached) = cache.get(&path_buf) {
return Ok(cached);
}
}
let metadata = tokio::fs::symlink_metadata(&path_buf)
.await
.map_err(Error::Io)?;
let is_link = metadata.file_type().is_symlink();
{
let mut cache = self.symlink_cache.write();
cache.put(path_buf, is_link);
}
Ok(is_link)
}
pub fn is_symlink_sync(&self, path: &Path) -> Result<bool> {
let path_buf = path.to_path_buf();
{
let cache = self.symlink_cache.read();
if let Some(&cached) = cache.get(&path_buf) {
return Ok(cached);
}
}
let metadata = std::fs::symlink_metadata(&path_buf).map_err(Error::Io)?;
let is_link = metadata.file_type().is_symlink();
{
let mut cache = self.symlink_cache.write();
cache.put(path_buf, is_link);
}
Ok(is_link)
}
pub fn clear_cache(&self) {
let mut cache = self.symlink_cache.write();
cache.map.clear();
}
}
impl Default for FileOps {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_is_symlink_sync() {
let dir = TempDir::new().unwrap();
let file_path = dir.path().join("regular_file.txt");
fs::write(&file_path, "content").unwrap();
let ops = FileOps::new();
let is_link = ops.is_symlink_sync(&file_path).unwrap();
assert!(!is_link);
}
#[test]
fn test_symlink_cache() {
let dir = TempDir::new().unwrap();
let file_path = dir.path().join("test.txt");
fs::write(&file_path, "content").unwrap();
let ops = FileOps::new();
let result1 = ops.is_symlink_sync(&file_path).unwrap();
let result2 = ops.is_symlink_sync(&file_path).unwrap();
assert_eq!(result1, result2);
assert!(!result1);
}
#[test]
fn test_clear_cache() {
let dir = TempDir::new().unwrap();
let file_path = dir.path().join("test.txt");
fs::write(&file_path, "content").unwrap();
let ops = FileOps::new();
ops.is_symlink_sync(&file_path).unwrap();
ops.clear_cache();
let result = ops.is_symlink_sync(&file_path).unwrap();
assert!(!result);
}
#[tokio::test]
async fn test_is_symlink_async() {
let dir = TempDir::new().unwrap();
let file_path = dir.path().join("test.txt");
fs::write(&file_path, "content").unwrap();
let ops = FileOps::new();
let is_link = ops.is_symlink(&file_path).await.unwrap();
assert!(!is_link);
}
}