//! Cache Ports — Application-layer abstractions for all caching concerns. //! //! This module defines ports (traits) for: //! - **WriteBehindCachePort**: deferred write caching for zero-latency uploads. //! - **MetadataCachePort**: file/directory metadata caching (existence, size, timestamps). //! - **ContentCachePort**: hot file content caching (small files served from RAM). //! //! The application and interface layers remain independent of the caching //! implementation details. use crate::common::errors::DomainError; use bytes::Bytes; use std::path::{Path, PathBuf}; use std::sync::Arc; /// Statistics for monitoring write-behind cache status. #[derive(Debug, Clone, Default)] pub struct WriteBehindStatsDto { pub pending_count: usize, pub pending_bytes: usize, pub total_writes: u64, pub total_bytes_written: u64, pub cache_hits: u64, pub avg_flush_time_us: u64, } /// Port for write-behind cache operations. /// /// Provides deferred write semantics: small files are held in memory /// and the response is returned immediately, while actual disk writes /// happen asynchronously in the background. pub trait WriteBehindCachePort: Send + Sync + 'static { /// Check if a file size is eligible for write-behind caching. fn is_eligible_size(&self, size: usize) -> bool; /// Put a file in the pending write cache. /// /// Returns `Ok(true)` if cached successfully, `Ok(false)` if cache is full. async fn put_pending( &self, file_id: String, content: Bytes, target_path: PathBuf, ) -> Result; /// Get content from cache if the file is still pending flush. async fn get_pending(&self, file_id: &str) -> Option; /// Check if a file is pending flush. async fn is_pending(&self, file_id: &str) -> bool; /// Force immediate flush of a specific file. async fn force_flush(&self, file_id: &str) -> Result<(), DomainError>; /// Flush all pending writes immediately. async fn flush_all(&self) -> Result<(), DomainError>; /// Gracefully shutdown the cache, flushing all pending writes. async fn shutdown(&self) -> Result<(), DomainError>; /// Get current cache statistics. async fn get_stats(&self) -> WriteBehindStatsDto; } // ─── Metadata Cache ────────────────────────────────────────── /// Lightweight DTO for cached file/directory metadata. #[derive(Debug, Clone)] pub struct CachedMetadataDto { pub path: PathBuf, pub exists: bool, pub is_file: bool, pub size: Option, pub mime_type: Option, pub created_at: Option, pub modified_at: Option, } /// Port for file/directory metadata caching. /// /// Provides fast lookups for existence, size, timestamps and MIME types /// without hitting the filesystem on every request. pub trait MetadataCachePort: Send + Sync + 'static { /// Get cached metadata for a path, or `None` on miss / expired. async fn get_metadata(&self, path: &Path) -> Option; /// Check whether a path is a file (cached). Returns `None` on miss. async fn is_file(&self, path: &Path) -> Option; /// Read actual filesystem metadata and update the cache entry. async fn refresh_metadata(&self, path: &Path) -> Result; /// Invalidate a single cache entry. async fn invalidate(&self, path: &Path); /// Invalidate all entries under a directory (recursive prefix match). async fn invalidate_directory(&self, dir_path: &Path); } // ─── Content Cache ─────────────────────────────────────────── /// Port for hot file content caching (small frequently-accessed files in RAM). /// /// Implementations should use LRU eviction and respect size limits so that /// the application layer never needs to know the concrete cache type. pub trait ContentCachePort: Send + Sync + 'static { /// Check whether a file of the given size should be cached. fn should_cache(&self, size: usize) -> bool; /// Get cached content. Returns `(content, etag, content_type)` on hit. /// /// `etag` and `content_type` are `Arc` so cloning on cache hit is O(1) /// (atomic ref-count increment) instead of O(n) heap allocation. async fn get(&self, file_id: &str) -> Option<(Bytes, Arc, Arc)>; /// Store content in the cache (may evict older entries). async fn put(&self, file_id: String, content: Bytes, etag: Arc, content_type: Arc); /// Remove a file from the cache (e.g. on delete/update). async fn invalidate(&self, file_id: &str); /// Clear the entire cache. async fn clear(&self); }