adding several features
This commit is contained in:
@@ -0,0 +1,659 @@
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant, UNIX_EPOCH};
|
||||
use tokio::fs;
|
||||
use tokio::sync::RwLock;
|
||||
use tokio::time;
|
||||
use futures::future::BoxFuture;
|
||||
use tracing::debug;
|
||||
use mime_guess::from_path;
|
||||
|
||||
use crate::common::config::AppConfig;
|
||||
|
||||
/// Tipos de entradas en caché
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum CacheEntryType {
|
||||
/// Archivo
|
||||
File,
|
||||
/// Directorio
|
||||
Directory,
|
||||
/// Tipo desconocido
|
||||
Unknown,
|
||||
}
|
||||
|
||||
/// Estadísticas de caché para monitoreo
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct CacheStats {
|
||||
/// Número de hits en caché
|
||||
pub hits: usize,
|
||||
/// Número de misses en caché
|
||||
pub misses: usize,
|
||||
/// Número de invalidaciones manuales
|
||||
pub invalidations: usize,
|
||||
/// Número de expiraciones automáticas
|
||||
pub expirations: usize,
|
||||
/// Número de inserciones en caché
|
||||
pub inserts: usize,
|
||||
/// Tiempo total ahorrado (milisegundos)
|
||||
pub time_saved_ms: u64,
|
||||
}
|
||||
|
||||
/// Metadatos completos de archivo en caché
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FileMetadata {
|
||||
/// Ruta absoluta del archivo
|
||||
pub path: PathBuf,
|
||||
/// Si el archivo existe físicamente
|
||||
#[allow(dead_code)]
|
||||
pub exists: bool,
|
||||
/// Tipo de entrada (archivo, directorio)
|
||||
pub entry_type: CacheEntryType,
|
||||
/// Tamaño en bytes (para archivos)
|
||||
pub size: Option<u64>,
|
||||
/// Tipo MIME (para archivos)
|
||||
#[allow(dead_code)]
|
||||
pub mime_type: Option<String>,
|
||||
/// Timestamp de creación (UNIX epoch seconds)
|
||||
pub created_at: Option<u64>,
|
||||
/// Timestamp de modificación (UNIX epoch seconds)
|
||||
pub modified_at: Option<u64>,
|
||||
/// Acceso previo (usado para LRU)
|
||||
pub last_access: Instant,
|
||||
/// Tiempo de expiración de la caché
|
||||
pub expires_at: Instant,
|
||||
/// Número de accesos a esta entrada
|
||||
pub access_count: usize,
|
||||
}
|
||||
|
||||
impl FileMetadata {
|
||||
/// Crea una nueva entrada de metadatos
|
||||
pub fn new(
|
||||
path: PathBuf,
|
||||
exists: bool,
|
||||
entry_type: CacheEntryType,
|
||||
size: Option<u64>,
|
||||
mime_type: Option<String>,
|
||||
created_at: Option<u64>,
|
||||
modified_at: Option<u64>,
|
||||
ttl: Duration,
|
||||
) -> Self {
|
||||
let now = Instant::now();
|
||||
|
||||
Self {
|
||||
path,
|
||||
exists,
|
||||
entry_type,
|
||||
size,
|
||||
mime_type,
|
||||
created_at,
|
||||
modified_at,
|
||||
last_access: now,
|
||||
expires_at: now + ttl,
|
||||
access_count: 1,
|
||||
}
|
||||
}
|
||||
|
||||
/// Actualiza el tiempo de último acceso
|
||||
pub fn touch(&mut self) {
|
||||
self.last_access = Instant::now();
|
||||
self.access_count += 1;
|
||||
}
|
||||
|
||||
/// Verifica si la entrada ha expirado
|
||||
pub fn is_expired(&self) -> bool {
|
||||
Instant::now() > self.expires_at
|
||||
}
|
||||
|
||||
/// Actualiza el tiempo de expiración con un nuevo TTL
|
||||
pub fn update_expiry(&mut self, ttl: Duration) {
|
||||
self.expires_at = Instant::now() + ttl;
|
||||
}
|
||||
}
|
||||
|
||||
/// Caché avanzada de metadatos de archivos
|
||||
pub struct FileMetadataCache {
|
||||
/// Caché principal de metadatos
|
||||
metadata_cache: RwLock<HashMap<PathBuf, FileMetadata>>,
|
||||
/// Cola LRU para administración de caché
|
||||
lru_queue: RwLock<VecDeque<PathBuf>>,
|
||||
/// Estadísticas de uso del caché
|
||||
stats: RwLock<CacheStats>,
|
||||
/// Configuración global de la aplicación
|
||||
config: AppConfig,
|
||||
/// TTL adaptativo para entradas populares
|
||||
ttl_multiplier: f64,
|
||||
/// Umbral de popularidad para TTL extendido
|
||||
popularity_threshold: usize,
|
||||
/// Tamaño máximo de caché
|
||||
max_entries: usize,
|
||||
}
|
||||
|
||||
impl FileMetadataCache {
|
||||
/// Crea una nueva instancia de caché de metadatos
|
||||
pub fn new(config: AppConfig, max_entries: usize) -> Self {
|
||||
Self {
|
||||
metadata_cache: RwLock::new(HashMap::with_capacity(max_entries)),
|
||||
lru_queue: RwLock::new(VecDeque::with_capacity(max_entries)),
|
||||
stats: RwLock::new(CacheStats::default()),
|
||||
config,
|
||||
ttl_multiplier: 5.0, // Entradas populares tienen 5x TTL
|
||||
popularity_threshold: 10, // Después de 10 accesos se considera popular
|
||||
max_entries,
|
||||
}
|
||||
}
|
||||
|
||||
/// Crea una instancia de caché con configuración por defecto
|
||||
pub fn default_with_config(config: AppConfig) -> Self {
|
||||
Self::new(config, 50_000) // Caché más grande para sistema en producción
|
||||
}
|
||||
|
||||
/// Obtiene los metadatos de un archivo si están en caché
|
||||
pub async fn get_metadata(&self, path: &Path) -> Option<FileMetadata> {
|
||||
let start_time = Instant::now();
|
||||
let mut cache = self.metadata_cache.write().await;
|
||||
|
||||
if let Some(metadata) = cache.get_mut(path) {
|
||||
// Verificar si ha expirado
|
||||
if metadata.is_expired() {
|
||||
// Eliminar de caché si expiró
|
||||
cache.remove(path);
|
||||
|
||||
// Actualizar estadísticas
|
||||
let mut stats = self.stats.write().await;
|
||||
stats.misses += 1;
|
||||
stats.expirations += 1;
|
||||
|
||||
debug!("Cache entry expired for: {}", path.display());
|
||||
|
||||
return None;
|
||||
}
|
||||
|
||||
// Actualizar tiempo de acceso
|
||||
metadata.touch();
|
||||
|
||||
// Para entradas populares, extender TTL
|
||||
if metadata.access_count >= self.popularity_threshold {
|
||||
let new_ttl = match metadata.entry_type {
|
||||
CacheEntryType::File => Duration::from_millis(
|
||||
(self.config.timeouts.file_operation_ms as f64 * self.ttl_multiplier) as u64
|
||||
),
|
||||
CacheEntryType::Directory => Duration::from_millis(
|
||||
(self.config.timeouts.dir_operation_ms as f64 * self.ttl_multiplier) as u64
|
||||
),
|
||||
_ => Duration::from_secs(60), // 1 minuto por defecto
|
||||
};
|
||||
|
||||
metadata.update_expiry(new_ttl);
|
||||
debug!("Extended TTL for popular entry: {}", path.display());
|
||||
}
|
||||
|
||||
// Calcular tiempo ahorrado aproximado
|
||||
let elapsed = start_time.elapsed().as_millis() as u64;
|
||||
let estimated_io_time: u64 = 10; // Asumimos 10ms mínimo para operación de IO
|
||||
let time_saved = estimated_io_time.saturating_sub(elapsed);
|
||||
|
||||
// Actualizar estadísticas
|
||||
let mut stats = self.stats.write().await;
|
||||
stats.hits += 1;
|
||||
stats.time_saved_ms += time_saved;
|
||||
|
||||
debug!("Cache hit for: {}", path.display());
|
||||
|
||||
// Mantener también la cola LRU actualizada
|
||||
self.update_lru(path.to_path_buf()).await;
|
||||
|
||||
// Clonar para retornar
|
||||
return Some(metadata.clone());
|
||||
}
|
||||
|
||||
// No encontrado en caché
|
||||
let mut stats = self.stats.write().await;
|
||||
stats.misses += 1;
|
||||
|
||||
debug!("Cache miss for: {}", path.display());
|
||||
None
|
||||
}
|
||||
|
||||
/// Actualiza la cola LRU
|
||||
async fn update_lru(&self, path: PathBuf) {
|
||||
let mut lru = self.lru_queue.write().await;
|
||||
|
||||
// Eliminar si ya existe
|
||||
if let Some(pos) = lru.iter().position(|p| p == &path) {
|
||||
lru.remove(pos);
|
||||
}
|
||||
|
||||
// Agregar al final (más reciente)
|
||||
lru.push_back(path);
|
||||
}
|
||||
|
||||
/// Verifica si un archivo existe
|
||||
#[allow(dead_code)]
|
||||
pub async fn exists(&self, path: &Path) -> Option<bool> {
|
||||
if let Some(metadata) = self.get_metadata(path).await {
|
||||
return Some(metadata.exists);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Verifica si un path es un directorio
|
||||
#[allow(dead_code)]
|
||||
pub async fn is_dir(&self, path: &Path) -> Option<bool> {
|
||||
if let Some(metadata) = self.get_metadata(path).await {
|
||||
return Some(metadata.entry_type == CacheEntryType::Directory);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Verifica si un path es un archivo
|
||||
pub async fn is_file(&self, path: &Path) -> Option<bool> {
|
||||
if let Some(metadata) = self.get_metadata(path).await {
|
||||
return Some(metadata.entry_type == CacheEntryType::File);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Obtiene el tamaño de un archivo
|
||||
#[allow(dead_code)]
|
||||
pub async fn get_size(&self, path: &Path) -> Option<u64> {
|
||||
if let Some(metadata) = self.get_metadata(path).await {
|
||||
return metadata.size;
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Obtiene el tipo MIME de un archivo
|
||||
#[allow(dead_code)]
|
||||
pub async fn get_mime_type(&self, path: &Path) -> Option<String> {
|
||||
if let Some(metadata) = self.get_metadata(path).await {
|
||||
return metadata.mime_type;
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Refresca los metadatos de un path
|
||||
pub async fn refresh_metadata(&self, path: &Path) -> Result<FileMetadata, std::io::Error> {
|
||||
// Realizar lectura real del sistema de archivos
|
||||
let metadata = fs::metadata(path).await?;
|
||||
|
||||
// Determinar tipo de entrada
|
||||
let entry_type = if metadata.is_dir() {
|
||||
CacheEntryType::Directory
|
||||
} else if metadata.is_file() {
|
||||
CacheEntryType::File
|
||||
} else {
|
||||
CacheEntryType::Unknown
|
||||
};
|
||||
|
||||
// Obtener tamaño para archivos
|
||||
let size = if metadata.is_file() {
|
||||
Some(metadata.len())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Obtener tipo MIME para archivos
|
||||
let mime_type = if metadata.is_file() {
|
||||
Some(from_path(path).first_or_octet_stream().to_string())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Obtener timestamps
|
||||
let created_at = metadata.created()
|
||||
.map(|time| time.duration_since(UNIX_EPOCH).unwrap_or_default().as_secs())
|
||||
.ok();
|
||||
|
||||
let modified_at = metadata.modified()
|
||||
.map(|time| time.duration_since(UNIX_EPOCH).unwrap_or_default().as_secs())
|
||||
.ok();
|
||||
|
||||
// Determinar TTL apropiado
|
||||
let ttl = if metadata.is_dir() {
|
||||
Duration::from_millis(self.config.timeouts.dir_operation_ms)
|
||||
} else {
|
||||
Duration::from_millis(self.config.timeouts.file_operation_ms)
|
||||
};
|
||||
|
||||
// Crear entrada de metadatos
|
||||
let file_metadata = FileMetadata::new(
|
||||
path.to_path_buf(),
|
||||
true,
|
||||
entry_type,
|
||||
size,
|
||||
mime_type,
|
||||
created_at,
|
||||
modified_at,
|
||||
ttl,
|
||||
);
|
||||
|
||||
// Actualizar caché
|
||||
self.update_cache(file_metadata.clone()).await;
|
||||
|
||||
Ok(file_metadata)
|
||||
}
|
||||
|
||||
/// Actualiza la caché con nuevos metadatos
|
||||
pub async fn update_cache(&self, metadata: FileMetadata) {
|
||||
// Evitar caché llena antes de insertar
|
||||
self.ensure_capacity().await;
|
||||
|
||||
let path = metadata.path.clone();
|
||||
|
||||
// Insertar en caché
|
||||
{
|
||||
let mut cache = self.metadata_cache.write().await;
|
||||
cache.insert(path.clone(), metadata);
|
||||
|
||||
// Actualizar estadísticas
|
||||
let mut stats = self.stats.write().await;
|
||||
stats.inserts += 1;
|
||||
}
|
||||
|
||||
// Actualizar la cola LRU
|
||||
self.update_lru(path).await;
|
||||
}
|
||||
|
||||
/// Asegura que hay espacio en la caché
|
||||
async fn ensure_capacity(&self) {
|
||||
let cache_size = {
|
||||
let cache = self.metadata_cache.read().await;
|
||||
cache.len()
|
||||
};
|
||||
|
||||
if cache_size >= self.max_entries {
|
||||
self.evict_lru_entries(cache_size / 10).await; // Liberar 10%
|
||||
}
|
||||
}
|
||||
|
||||
/// Elimina entradas menos recientemente usadas
|
||||
async fn evict_lru_entries(&self, count: usize) {
|
||||
let mut paths_to_remove = Vec::with_capacity(count);
|
||||
|
||||
// Obtener entries a eliminar de la cola LRU
|
||||
{
|
||||
let mut lru = self.lru_queue.write().await;
|
||||
for _ in 0..count {
|
||||
if let Some(path) = lru.pop_front() {
|
||||
paths_to_remove.push(path);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Eliminar de la caché principal
|
||||
{
|
||||
let mut cache = self.metadata_cache.write().await;
|
||||
for path in paths_to_remove {
|
||||
cache.remove(&path);
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Evicted {} LRU entries from cache", count);
|
||||
}
|
||||
|
||||
/// Invalidar una entrada específica de caché
|
||||
pub async fn invalidate(&self, path: &Path) {
|
||||
// Eliminar de la caché principal
|
||||
{
|
||||
let mut cache = self.metadata_cache.write().await;
|
||||
cache.remove(path);
|
||||
|
||||
// Actualizar estadísticas
|
||||
let mut stats = self.stats.write().await;
|
||||
stats.invalidations += 1;
|
||||
}
|
||||
|
||||
// Eliminar de la cola LRU
|
||||
let path_buf = path.to_path_buf();
|
||||
{
|
||||
let mut lru = self.lru_queue.write().await;
|
||||
if let Some(pos) = lru.iter().position(|p| p == &path_buf) {
|
||||
lru.remove(pos);
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Invalidated cache entry for: {}", path.display());
|
||||
}
|
||||
|
||||
/// Invalidar recursivamente entradas bajo un directorio
|
||||
pub async fn invalidate_directory(&self, dir_path: &Path) {
|
||||
let dir_str = dir_path.to_string_lossy().to_string();
|
||||
let mut paths_to_remove = Vec::new();
|
||||
|
||||
// Encontrar todos los paths que comienzan con el directorio
|
||||
{
|
||||
let cache = self.metadata_cache.read().await;
|
||||
for path in cache.keys() {
|
||||
let path_str = path.to_string_lossy().to_string();
|
||||
if path_str.starts_with(&dir_str) {
|
||||
paths_to_remove.push(path.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Actualizar estadísticas
|
||||
{
|
||||
let mut stats = self.stats.write().await;
|
||||
stats.invalidations += paths_to_remove.len();
|
||||
}
|
||||
|
||||
// Eliminar cada path encontrado
|
||||
for path in paths_to_remove {
|
||||
self.invalidate(&path).await;
|
||||
}
|
||||
|
||||
debug!("Invalidated directory and contents: {}", dir_path.display());
|
||||
}
|
||||
|
||||
/// Obtener estadísticas actuales de la caché
|
||||
pub async fn get_stats(&self) -> CacheStats {
|
||||
let stats = self.stats.read().await;
|
||||
stats.clone()
|
||||
}
|
||||
|
||||
/// Limpia todas las entradas expiradas de la caché
|
||||
pub async fn clear_expired(&self) {
|
||||
let now = Instant::now();
|
||||
let mut paths_to_remove = Vec::new();
|
||||
|
||||
// Encontrar entradas expiradas
|
||||
{
|
||||
let cache = self.metadata_cache.read().await;
|
||||
for (path, metadata) in cache.iter() {
|
||||
if now > metadata.expires_at {
|
||||
paths_to_remove.push(path.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Actualizar estadísticas
|
||||
{
|
||||
let mut stats = self.stats.write().await;
|
||||
stats.expirations += paths_to_remove.len();
|
||||
}
|
||||
|
||||
// Guardar la cantidad de entradas para el logging
|
||||
let num_paths = paths_to_remove.len();
|
||||
|
||||
// Eliminar entradas expiradas
|
||||
for path in paths_to_remove {
|
||||
self.invalidate(&path).await;
|
||||
}
|
||||
|
||||
debug!("Cleared {} expired entries from cache", num_paths);
|
||||
}
|
||||
|
||||
/// Inicia el proceso de limpieza periódica
|
||||
pub fn start_cleanup_task(cache: Arc<Self>) -> BoxFuture<'static, ()> {
|
||||
Box::pin(async move {
|
||||
let cleanup_interval = Duration::from_secs(60); // Cada minuto
|
||||
|
||||
loop {
|
||||
// Esperar el intervalo
|
||||
time::sleep(cleanup_interval).await;
|
||||
|
||||
// Limpiar entradas expiradas
|
||||
cache.clear_expired().await;
|
||||
|
||||
// Registrar estadísticas
|
||||
let stats = cache.get_stats().await;
|
||||
let cache_size = {
|
||||
let cache_map = cache.metadata_cache.read().await;
|
||||
cache_map.len()
|
||||
};
|
||||
|
||||
debug!(
|
||||
"Cache stats: size={}, hits={}, misses={}, hit_ratio={:.2}%, time_saved={}ms",
|
||||
cache_size,
|
||||
stats.hits,
|
||||
stats.misses,
|
||||
if stats.hits + stats.misses > 0 {
|
||||
(stats.hits as f64 * 100.0) / (stats.hits + stats.misses) as f64
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
stats.time_saved_ms
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Precarga metadatos de directorios completos (útil para inicialización)
|
||||
pub async fn preload_directory(&self, dir_path: &Path, recursive: bool, max_depth: usize) -> Result<usize, std::io::Error> {
|
||||
self._preload_directory_internal(dir_path, recursive, max_depth, 0).await
|
||||
}
|
||||
|
||||
/// Implementación interna de precarga con seguimiento de profundidad
|
||||
async fn _preload_directory_internal(
|
||||
&self,
|
||||
dir_path: &Path,
|
||||
recursive: bool,
|
||||
max_depth: usize,
|
||||
current_depth: usize
|
||||
) -> Result<usize, std::io::Error> {
|
||||
Box::pin(async move {
|
||||
if current_depth > max_depth {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
// Obtener entradas del directorio
|
||||
let mut entries = fs::read_dir(dir_path).await?;
|
||||
let mut count = 0;
|
||||
|
||||
// Procesar cada entrada
|
||||
while let Some(entry) = entries.next_entry().await? {
|
||||
let path = entry.path();
|
||||
let metadata = fs::metadata(&path).await?;
|
||||
|
||||
// Refrescar metadatos de esta entrada
|
||||
self.refresh_metadata(&path).await?;
|
||||
count += 1;
|
||||
|
||||
// Recursivamente procesar subdirectorios si es necesario
|
||||
if recursive && metadata.is_dir() {
|
||||
// Box to break recursion
|
||||
count += self._preload_directory_internal(
|
||||
&path,
|
||||
recursive,
|
||||
max_depth,
|
||||
current_depth + 1
|
||||
).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(count)
|
||||
}).await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tempfile::tempdir;
|
||||
use tokio::fs::File;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_cache_operations() {
|
||||
// Crear directorio temporal para pruebas
|
||||
let temp_dir = tempdir().unwrap();
|
||||
let file_path = temp_dir.path().join("test_file.txt");
|
||||
|
||||
// Crear un archivo de prueba
|
||||
let mut file = File::create(&file_path).await.unwrap();
|
||||
file.write_all(b"test content").await.unwrap();
|
||||
file.flush().await.unwrap();
|
||||
drop(file);
|
||||
|
||||
// Crear caché
|
||||
let config = AppConfig::default();
|
||||
let cache = FileMetadataCache::new(config, 1000);
|
||||
|
||||
// Verificar miss inicial
|
||||
assert!(cache.exists(&file_path).await.is_none());
|
||||
|
||||
// Refrescar y verificar hit
|
||||
let metadata = cache.refresh_metadata(&file_path).await.unwrap();
|
||||
assert_eq!(metadata.entry_type, CacheEntryType::File);
|
||||
assert_eq!(metadata.size, Some(12)); // "test content" = 12 bytes
|
||||
|
||||
// Verificar que ahora existe en caché
|
||||
assert_eq!(cache.exists(&file_path).await, Some(true));
|
||||
assert_eq!(cache.is_file(&file_path).await, Some(true));
|
||||
|
||||
// Invalidar y verificar que ya no existe en caché
|
||||
cache.invalidate(&file_path).await;
|
||||
assert!(cache.exists(&file_path).await.is_none());
|
||||
|
||||
// Verificar estadísticas
|
||||
let stats = cache.get_stats().await;
|
||||
assert_eq!(stats.inserts, 1);
|
||||
assert_eq!(stats.invalidations, 1);
|
||||
assert!(stats.hits > 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_directory_operations() {
|
||||
// Crear estructura de directorios para pruebas
|
||||
let temp_dir = tempdir().unwrap();
|
||||
let sub_dir = temp_dir.path().join("subdir");
|
||||
fs::create_dir(&sub_dir).await.unwrap();
|
||||
|
||||
let file1 = temp_dir.path().join("file1.txt");
|
||||
let file2 = sub_dir.join("file2.txt");
|
||||
|
||||
File::create(&file1).await.unwrap();
|
||||
File::create(&file2).await.unwrap();
|
||||
|
||||
// Crear caché
|
||||
let config = AppConfig::default();
|
||||
let cache = FileMetadataCache::new(config, 1000);
|
||||
|
||||
// Precargar directorio recursivamente
|
||||
let count = cache.preload_directory(temp_dir.path(), true, 2).await.unwrap();
|
||||
assert_eq!(count, 3); // dir, subdir, 2 files
|
||||
|
||||
// Verificar existencia en caché
|
||||
assert_eq!(cache.is_dir(temp_dir.path()).await, Some(true));
|
||||
assert_eq!(cache.is_dir(&sub_dir).await, Some(true));
|
||||
assert_eq!(cache.is_file(&file1).await, Some(true));
|
||||
assert_eq!(cache.is_file(&file2).await, Some(true));
|
||||
|
||||
// Invalidar directorio y contenido
|
||||
cache.invalidate_directory(temp_dir.path()).await;
|
||||
|
||||
// Verificar que nada existe en caché
|
||||
assert!(cache.exists(temp_dir.path()).await.is_none());
|
||||
assert!(cache.exists(&sub_dir).await.is_none());
|
||||
assert!(cache.exists(&file1).await.is_none());
|
||||
assert!(cache.exists(&file2).await.is_none());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user