adding features
This commit is contained in:
@@ -24,21 +24,74 @@ OxiCloud incorporates multiple advanced performance optimizations:
|
||||
- **Parallel File Processing**: Automatically splits large files into chunks for parallel processing
|
||||
- **Asynchronous I/O**: Built on Tokio for non-blocking operations
|
||||
- **Worker Pools**: Smart thread management for optimal resource utilization
|
||||
- **Timeout Management**: Strategic timeouts to prevent resource exhaustion
|
||||
|
||||
### Intelligent Caching
|
||||
- **File Metadata Cache**: Drastically reduces filesystem calls
|
||||
- **Smart Cache Invalidation**: Selectively invalidates cache entries
|
||||
- **Preloading**: Strategic preloading for frequently accessed directories
|
||||
- **TTL-Based Cache**: Time-based expiration for optimal memory usage
|
||||
|
||||
### I/O Optimization
|
||||
- **Buffer Pooling**: Reuses memory buffers to reduce GC pressure
|
||||
- **Buffer Pooling**: Reuses memory buffers to reduce allocation pressure
|
||||
- **Adaptive Streaming**: Adjusts chunk sizes based on file size
|
||||
- **Size-Based Processing**: Different strategies for small, medium, and large files
|
||||
- **Non-Blocking Filesystem Operations**: Prevents I/O bottlenecks
|
||||
|
||||
### Batch Processing
|
||||
- **ID Mapping Optimizer**: Groups mapping operations to reduce overhead
|
||||
- **Operation Batching**: Processes multiple file operations concurrently
|
||||
- **Debounced Saving**: Groups write operations for optimal I/O
|
||||
- **Parallel Directory Scanning**: Efficient directory traversal
|
||||
|
||||
## 🧠 Advanced Technical Features
|
||||
|
||||
### Clean Architecture Implementation
|
||||
- **Hexagonal/Ports and Adapters Pattern**: Clear separation between domain, application, and infrastructure
|
||||
- **Dependency Inversion**: Domain business rules are independent of external frameworks
|
||||
- **Explicit Dependency Injection**: Manual, type-safe DI without heavy frameworks
|
||||
|
||||
### Advanced Error Handling
|
||||
- **Domain-Specific Error Types**: Granular error classification with context
|
||||
- **Error Propagation Chain**: Preserves context through abstraction layers
|
||||
- **Custom Error Context**: Enriches errors with additional information
|
||||
- **Source Tracking**: Errors maintain their original source for debugging
|
||||
|
||||
### Robust Repository Pattern
|
||||
- **Persistence Abstraction**: Domain layer completely isolated from storage details
|
||||
- **Repository Interfaces**: Defined in domain layer and implemented in infrastructure
|
||||
- **Storage Mediator Pattern**: Coordinates interactions between repositories
|
||||
- **ID Mapping Service**: Decouples domain identifiers from filesystem paths
|
||||
|
||||
### Transaction Management
|
||||
- **Atomic Operations**: Entity-level transaction support
|
||||
- **Pending Changes System**: Batches persistence operations for efficiency
|
||||
- **Rollback Capabilities**: Reverts state on failed operations
|
||||
- **Optimistic Concurrency**: Protects against concurrent modifications
|
||||
|
||||
### Advanced File System Handling
|
||||
- **Parallel Processing for Large Files**: Chunked operations for efficient I/O
|
||||
- **Specialized Strategies**: Different handlers for various file sizes
|
||||
- **Timeout-Protected Operations**: Prevents hanging on problematic files
|
||||
- **Background Processing**: Heavy operations offloaded to background tasks
|
||||
|
||||
### Memory Efficiency
|
||||
- **Buffer Pool Manager**: Reuses allocated memory to reduce fragmentation
|
||||
- **Streaming I/O**: Processing large files without loading entirely into memory
|
||||
- **Resource-Aware Processing**: Adapts resource usage based on file size
|
||||
- **Lazy Loading**: Loads data only when needed
|
||||
|
||||
### Defensive Programming
|
||||
- **Extensive Input Validation**: Domain entities enforce business rules
|
||||
- **Immutable Data Structures**: Prevents unexpected state mutations
|
||||
- **Fail-Fast Operations**: Early validation to prevent cascading failures
|
||||
- **Extensive Logging**: Structured logs with contextual information
|
||||
|
||||
### Service Layer Optimizations
|
||||
- **Application Services**: Orchestrate use cases with domain entities
|
||||
- **Transaction Coordination**: Ensures data consistency across operations
|
||||
- **Domain Service Specialization**: Services focused on specific domain concerns
|
||||
- **Cross-Cutting Concerns**: Separated into dedicated middleware components
|
||||
|
||||
## 📸 Screenshots
|
||||
|
||||
|
||||
@@ -3,3 +3,8 @@ pub mod ports;
|
||||
pub mod services;
|
||||
pub mod transactions;
|
||||
|
||||
// Re-exportaciones para facilitar el acceso a los principales puertos
|
||||
pub use ports::inbound::FolderUseCase;
|
||||
pub use ports::file_ports::{FileUploadUseCase, FileRetrievalUseCase, FileManagementUseCase, FileUseCaseFactory};
|
||||
pub use ports::outbound::{FolderStoragePort, IdMappingPort};
|
||||
pub use ports::storage_ports::{FileReadPort, FileWritePort, FilePathResolutionPort, StorageVerificationPort, DirectoryManagementPort};
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
use std::sync::Arc;
|
||||
use async_trait::async_trait;
|
||||
use bytes::Bytes;
|
||||
use futures::Stream;
|
||||
|
||||
use crate::application::dtos::file_dto::FileDto;
|
||||
use crate::common::errors::DomainError;
|
||||
|
||||
/// Puerto primario para operaciones de subida de archivos
|
||||
#[async_trait]
|
||||
pub trait FileUploadUseCase: Send + Sync + 'static {
|
||||
/// Sube un nuevo archivo desde bytes
|
||||
async fn upload_file(
|
||||
&self,
|
||||
name: String,
|
||||
folder_id: Option<String>,
|
||||
content_type: String,
|
||||
content: Vec<u8>,
|
||||
) -> Result<FileDto, DomainError>;
|
||||
}
|
||||
|
||||
/// Puerto primario para operaciones de recuperación de archivos
|
||||
#[async_trait]
|
||||
pub trait FileRetrievalUseCase: Send + Sync + 'static {
|
||||
/// Obtiene un archivo por su ID
|
||||
async fn get_file(&self, id: &str) -> Result<FileDto, DomainError>;
|
||||
|
||||
/// Lista archivos en una carpeta
|
||||
async fn list_files(&self, folder_id: Option<&str>) -> Result<Vec<FileDto>, DomainError>;
|
||||
|
||||
/// Obtiene contenido de archivo como bytes (para archivos pequeños)
|
||||
async fn get_file_content(&self, id: &str) -> Result<Vec<u8>, DomainError>;
|
||||
|
||||
/// Obtiene contenido de archivo como stream (para archivos grandes)
|
||||
async fn get_file_stream(&self, id: &str) -> Result<Box<dyn Stream<Item = Result<Bytes, std::io::Error>> + Send>, DomainError>;
|
||||
}
|
||||
|
||||
/// Puerto primario para operaciones de gestión de archivos
|
||||
#[async_trait]
|
||||
pub trait FileManagementUseCase: Send + Sync + 'static {
|
||||
/// Mueve un archivo a otra carpeta
|
||||
async fn move_file(&self, file_id: &str, folder_id: Option<String>) -> Result<FileDto, DomainError>;
|
||||
|
||||
/// Elimina un archivo
|
||||
async fn delete_file(&self, id: &str) -> Result<(), DomainError>;
|
||||
}
|
||||
|
||||
/// Factory para crear implementaciones de casos de uso de archivos
|
||||
pub trait FileUseCaseFactory {
|
||||
fn create_file_upload_use_case(&self) -> Arc<dyn FileUploadUseCase>;
|
||||
fn create_file_retrieval_use_case(&self) -> Arc<dyn FileRetrievalUseCase>;
|
||||
fn create_file_management_use_case(&self) -> Arc<dyn FileManagementUseCase>;
|
||||
}
|
||||
@@ -1,2 +1,4 @@
|
||||
pub mod inbound;
|
||||
pub mod outbound;
|
||||
pub mod file_ports;
|
||||
pub mod storage_ports;
|
||||
@@ -0,0 +1,70 @@
|
||||
use std::path::PathBuf;
|
||||
use async_trait::async_trait;
|
||||
use bytes::Bytes;
|
||||
use futures::Stream;
|
||||
|
||||
use crate::domain::entities::file::File;
|
||||
use crate::domain::services::path_service::StoragePath;
|
||||
use crate::common::errors::DomainError;
|
||||
|
||||
/// Puerto secundario para lectura de archivos
|
||||
#[async_trait]
|
||||
pub trait FileReadPort: Send + Sync + 'static {
|
||||
/// Obtiene un archivo por su ID
|
||||
async fn get_file(&self, id: &str) -> Result<File, DomainError>;
|
||||
|
||||
/// Lista archivos en una carpeta
|
||||
async fn list_files(&self, folder_id: Option<&str>) -> Result<Vec<File>, DomainError>;
|
||||
|
||||
/// Obtiene contenido de archivo como bytes
|
||||
async fn get_file_content(&self, id: &str) -> Result<Vec<u8>, DomainError>;
|
||||
|
||||
/// Obtiene contenido de archivo como stream
|
||||
async fn get_file_stream(&self, id: &str) -> Result<Box<dyn Stream<Item = Result<Bytes, std::io::Error>> + Send>, DomainError>;
|
||||
}
|
||||
|
||||
/// Puerto secundario para escritura de archivos
|
||||
#[async_trait]
|
||||
pub trait FileWritePort: Send + Sync + 'static {
|
||||
/// Guarda un nuevo archivo desde bytes
|
||||
async fn save_file(
|
||||
&self,
|
||||
name: String,
|
||||
folder_id: Option<String>,
|
||||
content_type: String,
|
||||
content: Vec<u8>,
|
||||
) -> Result<File, DomainError>;
|
||||
|
||||
/// Mueve un archivo a otra carpeta
|
||||
async fn move_file(&self, file_id: &str, target_folder_id: Option<String>) -> Result<File, DomainError>;
|
||||
|
||||
/// Elimina un archivo
|
||||
async fn delete_file(&self, id: &str) -> Result<(), DomainError>;
|
||||
}
|
||||
|
||||
/// Puerto secundario para resolución de rutas de archivos
|
||||
#[async_trait]
|
||||
pub trait FilePathResolutionPort: Send + Sync + 'static {
|
||||
/// Obtiene la ruta de almacenamiento de un archivo
|
||||
async fn get_file_path(&self, id: &str) -> Result<StoragePath, DomainError>;
|
||||
|
||||
/// Resuelve una ruta de dominio a una ruta física
|
||||
fn resolve_path(&self, storage_path: &StoragePath) -> PathBuf;
|
||||
}
|
||||
|
||||
/// Puerto secundario para verificación de existencia de archivos/directorios
|
||||
#[async_trait]
|
||||
pub trait StorageVerificationPort: Send + Sync + 'static {
|
||||
/// Verifica si existe un archivo en la ruta dada
|
||||
async fn file_exists(&self, storage_path: &StoragePath) -> Result<bool, DomainError>;
|
||||
|
||||
/// Verifica si existe un directorio en la ruta dada
|
||||
async fn directory_exists(&self, storage_path: &StoragePath) -> Result<bool, DomainError>;
|
||||
}
|
||||
|
||||
/// Puerto secundario para gestión de directorios
|
||||
#[async_trait]
|
||||
pub trait DirectoryManagementPort: Send + Sync + 'static {
|
||||
/// Crea directorios si no existen
|
||||
async fn ensure_directory(&self, storage_path: &StoragePath) -> Result<(), DomainError>;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
use std::sync::Arc;
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::application::dtos::file_dto::FileDto;
|
||||
use crate::application::ports::file_ports::FileManagementUseCase;
|
||||
use crate::application::ports::storage_ports::FileWritePort;
|
||||
use crate::common::errors::DomainError;
|
||||
|
||||
/// Servicio para operaciones de gestión de archivos
|
||||
pub struct FileManagementService {
|
||||
file_repository: Arc<dyn FileWritePort>,
|
||||
}
|
||||
|
||||
impl FileManagementService {
|
||||
/// Crea un nuevo servicio de gestión de archivos
|
||||
pub fn new(file_repository: Arc<dyn FileWritePort>) -> Self {
|
||||
Self { file_repository }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl FileManagementUseCase for FileManagementService {
|
||||
async fn move_file(&self, file_id: &str, folder_id: Option<String>) -> Result<FileDto, DomainError> {
|
||||
tracing::info!("Moviendo archivo con ID: {} a carpeta: {:?}", file_id, folder_id);
|
||||
|
||||
let moved_file = self.file_repository.move_file(file_id, folder_id).await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Error al mover archivo (ID: {}): {}", file_id, e);
|
||||
e
|
||||
})?;
|
||||
|
||||
tracing::info!("Archivo movido exitosamente: {} (ID: {}) a carpeta: {:?}",
|
||||
moved_file.name(), moved_file.id(), moved_file.folder_id());
|
||||
|
||||
Ok(FileDto::from(moved_file))
|
||||
}
|
||||
|
||||
async fn delete_file(&self, id: &str) -> Result<(), DomainError> {
|
||||
self.file_repository.delete_file(id).await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
use std::sync::Arc;
|
||||
use async_trait::async_trait;
|
||||
use bytes::Bytes;
|
||||
use futures::Stream;
|
||||
|
||||
use crate::application::dtos::file_dto::FileDto;
|
||||
use crate::application::ports::file_ports::FileRetrievalUseCase;
|
||||
use crate::application::ports::storage_ports::FileReadPort;
|
||||
use crate::common::errors::DomainError;
|
||||
|
||||
/// Servicio para operaciones de recuperación de archivos
|
||||
pub struct FileRetrievalService {
|
||||
file_repository: Arc<dyn FileReadPort>,
|
||||
}
|
||||
|
||||
impl FileRetrievalService {
|
||||
/// Crea un nuevo servicio de recuperación de archivos
|
||||
pub fn new(file_repository: Arc<dyn FileReadPort>) -> Self {
|
||||
Self { file_repository }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl FileRetrievalUseCase for FileRetrievalService {
|
||||
async fn get_file(&self, id: &str) -> Result<FileDto, DomainError> {
|
||||
let file = self.file_repository.get_file(id).await?;
|
||||
Ok(FileDto::from(file))
|
||||
}
|
||||
|
||||
async fn list_files(&self, folder_id: Option<&str>) -> Result<Vec<FileDto>, DomainError> {
|
||||
let files = self.file_repository.list_files(folder_id).await?;
|
||||
Ok(files.into_iter().map(FileDto::from).collect())
|
||||
}
|
||||
|
||||
async fn get_file_content(&self, id: &str) -> Result<Vec<u8>, DomainError> {
|
||||
self.file_repository.get_file_content(id).await
|
||||
}
|
||||
|
||||
async fn get_file_stream(&self, id: &str) -> Result<Box<dyn Stream<Item = Result<Bytes, std::io::Error>> + Send>, DomainError> {
|
||||
self.file_repository.get_file_stream(id).await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
use std::sync::Arc;
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::application::dtos::file_dto::FileDto;
|
||||
use crate::application::ports::file_ports::FileUploadUseCase;
|
||||
use crate::application::ports::storage_ports::FileWritePort;
|
||||
use crate::common::errors::DomainError;
|
||||
|
||||
/// Servicio para operaciones de subida de archivos
|
||||
pub struct FileUploadService {
|
||||
file_repository: Arc<dyn FileWritePort>,
|
||||
}
|
||||
|
||||
impl FileUploadService {
|
||||
/// Crea un nuevo servicio de subida de archivos
|
||||
pub fn new(file_repository: Arc<dyn FileWritePort>) -> Self {
|
||||
Self { file_repository }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl FileUploadUseCase for FileUploadService {
|
||||
async fn upload_file(
|
||||
&self,
|
||||
name: String,
|
||||
folder_id: Option<String>,
|
||||
content_type: String,
|
||||
content: Vec<u8>,
|
||||
) -> Result<FileDto, DomainError> {
|
||||
let file = self.file_repository.save_file(name, folder_id, content_type, content).await?;
|
||||
Ok(FileDto::from(file))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::application::ports::file_ports::{FileUploadUseCase, FileRetrievalUseCase, FileManagementUseCase, FileUseCaseFactory};
|
||||
use crate::application::services::file_upload_service::FileUploadService;
|
||||
use crate::application::services::file_retrieval_service::FileRetrievalService;
|
||||
use crate::application::services::file_management_service::FileManagementService;
|
||||
use crate::application::ports::storage_ports::{FileReadPort, FileWritePort};
|
||||
|
||||
/// Factory para crear implementaciones de casos de uso de archivos
|
||||
pub struct AppFileUseCaseFactory {
|
||||
file_read_repository: Arc<dyn FileReadPort>,
|
||||
file_write_repository: Arc<dyn FileWritePort>,
|
||||
}
|
||||
|
||||
impl AppFileUseCaseFactory {
|
||||
/// Crea una nueva factory para casos de uso de archivos
|
||||
pub fn new(
|
||||
file_read_repository: Arc<dyn FileReadPort>,
|
||||
file_write_repository: Arc<dyn FileWritePort>
|
||||
) -> Self {
|
||||
Self {
|
||||
file_read_repository,
|
||||
file_write_repository,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FileUseCaseFactory for AppFileUseCaseFactory {
|
||||
fn create_file_upload_use_case(&self) -> Arc<dyn FileUploadUseCase> {
|
||||
Arc::new(FileUploadService::new(self.file_write_repository.clone()))
|
||||
}
|
||||
|
||||
fn create_file_retrieval_use_case(&self) -> Arc<dyn FileRetrievalUseCase> {
|
||||
Arc::new(FileRetrievalService::new(self.file_read_repository.clone()))
|
||||
}
|
||||
|
||||
fn create_file_management_use_case(&self) -> Arc<dyn FileManagementUseCase> {
|
||||
Arc::new(FileManagementService::new(self.file_write_repository.clone()))
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,17 @@
|
||||
pub mod batch_operations;
|
||||
pub mod file_service;
|
||||
pub mod folder_service;
|
||||
pub mod i18n_application_service;
|
||||
pub mod storage_mediator;
|
||||
pub mod batch_operations;
|
||||
|
||||
// Nuevos servicios refactorizados
|
||||
pub mod file_upload_service;
|
||||
pub mod file_retrieval_service;
|
||||
pub mod file_management_service;
|
||||
pub mod file_use_case_factory;
|
||||
|
||||
// Re-exportar para facilitar acceso
|
||||
pub use file_upload_service::FileUploadService;
|
||||
pub use file_retrieval_service::FileRetrievalService;
|
||||
pub use file_management_service::FileManagementService;
|
||||
pub use file_use_case_factory::AppFileUseCaseFactory;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
use async_trait::async_trait;
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -97,9 +98,9 @@ pub trait StorageMediator: Send + Sync + 'static {
|
||||
|
||||
/// Implementación concreta del mediador de almacenamiento
|
||||
pub struct FileSystemStorageMediator {
|
||||
folder_repository: Arc<dyn FolderRepository>,
|
||||
path_service: Arc<PathService>,
|
||||
id_mapping: Arc<dyn IdMappingPort>,
|
||||
pub folder_repository: Arc<dyn FolderRepository>,
|
||||
pub path_service: Arc<PathService>,
|
||||
pub id_mapping: Arc<dyn IdMappingPort>,
|
||||
}
|
||||
|
||||
impl FileSystemStorageMediator {
|
||||
@@ -111,6 +112,86 @@ impl FileSystemStorageMediator {
|
||||
pub fn new_stub() -> StubStorageMediator {
|
||||
StubStorageMediator::new()
|
||||
}
|
||||
|
||||
/// Overload para implementar inicialización diferida con repository placeholder
|
||||
pub fn new_with_lazy_folder(
|
||||
folder_repository: Arc<RwLock<Option<Arc<dyn FolderRepository>>>>,
|
||||
path_service: Arc<PathService>,
|
||||
id_mapping: Arc<dyn IdMappingPort>
|
||||
) -> Self {
|
||||
// Create temporary stub repository
|
||||
let temp_repo = Arc::new(FolderRepositoryStub {});
|
||||
|
||||
Self {
|
||||
folder_repository: temp_repo,
|
||||
path_service,
|
||||
id_mapping,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stub repository for initialization
|
||||
#[derive(Debug)]
|
||||
pub struct FolderRepositoryStub {}
|
||||
|
||||
#[async_trait]
|
||||
impl FolderRepository for FolderRepositoryStub {
|
||||
async fn create_folder(&self, _name: String, _parent_id: Option<String>) -> Result<Folder, FolderRepositoryError> {
|
||||
Err(FolderRepositoryError::Other("Stub repository".to_string()))
|
||||
}
|
||||
|
||||
async fn get_folder_by_id(&self, _id: &str) -> Result<Folder, FolderRepositoryError> {
|
||||
Err(FolderRepositoryError::Other("Stub repository".to_string()))
|
||||
}
|
||||
|
||||
async fn get_folder_by_storage_path(&self, _storage_path: &StoragePath) -> Result<Folder, FolderRepositoryError> {
|
||||
Err(FolderRepositoryError::Other("Stub repository".to_string()))
|
||||
}
|
||||
|
||||
async fn list_folders(&self, _parent_id: Option<&str>) -> Result<Vec<Folder>, FolderRepositoryError> {
|
||||
Err(FolderRepositoryError::Other("Stub repository".to_string()))
|
||||
}
|
||||
|
||||
async fn list_folders_paginated(
|
||||
&self,
|
||||
_parent_id: Option<&str>,
|
||||
_offset: usize,
|
||||
_limit: usize,
|
||||
_include_total: bool
|
||||
) -> Result<(Vec<Folder>, Option<usize>), FolderRepositoryError> {
|
||||
Err(FolderRepositoryError::Other("Stub repository".to_string()))
|
||||
}
|
||||
|
||||
async fn rename_folder(&self, _id: &str, _new_name: String) -> Result<Folder, FolderRepositoryError> {
|
||||
Err(FolderRepositoryError::Other("Stub repository".to_string()))
|
||||
}
|
||||
|
||||
async fn move_folder(&self, _id: &str, _new_parent_id: Option<&str>) -> Result<Folder, FolderRepositoryError> {
|
||||
Err(FolderRepositoryError::Other("Stub repository".to_string()))
|
||||
}
|
||||
|
||||
async fn delete_folder(&self, _id: &str) -> Result<(), FolderRepositoryError> {
|
||||
Err(FolderRepositoryError::Other("Stub repository".to_string()))
|
||||
}
|
||||
|
||||
async fn folder_exists_at_storage_path(&self, _storage_path: &StoragePath) -> Result<bool, FolderRepositoryError> {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
async fn get_folder_storage_path(&self, _id: &str) -> Result<StoragePath, FolderRepositoryError> {
|
||||
Ok(StoragePath::root())
|
||||
}
|
||||
|
||||
// Legacy methods
|
||||
#[allow(deprecated)]
|
||||
async fn folder_exists(&self, _path: &std::path::PathBuf) -> Result<bool, FolderRepositoryError> {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
async fn get_folder_by_path(&self, _path: &std::path::PathBuf) -> Result<Folder, FolderRepositoryError> {
|
||||
Err(FolderRepositoryError::Other("Stub repository".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Stub implementation for initialization dependency issues
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
use std::time::Duration;
|
||||
|
||||
/// Configuración de caché
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CacheConfig {
|
||||
/// TTL para entradas de archivos en caché (ms)
|
||||
pub file_ttl_ms: u64,
|
||||
/// TTL para entradas de directorios en caché (ms)
|
||||
pub directory_ttl_ms: u64,
|
||||
/// Máximo número de entradas en caché
|
||||
pub max_entries: usize,
|
||||
}
|
||||
|
||||
impl Default for CacheConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
file_ttl_ms: 60_000, // 1 minuto
|
||||
directory_ttl_ms: 120_000, // 2 minutos
|
||||
max_entries: 10_000, // 10,000 entradas
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuración de timeouts para diferentes operaciones
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TimeoutConfig {
|
||||
@@ -160,6 +181,8 @@ impl Default for ConcurrencyConfig {
|
||||
/// Configuración global de la aplicación
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AppConfig {
|
||||
/// Configuración de caché
|
||||
pub cache: CacheConfig,
|
||||
/// Configuración de timeouts
|
||||
pub timeouts: TimeoutConfig,
|
||||
/// Configuración de recursos
|
||||
@@ -171,6 +194,7 @@ pub struct AppConfig {
|
||||
impl Default for AppConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
cache: CacheConfig::default(),
|
||||
timeouts: TimeoutConfig::default(),
|
||||
resources: ResourceConfig::default(),
|
||||
concurrency: ConcurrencyConfig::default(),
|
||||
|
||||
+100
-36
@@ -1,6 +1,6 @@
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use async_trait::async_trait;
|
||||
use std::sync::RwLock;
|
||||
|
||||
use crate::domain::services::path_service::PathService;
|
||||
use crate::infrastructure::repositories::folder_fs_repository::FolderFsRepository;
|
||||
@@ -8,20 +8,28 @@ use crate::infrastructure::repositories::file_fs_repository::FileFsRepository;
|
||||
use crate::infrastructure::services::file_system_i18n_service::FileSystemI18nService;
|
||||
use crate::infrastructure::services::id_mapping_service::IdMappingService;
|
||||
use crate::infrastructure::services::cache_manager::StorageCacheManager;
|
||||
use crate::infrastructure::services::file_metadata_cache::FileMetadataCache;
|
||||
use crate::application::services::folder_service::FolderService;
|
||||
use crate::application::services::file_service::FileService;
|
||||
use crate::application::services::i18n_application_service::I18nApplicationService;
|
||||
use crate::application::services::storage_mediator::{StorageMediator, FileSystemStorageMediator};
|
||||
use crate::application::ports::inbound::{FileUseCase, FolderUseCase, UseCaseFactory};
|
||||
use crate::application::ports::outbound::{FileStoragePort, FolderStoragePort};
|
||||
use crate::application::ports::file_ports::{FileUploadUseCase, FileRetrievalUseCase, FileManagementUseCase, FileUseCaseFactory};
|
||||
use crate::application::ports::storage_ports::{FileReadPort, FileWritePort, FilePathResolutionPort};
|
||||
use crate::infrastructure::repositories::{FileMetadataManager, FilePathResolver, FileFsReadRepository, FileFsWriteRepository};
|
||||
use crate::application::services::{FileUploadService, FileRetrievalService, FileManagementService, AppFileUseCaseFactory};
|
||||
use crate::common::errors::DomainError;
|
||||
use crate::domain::services::i18n_service::I18nService;
|
||||
use crate::common::config::AppConfig;
|
||||
use crate::domain::repositories::folder_repository::FolderRepository;
|
||||
|
||||
/// Fábrica para los diferentes componentes de la aplicación
|
||||
#[allow(dead_code)]
|
||||
pub struct AppServiceFactory {
|
||||
storage_path: PathBuf,
|
||||
locales_path: PathBuf,
|
||||
config: AppConfig,
|
||||
}
|
||||
|
||||
impl AppServiceFactory {
|
||||
@@ -31,6 +39,17 @@ impl AppServiceFactory {
|
||||
Self {
|
||||
storage_path,
|
||||
locales_path,
|
||||
config: AppConfig::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Crea una nueva fábrica de servicios con configuración personalizada
|
||||
#[allow(dead_code)]
|
||||
pub fn with_config(storage_path: PathBuf, locales_path: PathBuf, config: AppConfig) -> Self {
|
||||
Self {
|
||||
storage_path,
|
||||
locales_path,
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,18 +82,18 @@ impl AppServiceFactory {
|
||||
path_service,
|
||||
cache_manager,
|
||||
id_mapping_service,
|
||||
config: self.config.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Inicializa los servicios de repositorio
|
||||
/// Inicializa los servicios de repositorio utilizando el patrón Builder mejorado
|
||||
#[allow(dead_code)]
|
||||
pub fn create_repository_services(&self, core: &CoreServices) -> RepositoryServices {
|
||||
// Storage mediator - create first because it's needed by folder repository
|
||||
// (temporarily using a placeholder for folder repository, will update later)
|
||||
let placeholder_folder_repo = Arc::new(FolderFsRepository::new_stub());
|
||||
// Storage mediator - con inicialización diferida para folder repository
|
||||
let folder_repository_holder = Arc::new(RwLock::new(None));
|
||||
|
||||
let storage_mediator = Arc::new(FileSystemStorageMediator::new(
|
||||
placeholder_folder_repo.clone(),
|
||||
let storage_mediator = Arc::new(FileSystemStorageMediator::new_with_lazy_folder(
|
||||
folder_repository_holder.clone(),
|
||||
core.path_service.clone(),
|
||||
core.id_mapping_service.clone()
|
||||
));
|
||||
@@ -87,14 +106,47 @@ impl AppServiceFactory {
|
||||
core.path_service.clone(),
|
||||
));
|
||||
|
||||
// Create a file metadata cache with default configuration
|
||||
// Actualizar el holder para el mediador una vez que el repository está creado
|
||||
if let Ok(mut holder) = folder_repository_holder.write() {
|
||||
*holder = Some(folder_repository.clone());
|
||||
}
|
||||
|
||||
// Metadata cache
|
||||
let metadata_cache = Arc::new(
|
||||
crate::infrastructure::services::file_metadata_cache::FileMetadataCache::default_with_config(
|
||||
crate::common::config::AppConfig::default()
|
||||
)
|
||||
FileMetadataCache::default_with_config(core.config.clone())
|
||||
);
|
||||
|
||||
// File repository
|
||||
// Componentes refactorizados
|
||||
let metadata_manager = Arc::new(FileMetadataManager::new(
|
||||
metadata_cache.clone(),
|
||||
core.config.clone()
|
||||
));
|
||||
|
||||
let path_resolver = Arc::new(FilePathResolver::new(
|
||||
core.path_service.clone(),
|
||||
storage_mediator.clone(),
|
||||
core.id_mapping_service.clone()
|
||||
));
|
||||
|
||||
// File repositories separados para lectura y escritura
|
||||
let file_read_repository = Arc::new(FileFsReadRepository::new(
|
||||
self.storage_path.clone(),
|
||||
metadata_manager.clone(),
|
||||
path_resolver.clone(),
|
||||
core.config.clone(),
|
||||
None // processor will be added later if needed
|
||||
));
|
||||
|
||||
let file_write_repository = Arc::new(FileFsWriteRepository::new(
|
||||
self.storage_path.clone(),
|
||||
metadata_manager.clone(),
|
||||
path_resolver.clone(),
|
||||
storage_mediator.clone(),
|
||||
core.config.clone(),
|
||||
None // processor will be added later if needed
|
||||
));
|
||||
|
||||
// Legacy file repository - mantenido por compatibilidad
|
||||
let file_repository = Arc::new(FileFsRepository::new(
|
||||
self.storage_path.clone(),
|
||||
storage_mediator.clone(),
|
||||
@@ -111,8 +163,12 @@ impl AppServiceFactory {
|
||||
RepositoryServices {
|
||||
folder_repository,
|
||||
file_repository,
|
||||
file_read_repository,
|
||||
file_write_repository,
|
||||
i18n_repository,
|
||||
storage_mediator,
|
||||
metadata_manager,
|
||||
path_resolver,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,10 +180,29 @@ impl AppServiceFactory {
|
||||
repos.folder_repository.clone()
|
||||
));
|
||||
|
||||
// Antiguo servicio único
|
||||
let file_service = Arc::new(FileService::new(
|
||||
repos.file_repository.clone()
|
||||
));
|
||||
|
||||
// Nuevos servicios refactorizados
|
||||
let file_upload_service = Arc::new(FileUploadService::new(
|
||||
repos.file_write_repository.clone()
|
||||
));
|
||||
|
||||
let file_retrieval_service = Arc::new(FileRetrievalService::new(
|
||||
repos.file_read_repository.clone()
|
||||
));
|
||||
|
||||
let file_management_service = Arc::new(FileManagementService::new(
|
||||
repos.file_write_repository.clone()
|
||||
));
|
||||
|
||||
let file_use_case_factory = Arc::new(AppFileUseCaseFactory::new(
|
||||
repos.file_read_repository.clone(),
|
||||
repos.file_write_repository.clone()
|
||||
));
|
||||
|
||||
let i18n_service = Arc::new(I18nApplicationService::new(
|
||||
repos.i18n_repository.clone()
|
||||
));
|
||||
@@ -135,6 +210,10 @@ impl AppServiceFactory {
|
||||
ApplicationServices {
|
||||
folder_service,
|
||||
file_service,
|
||||
file_upload_service,
|
||||
file_retrieval_service,
|
||||
file_management_service,
|
||||
file_use_case_factory,
|
||||
i18n_service,
|
||||
}
|
||||
}
|
||||
@@ -146,6 +225,7 @@ pub struct CoreServices {
|
||||
pub path_service: Arc<PathService>,
|
||||
pub cache_manager: Arc<StorageCacheManager>,
|
||||
pub id_mapping_service: Arc<IdMappingService>,
|
||||
pub config: AppConfig,
|
||||
}
|
||||
|
||||
/// Contenedor para servicios de repositorio
|
||||
@@ -153,8 +233,12 @@ pub struct CoreServices {
|
||||
pub struct RepositoryServices {
|
||||
pub folder_repository: Arc<dyn FolderStoragePort>,
|
||||
pub file_repository: Arc<dyn FileStoragePort>,
|
||||
pub file_read_repository: Arc<dyn FileReadPort>,
|
||||
pub file_write_repository: Arc<dyn FileWritePort>,
|
||||
pub i18n_repository: Arc<dyn I18nService>,
|
||||
pub storage_mediator: Arc<dyn StorageMediator>,
|
||||
pub metadata_manager: Arc<FileMetadataManager>,
|
||||
pub path_resolver: Arc<FilePathResolver>,
|
||||
}
|
||||
|
||||
/// Contenedor para servicios de aplicación
|
||||
@@ -162,29 +246,9 @@ pub struct RepositoryServices {
|
||||
pub struct ApplicationServices {
|
||||
pub folder_service: Arc<dyn FolderUseCase>,
|
||||
pub file_service: Arc<dyn FileUseCase>,
|
||||
pub file_upload_service: Arc<dyn FileUploadUseCase>,
|
||||
pub file_retrieval_service: Arc<dyn FileRetrievalUseCase>,
|
||||
pub file_management_service: Arc<dyn FileManagementUseCase>,
|
||||
pub file_use_case_factory: Arc<dyn FileUseCaseFactory>,
|
||||
pub i18n_service: Arc<I18nApplicationService>,
|
||||
}
|
||||
|
||||
/// Fábrica de casos de uso para la inyección de dependencias
|
||||
#[allow(dead_code)]
|
||||
pub struct AppUseCaseFactory {
|
||||
services: ApplicationServices,
|
||||
}
|
||||
|
||||
impl AppUseCaseFactory {
|
||||
#[allow(dead_code)]
|
||||
pub fn new(services: ApplicationServices) -> Self {
|
||||
Self { services }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl UseCaseFactory for AppUseCaseFactory {
|
||||
fn create_file_use_case(&self) -> Arc<dyn FileUseCase> {
|
||||
self.services.file_service.clone()
|
||||
}
|
||||
|
||||
fn create_folder_use_case(&self) -> Arc<dyn FolderUseCase> {
|
||||
self.services.folder_service.clone()
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,8 @@ pub enum ErrorKind {
|
||||
Timeout,
|
||||
/// Error interno del sistema
|
||||
InternalError,
|
||||
/// Funcionalidad no implementada
|
||||
NotImplemented,
|
||||
}
|
||||
|
||||
impl Display for ErrorKind {
|
||||
@@ -28,6 +30,7 @@ impl Display for ErrorKind {
|
||||
ErrorKind::AccessDenied => write!(f, "Access Denied"),
|
||||
ErrorKind::Timeout => write!(f, "Timeout"),
|
||||
ErrorKind::InternalError => write!(f, "Internal Error"),
|
||||
ErrorKind::NotImplemented => write!(f, "Not Implemented"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,6 +136,17 @@ impl DomainError {
|
||||
}
|
||||
}
|
||||
|
||||
/// Crea un error de funcionalidad no implementada
|
||||
pub fn not_implemented<S: Into<String>>(entity_type: &'static str, message: S) -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::NotImplemented,
|
||||
entity_type,
|
||||
entity_id: None,
|
||||
message: message.into(),
|
||||
source: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Establece el ID de la entidad
|
||||
#[allow(dead_code)]
|
||||
pub fn with_id<S: Into<String>>(mut self, entity_id: S) -> Self {
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use async_trait::async_trait;
|
||||
use bytes::Bytes;
|
||||
use futures::Stream;
|
||||
|
||||
use crate::domain::entities::file::File;
|
||||
use crate::application::ports::storage_ports::FileReadPort;
|
||||
use crate::common::errors::DomainError;
|
||||
use crate::domain::repositories::file_repository::FileRepositoryResult;
|
||||
use crate::infrastructure::repositories::file_metadata_manager::{FileMetadataManager, MetadataError};
|
||||
use crate::infrastructure::repositories::file_path_resolver::FilePathResolver;
|
||||
use crate::domain::services::path_service::StoragePath;
|
||||
use crate::infrastructure::repositories::parallel_file_processor::ParallelFileProcessor;
|
||||
use crate::common::config::AppConfig;
|
||||
|
||||
/// Implementación de repositorio para operaciones de lectura de archivos
|
||||
pub struct FileFsReadRepository {
|
||||
root_path: PathBuf,
|
||||
metadata_manager: Arc<FileMetadataManager>,
|
||||
path_resolver: Arc<FilePathResolver>,
|
||||
config: AppConfig,
|
||||
parallel_processor: Option<Arc<ParallelFileProcessor>>,
|
||||
}
|
||||
|
||||
impl FileFsReadRepository {
|
||||
/// Crea un nuevo repositorio de lectura de archivos
|
||||
pub fn new(
|
||||
root_path: PathBuf,
|
||||
metadata_manager: Arc<FileMetadataManager>,
|
||||
path_resolver: Arc<FilePathResolver>,
|
||||
config: AppConfig,
|
||||
parallel_processor: Option<Arc<ParallelFileProcessor>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
root_path,
|
||||
metadata_manager,
|
||||
path_resolver,
|
||||
config,
|
||||
parallel_processor,
|
||||
}
|
||||
}
|
||||
|
||||
/// Crea una entidad de archivo a partir de metadatos
|
||||
async fn create_file_entity(
|
||||
&self,
|
||||
id: String,
|
||||
name: String,
|
||||
storage_path: StoragePath,
|
||||
size: u64,
|
||||
mime_type: String,
|
||||
folder_id: Option<String>,
|
||||
created_at: Option<u64>,
|
||||
modified_at: Option<u64>,
|
||||
) -> FileRepositoryResult<File> {
|
||||
// If timestamps are provided, use them; otherwise, let File::new create default timestamps
|
||||
if let (Some(created), Some(modified)) = (created_at, modified_at) {
|
||||
File::with_timestamps(
|
||||
id,
|
||||
name,
|
||||
storage_path,
|
||||
size,
|
||||
mime_type,
|
||||
folder_id,
|
||||
created,
|
||||
modified,
|
||||
)
|
||||
.map_err(|e| crate::domain::repositories::file_repository::FileRepositoryError::Other(e.to_string()))
|
||||
} else {
|
||||
File::new(
|
||||
id,
|
||||
name,
|
||||
storage_path,
|
||||
size,
|
||||
mime_type,
|
||||
folder_id,
|
||||
)
|
||||
.map_err(|e| crate::domain::repositories::file_repository::FileRepositoryError::Other(e.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtiene un archivo por su ID
|
||||
async fn get_file_by_id(&self, id: &str) -> FileRepositoryResult<File> {
|
||||
// Obtener la ruta del archivo usando el resolver de rutas
|
||||
let storage_path = self.path_resolver.get_path_by_id(id).await?;
|
||||
|
||||
// Verificar que el archivo existe físicamente
|
||||
let abs_path = self.path_resolver.resolve_storage_path(&storage_path);
|
||||
if !self.metadata_manager.file_exists(&abs_path).await
|
||||
.map_err(|e| crate::domain::repositories::file_repository::FileRepositoryError::Other(e.to_string()))? {
|
||||
return Err(crate::domain::repositories::file_repository::FileRepositoryError::NotFound(
|
||||
format!("File {} not found at {}", id, storage_path.to_string())
|
||||
));
|
||||
}
|
||||
|
||||
// Obtener metadatos del archivo
|
||||
let (size, created_at, modified_at) = self.metadata_manager.get_file_metadata(&abs_path).await
|
||||
.map_err(|e| match e {
|
||||
MetadataError::IoError(io_err) => crate::domain::repositories::file_repository::FileRepositoryError::IoError(io_err),
|
||||
MetadataError::Timeout(msg) => crate::domain::repositories::file_repository::FileRepositoryError::Timeout(msg),
|
||||
MetadataError::Unavailable(msg) => crate::domain::repositories::file_repository::FileRepositoryError::NotFound(msg),
|
||||
})?;
|
||||
|
||||
// Obtener nombre del archivo de la ruta
|
||||
let name = match storage_path.file_name() {
|
||||
Some(name) => name,
|
||||
None => {
|
||||
return Err(crate::domain::repositories::file_repository::FileRepositoryError::InvalidPath(
|
||||
storage_path.to_string()
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
// Determinar ID de carpeta padre
|
||||
let parent = storage_path.parent();
|
||||
let folder_id: Option<String> = if parent.is_none() || parent.as_ref().unwrap().is_empty() {
|
||||
None // Root folder
|
||||
} else {
|
||||
None // En implementación real, buscar ID de la carpeta padre
|
||||
};
|
||||
|
||||
// Determinar tipo MIME
|
||||
let mime_type = mime_guess::from_path(&abs_path)
|
||||
.first_or_octet_stream()
|
||||
.to_string();
|
||||
|
||||
// Crear entidad de archivo
|
||||
let file = self.create_file_entity(
|
||||
id.to_string(),
|
||||
name,
|
||||
storage_path,
|
||||
size,
|
||||
mime_type,
|
||||
folder_id,
|
||||
Some(created_at),
|
||||
Some(modified_at),
|
||||
).await?;
|
||||
|
||||
Ok(file)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl FileReadPort for FileFsReadRepository {
|
||||
async fn get_file(&self, id: &str) -> Result<File, DomainError> {
|
||||
self.get_file_by_id(id).await
|
||||
.map_err(|e| match e {
|
||||
crate::domain::repositories::file_repository::FileRepositoryError::NotFound(msg) => DomainError::not_found("File", msg),
|
||||
crate::domain::repositories::file_repository::FileRepositoryError::IoError(io_err) => DomainError::internal_error("File", io_err.to_string()),
|
||||
crate::domain::repositories::file_repository::FileRepositoryError::Timeout(msg) => DomainError::internal_error("File", msg),
|
||||
_ => DomainError::internal_error("File", e.to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
async fn list_files(&self, folder_id: Option<&str>) -> Result<Vec<File>, DomainError> {
|
||||
// Implementación real debe obtener la lista de archivos en una carpeta
|
||||
// Por ahora, devolvemos lista vacía
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
async fn get_file_content(&self, id: &str) -> Result<Vec<u8>, DomainError> {
|
||||
// Primero obtenemos el archivo para verificar existencia
|
||||
let file = self.get_file_by_id(id).await
|
||||
.map_err(|e| match e {
|
||||
crate::domain::repositories::file_repository::FileRepositoryError::NotFound(msg) => DomainError::not_found("File", msg),
|
||||
crate::domain::repositories::file_repository::FileRepositoryError::IoError(io_err) => DomainError::internal_error("File", io_err.to_string()),
|
||||
crate::domain::repositories::file_repository::FileRepositoryError::Timeout(msg) => DomainError::internal_error("File", msg),
|
||||
_ => DomainError::internal_error("File", e.to_string()),
|
||||
})?;
|
||||
|
||||
// Ruta absoluta del archivo
|
||||
let abs_path = self.path_resolver.resolve_storage_path(file.storage_path());
|
||||
|
||||
// Implementación real debe leer el contenido del archivo
|
||||
// Por ahora, devolvemos un vector vacío
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
async fn get_file_stream(&self, id: &str) -> Result<Box<dyn Stream<Item = Result<Bytes, std::io::Error>> + Send>, DomainError> {
|
||||
// Implementación real debe devolver un stream de bytes del archivo
|
||||
// Por ahora, lanzamos un error
|
||||
Err(DomainError::internal_error("File stream", "Stream functionality not yet implemented"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::domain::entities::file::File;
|
||||
use crate::application::ports::storage_ports::FileWritePort;
|
||||
use crate::common::errors::DomainError;
|
||||
use crate::domain::repositories::file_repository::FileRepositoryResult;
|
||||
use crate::infrastructure::repositories::file_metadata_manager::{FileMetadataManager, MetadataError};
|
||||
use crate::infrastructure::repositories::file_path_resolver::FilePathResolver;
|
||||
use crate::domain::services::path_service::StoragePath;
|
||||
use crate::infrastructure::repositories::parallel_file_processor::ParallelFileProcessor;
|
||||
use crate::common::config::AppConfig;
|
||||
use crate::application::services::storage_mediator::StorageMediator;
|
||||
|
||||
/// Implementación de repositorio para operaciones de escritura de archivos
|
||||
pub struct FileFsWriteRepository {
|
||||
root_path: PathBuf,
|
||||
metadata_manager: Arc<FileMetadataManager>,
|
||||
path_resolver: Arc<FilePathResolver>,
|
||||
storage_mediator: Arc<dyn StorageMediator>,
|
||||
config: AppConfig,
|
||||
parallel_processor: Option<Arc<ParallelFileProcessor>>,
|
||||
}
|
||||
|
||||
impl FileFsWriteRepository {
|
||||
/// Crea un nuevo repositorio de escritura de archivos
|
||||
pub fn new(
|
||||
root_path: PathBuf,
|
||||
metadata_manager: Arc<FileMetadataManager>,
|
||||
path_resolver: Arc<FilePathResolver>,
|
||||
storage_mediator: Arc<dyn StorageMediator>,
|
||||
config: AppConfig,
|
||||
parallel_processor: Option<Arc<ParallelFileProcessor>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
root_path,
|
||||
metadata_manager,
|
||||
path_resolver,
|
||||
storage_mediator,
|
||||
config,
|
||||
parallel_processor,
|
||||
}
|
||||
}
|
||||
|
||||
/// Crea directorios padres si es necesario
|
||||
async fn ensure_parent_directory(&self, abs_path: &PathBuf) -> FileRepositoryResult<()> {
|
||||
if let Some(parent) = abs_path.parent() {
|
||||
tokio::time::timeout(
|
||||
self.config.timeouts.dir_timeout(),
|
||||
tokio::fs::create_dir_all(parent)
|
||||
).await
|
||||
.map_err(|_| crate::domain::repositories::file_repository::FileRepositoryError::Timeout(
|
||||
format!("Timeout creating parent directory: {}", parent.display())
|
||||
))?
|
||||
.map_err(crate::domain::repositories::file_repository::FileRepositoryError::IoError)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Crea una entidad de archivo a partir de metadatos
|
||||
async fn create_file_entity(
|
||||
&self,
|
||||
id: String,
|
||||
name: String,
|
||||
storage_path: StoragePath,
|
||||
size: u64,
|
||||
mime_type: String,
|
||||
folder_id: Option<String>,
|
||||
created_at: Option<u64>,
|
||||
modified_at: Option<u64>,
|
||||
) -> FileRepositoryResult<File> {
|
||||
// If timestamps are provided, use them; otherwise, let File::new create default timestamps
|
||||
if let (Some(created), Some(modified)) = (created_at, modified_at) {
|
||||
File::with_timestamps(
|
||||
id,
|
||||
name,
|
||||
storage_path,
|
||||
size,
|
||||
mime_type,
|
||||
folder_id,
|
||||
created,
|
||||
modified,
|
||||
)
|
||||
.map_err(|e| crate::domain::repositories::file_repository::FileRepositoryError::Other(e.to_string()))
|
||||
} else {
|
||||
File::new(
|
||||
id,
|
||||
name,
|
||||
storage_path,
|
||||
size,
|
||||
mime_type,
|
||||
folder_id,
|
||||
)
|
||||
.map_err(|e| crate::domain::repositories::file_repository::FileRepositoryError::Other(e.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Elimina un archivo de forma no bloqueante
|
||||
async fn delete_file_non_blocking(&self, _abs_path: PathBuf) -> FileRepositoryResult<()> {
|
||||
// Implementación real debe eliminar el archivo
|
||||
// Por ahora, devolvemos OK
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl FileWritePort for FileFsWriteRepository {
|
||||
async fn save_file(
|
||||
&self,
|
||||
name: String,
|
||||
folder_id: Option<String>,
|
||||
content_type: String,
|
||||
content: Vec<u8>,
|
||||
) -> Result<File, DomainError> {
|
||||
// Implementación real debe guardar el archivo en disco
|
||||
// Por ahora, devolvemos un error
|
||||
Err(DomainError::internal_error("File save", "Save functionality not yet implemented"))
|
||||
}
|
||||
|
||||
async fn move_file(&self, file_id: &str, target_folder_id: Option<String>) -> Result<File, DomainError> {
|
||||
// Implementación real debe mover el archivo a otra carpeta
|
||||
// Por ahora, devolvemos un error
|
||||
Err(DomainError::internal_error("File move", "Move functionality not yet implemented"))
|
||||
}
|
||||
|
||||
async fn delete_file(&self, id: &str) -> Result<(), DomainError> {
|
||||
// Implementación real debe eliminar el archivo
|
||||
// Por ahora, devolvemos un error
|
||||
Err(DomainError::internal_error("File delete", "Delete functionality not yet implemented"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use tokio::time;
|
||||
use tokio::fs;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::infrastructure::services::file_metadata_cache::{FileMetadataCache, CacheEntryType, FileMetadata};
|
||||
use crate::common::config::AppConfig;
|
||||
use crate::common::errors::DomainError;
|
||||
|
||||
/// Gestor de metadatos de archivos que encapsula la lógica de caché
|
||||
pub struct FileMetadataManager {
|
||||
metadata_cache: Arc<FileMetadataCache>,
|
||||
config: AppConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum MetadataError {
|
||||
#[error("Error de E/S al acceder a los metadatos: {0}")]
|
||||
IoError(#[from] std::io::Error),
|
||||
|
||||
#[error("Timeout al acceder a los metadatos: {0}")]
|
||||
Timeout(String),
|
||||
|
||||
#[error("Metadatos no disponibles: {0}")]
|
||||
Unavailable(String),
|
||||
}
|
||||
|
||||
impl From<MetadataError> for DomainError {
|
||||
fn from(err: MetadataError) -> Self {
|
||||
match err {
|
||||
MetadataError::IoError(e) => DomainError::internal_error("FileMetadata", e.to_string()),
|
||||
MetadataError::Timeout(msg) => DomainError::internal_error("FileMetadata", msg),
|
||||
MetadataError::Unavailable(msg) => DomainError::not_found("FileMetadata", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FileMetadataManager {
|
||||
/// Crea un nuevo gestor de metadatos
|
||||
pub fn new(metadata_cache: Arc<FileMetadataCache>, config: AppConfig) -> Self {
|
||||
Self {
|
||||
metadata_cache,
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
/// Comprueba si un archivo existe en la ruta especificada con caché
|
||||
pub async fn file_exists(&self, abs_path: &PathBuf) -> Result<bool, MetadataError> {
|
||||
// Intentar obtener del caché avanzado primero
|
||||
if let Some(is_file) = self.metadata_cache.is_file(&abs_path).await {
|
||||
tracing::debug!("Metadata cache hit for existence check: {} - path: {}", is_file, abs_path.display());
|
||||
return Ok(is_file);
|
||||
}
|
||||
|
||||
// Si no está en caché, verificar directamente y actualizar caché
|
||||
tracing::debug!("Metadata cache miss for existence check: {}", abs_path.display());
|
||||
|
||||
// Utilizar timeout para evitar bloqueo
|
||||
match time::timeout(
|
||||
self.config.timeouts.file_timeout(),
|
||||
fs::metadata(&abs_path)
|
||||
).await {
|
||||
Ok(Ok(metadata)) => {
|
||||
let is_file = metadata.is_file();
|
||||
|
||||
// Actualizar la caché con información fresca
|
||||
if let Err(e) = self.metadata_cache.refresh_metadata(&abs_path).await {
|
||||
tracing::warn!("Failed to update cache for {}: {}", abs_path.display(), e);
|
||||
}
|
||||
|
||||
if is_file {
|
||||
tracing::debug!("File exists and is accessible: {}", abs_path.display());
|
||||
Ok(true)
|
||||
} else {
|
||||
tracing::warn!("Path exists but is not a file: {}", abs_path.display());
|
||||
Ok(false)
|
||||
}
|
||||
},
|
||||
Ok(Err(e)) => {
|
||||
tracing::warn!("File check failed: {} - {}", abs_path.display(), e);
|
||||
|
||||
// Añadir a caché como no existente
|
||||
let entry_type = CacheEntryType::Unknown;
|
||||
let file_metadata = FileMetadata::new(
|
||||
abs_path.clone(),
|
||||
false,
|
||||
entry_type,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Duration::from_millis(self.config.timeouts.file_operation_ms),
|
||||
);
|
||||
self.metadata_cache.update_cache(file_metadata).await;
|
||||
|
||||
Ok(false)
|
||||
},
|
||||
Err(_) => {
|
||||
tracing::warn!("Timeout checking file metadata: {}", abs_path.display());
|
||||
Err(MetadataError::Timeout(format!("Timeout checking file: {}", abs_path.display())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtiene metadatos de archivo (tamaño, fechas creación/modificación) con caché
|
||||
pub async fn get_file_metadata(&self, abs_path: &PathBuf) -> Result<(u64, u64, u64), MetadataError> {
|
||||
// Intentar obtener de caché primero
|
||||
if let Some(cached_metadata) = self.metadata_cache.get_metadata(abs_path).await {
|
||||
if let (Some(size), Some(created_at), Some(modified_at)) =
|
||||
(cached_metadata.size, cached_metadata.created_at, cached_metadata.modified_at) {
|
||||
tracing::debug!("Using cached metadata for: {}", abs_path.display());
|
||||
return Ok((size, created_at, modified_at));
|
||||
}
|
||||
}
|
||||
|
||||
// Si no está en caché o metadatos incompletos, cargar desde sistema de archivos
|
||||
let metadata = match time::timeout(
|
||||
self.config.timeouts.file_timeout(),
|
||||
fs::metadata(&abs_path)
|
||||
).await {
|
||||
Ok(Ok(metadata)) => metadata,
|
||||
Ok(Err(e)) => return Err(MetadataError::IoError(e)),
|
||||
Err(_) => return Err(MetadataError::Timeout(
|
||||
format!("Timeout getting metadata for: {}", abs_path.display())
|
||||
)),
|
||||
};
|
||||
|
||||
let size = metadata.len();
|
||||
|
||||
// Get creation timestamp
|
||||
let created_at = metadata.created()
|
||||
.map(|time| time.duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_secs())
|
||||
.unwrap_or_else(|_| 0);
|
||||
|
||||
// Get modification timestamp
|
||||
let modified_at = metadata.modified()
|
||||
.map(|time| time.duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_secs())
|
||||
.unwrap_or_else(|_| 0);
|
||||
|
||||
// Actualizar caché si es posible
|
||||
if let Err(e) = self.metadata_cache.refresh_metadata(abs_path).await {
|
||||
tracing::warn!("Failed to update metadata cache for {}: {}", abs_path.display(), e);
|
||||
}
|
||||
|
||||
Ok((size, created_at, modified_at))
|
||||
}
|
||||
|
||||
/// Invalida la entrada de caché para un archivo
|
||||
pub async fn invalidate(&self, abs_path: &PathBuf) {
|
||||
self.metadata_cache.invalidate(abs_path).await;
|
||||
}
|
||||
|
||||
/// Invalida la entrada de caché para un directorio y su contenido
|
||||
pub async fn invalidate_directory(&self, dir_path: &PathBuf) {
|
||||
self.metadata_cache.invalidate_directory(dir_path).await;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::domain::services::path_service::{PathService, StoragePath};
|
||||
use crate::application::services::storage_mediator::StorageMediator;
|
||||
use crate::infrastructure::services::id_mapping_service::{IdMappingService, IdMappingError};
|
||||
use crate::domain::repositories::file_repository::FileRepositoryError;
|
||||
use crate::common::errors::DomainError;
|
||||
use crate::application::ports::storage_ports::FilePathResolutionPort;
|
||||
|
||||
/// Resuelve rutas de archivos y gestiona el mapeo de IDs a rutas
|
||||
pub struct FilePathResolver {
|
||||
path_service: Arc<PathService>,
|
||||
storage_mediator: Arc<dyn StorageMediator>,
|
||||
id_mapping_service: Arc<IdMappingService>,
|
||||
}
|
||||
|
||||
impl FilePathResolver {
|
||||
/// Crea un nuevo resolver de rutas
|
||||
pub fn new(
|
||||
path_service: Arc<PathService>,
|
||||
storage_mediator: Arc<dyn StorageMediator>,
|
||||
id_mapping_service: Arc<IdMappingService>,
|
||||
) -> Self {
|
||||
Self {
|
||||
path_service,
|
||||
storage_mediator,
|
||||
id_mapping_service,
|
||||
}
|
||||
}
|
||||
|
||||
/// Resuelve una ruta de dominio a una ruta física absoluta
|
||||
pub fn resolve_storage_path(&self, storage_path: &StoragePath) -> PathBuf {
|
||||
self.path_service.resolve_path(storage_path)
|
||||
}
|
||||
|
||||
/// Resuelve una ruta PathBuf a una ruta física absoluta (legacy)
|
||||
pub fn resolve_legacy_path(&self, relative_path: &std::path::Path) -> PathBuf {
|
||||
self.storage_mediator.resolve_path(relative_path)
|
||||
}
|
||||
|
||||
/// Obtiene la ruta de un archivo por su ID
|
||||
pub async fn get_path_by_id(&self, id: &str) -> Result<StoragePath, FileRepositoryError> {
|
||||
self.id_mapping_service.get_path_by_id(id).await
|
||||
.map_err(FileRepositoryError::from)
|
||||
}
|
||||
|
||||
/// Actualiza la ruta para un ID existente
|
||||
pub async fn update_path(&self, id: &str, storage_path: &StoragePath) -> Result<(), FileRepositoryError> {
|
||||
self.id_mapping_service.update_path(id, storage_path).await
|
||||
.map_err(FileRepositoryError::from)
|
||||
}
|
||||
|
||||
/// Obtiene o crea un ID para una ruta
|
||||
pub async fn get_or_create_id(&self, storage_path: &StoragePath) -> Result<String, FileRepositoryError> {
|
||||
self.id_mapping_service.get_or_create_id(storage_path).await
|
||||
.map_err(FileRepositoryError::from)
|
||||
}
|
||||
|
||||
/// Elimina un ID del mapeo
|
||||
pub async fn remove_id(&self, id: &str) -> Result<(), FileRepositoryError> {
|
||||
self.id_mapping_service.remove_id(id).await
|
||||
.map_err(FileRepositoryError::from)
|
||||
}
|
||||
|
||||
/// Guarda cambios pendientes
|
||||
pub async fn save_changes(&self) -> Result<(), FileRepositoryError> {
|
||||
self.id_mapping_service.save_pending_changes().await
|
||||
.map_err(FileRepositoryError::from)
|
||||
}
|
||||
}
|
||||
|
||||
// Implementación de FilePathResolutionPort
|
||||
#[async_trait]
|
||||
impl FilePathResolutionPort for FilePathResolver {
|
||||
async fn get_file_path(&self, id: &str) -> Result<StoragePath, DomainError> {
|
||||
self.get_path_by_id(id).await
|
||||
.map_err(|e| match e {
|
||||
FileRepositoryError::NotFound(id) => DomainError::not_found("File", id),
|
||||
FileRepositoryError::IoError(e) => DomainError::internal_error("FilePath", e.to_string()),
|
||||
FileRepositoryError::Timeout(msg) => DomainError::internal_error("FilePath", msg),
|
||||
_ => DomainError::internal_error("FilePath", e.to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_path(&self, storage_path: &StoragePath) -> PathBuf {
|
||||
self.resolve_storage_path(storage_path)
|
||||
}
|
||||
}
|
||||
@@ -2,3 +2,14 @@ pub mod file_fs_repository;
|
||||
pub mod folder_fs_repository;
|
||||
pub mod parallel_file_processor;
|
||||
|
||||
// Nuevos repositorios refactorizados
|
||||
pub mod file_metadata_manager;
|
||||
pub mod file_path_resolver;
|
||||
pub mod file_fs_read_repository;
|
||||
pub mod file_fs_write_repository;
|
||||
|
||||
// Re-exportar para facilitar acceso
|
||||
pub use file_metadata_manager::FileMetadataManager;
|
||||
pub use file_path_resolver::FilePathResolver;
|
||||
pub use file_fs_read_repository::FileFsReadRepository;
|
||||
pub use file_fs_write_repository::FileFsWriteRepository;
|
||||
Reference in New Issue
Block a user