fix trash and additional bugs
This commit is contained in:
@@ -5,6 +5,7 @@ use async_trait::async_trait;
|
||||
use tokio::{fs, io::AsyncWriteExt, time};
|
||||
use tokio::fs::File as TokioFile;
|
||||
use tokio_util::codec::{BytesCodec, FramedRead};
|
||||
use tracing::instrument;
|
||||
use mime_guess::from_path;
|
||||
use futures::{Stream, StreamExt};
|
||||
use bytes::Bytes;
|
||||
@@ -20,7 +21,7 @@ use crate::application::ports::outbound::IdMappingPort;
|
||||
use crate::infrastructure::services::id_mapping_service::IdMappingError;
|
||||
use crate::infrastructure::services::file_metadata_cache::{FileMetadataCache, CacheEntryType};
|
||||
use crate::domain::services::path_service::{StoragePath, PathService};
|
||||
use crate::common::errors::{DomainError, ErrorContext};
|
||||
use crate::common::errors::DomainError;
|
||||
use crate::common::config::AppConfig;
|
||||
use crate::application::ports::outbound::FileStoragePort;
|
||||
use crate::infrastructure::repositories::parallel_file_processor::ParallelFileProcessor;
|
||||
@@ -454,23 +455,50 @@ impl FileStoragePort for FileFsRepository {
|
||||
|
||||
#[async_trait]
|
||||
impl FileRepository for FileFsRepository {
|
||||
// Temporary stubs for trash functionality
|
||||
async fn move_to_trash(&self, _file_id: &str) -> FileRepositoryResult<()> {
|
||||
Err(FileRepositoryError::OperationNotSupported(
|
||||
"Trash feature temporarily disabled".to_string()
|
||||
))
|
||||
#[instrument(skip(self))]
|
||||
async fn move_to_trash(&self, file_id: &str) -> FileRepositoryResult<()> {
|
||||
tracing::info!("FileRepository::move_to_trash called for file ID: {}", file_id);
|
||||
// Call the internal implementation for trash handling
|
||||
match self._trash_move_to_trash(file_id).await {
|
||||
Ok(_) => {
|
||||
tracing::info!("File successfully moved to trash: {}", file_id);
|
||||
Ok(())
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to move file to trash: {} - {}", file_id, e);
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn restore_from_trash(&self, _file_id: &str, _original_path: &str) -> FileRepositoryResult<()> {
|
||||
Err(FileRepositoryError::OperationNotSupported(
|
||||
"Trash feature temporarily disabled".to_string()
|
||||
))
|
||||
#[instrument(skip(self))]
|
||||
async fn restore_from_trash(&self, file_id: &str, original_path: &str) -> FileRepositoryResult<()> {
|
||||
tracing::info!("FileRepository::restore_from_trash called for file ID: {} to path: {}", file_id, original_path);
|
||||
match self._trash_restore_from_trash(file_id, original_path).await {
|
||||
Ok(_) => {
|
||||
tracing::info!("File successfully restored from trash: {}", file_id);
|
||||
Ok(())
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to restore file from trash: {} - {}", file_id, e);
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn delete_file_permanently(&self, _file_id: &str) -> FileRepositoryResult<()> {
|
||||
Err(FileRepositoryError::OperationNotSupported(
|
||||
"Trash feature temporarily disabled".to_string()
|
||||
))
|
||||
#[instrument(skip(self))]
|
||||
async fn delete_file_permanently(&self, file_id: &str) -> FileRepositoryResult<()> {
|
||||
tracing::info!("FileRepository::delete_file_permanently called for file ID: {}", file_id);
|
||||
match self._trash_delete_file_permanently(file_id).await {
|
||||
Ok(_) => {
|
||||
tracing::info!("File permanently deleted successfully: {}", file_id);
|
||||
Ok(())
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to delete file permanently: {} - {}", file_id, e);
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
async fn save_file_from_bytes(
|
||||
&self,
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use tokio::fs;
|
||||
use async_trait::async_trait;
|
||||
use tracing::{debug, error, instrument};
|
||||
|
||||
use crate::domain::repositories::file_repository::{FileRepository, FileRepositoryResult};
|
||||
use crate::common::errors::ErrorKind;
|
||||
use crate::domain::repositories::file_repository::FileRepositoryResult;
|
||||
use crate::infrastructure::repositories::file_fs_repository::FileFsRepository;
|
||||
|
||||
// Este archivo contiene la implementación de los métodos relacionados con la papelera
|
||||
@@ -15,37 +12,74 @@ use crate::infrastructure::repositories::file_fs_repository::FileFsRepository;
|
||||
impl FileFsRepository {
|
||||
// Obtiene la ruta completa a la papelera
|
||||
fn get_trash_dir(&self) -> PathBuf {
|
||||
self.get_root_path().join(".trash").join("files")
|
||||
let trash_dir = self.get_root_path().join(".trash").join("files");
|
||||
debug!("Base trash directory: {}", trash_dir.display());
|
||||
trash_dir
|
||||
}
|
||||
|
||||
// Obtiene la ruta de la papelera para un usuario específico (si se proporciona)
|
||||
fn get_user_trash_dir(&self, user_id: Option<&str>) -> PathBuf {
|
||||
let base_trash_dir = self.get_trash_dir();
|
||||
|
||||
if let Some(uid) = user_id {
|
||||
let user_trash_dir = base_trash_dir.join(uid);
|
||||
debug!("User-specific trash directory: {}", user_trash_dir.display());
|
||||
user_trash_dir
|
||||
} else {
|
||||
// Use a default user directory if not specified
|
||||
let default_dir = base_trash_dir.join("00000000-0000-0000-0000-000000000000");
|
||||
debug!("Default user trash directory: {}", default_dir.display());
|
||||
default_dir
|
||||
}
|
||||
}
|
||||
|
||||
// Crea una ruta única en la papelera para el archivo
|
||||
async fn create_trash_file_path(&self, file_id: &str) -> FileRepositoryResult<PathBuf> {
|
||||
let trash_dir = self.get_trash_dir();
|
||||
debug!("Creating trash file path for file ID: {}", file_id);
|
||||
|
||||
// Asegurarse que el directorio de la papelera existe
|
||||
if !trash_dir.exists() {
|
||||
fs::create_dir_all(&trash_dir).await
|
||||
.map_err(|e| FileRepositoryError::IoError(e))?;
|
||||
// Get the trash directory for the default user
|
||||
let user_trash_dir = self.get_user_trash_dir(Some("00000000-0000-0000-0000-000000000000"));
|
||||
|
||||
// Ensure the user's trash directory exists
|
||||
debug!("Ensuring user trash directory exists: {}", user_trash_dir.display());
|
||||
if !user_trash_dir.exists() {
|
||||
debug!("Creating user trash directory: {}", user_trash_dir.display());
|
||||
fs::create_dir_all(&user_trash_dir).await
|
||||
.map_err(|e| {
|
||||
error!("Failed to create user trash directory: {}", e);
|
||||
FileRepositoryError::IoError(e)
|
||||
})?;
|
||||
debug!("User trash directory created successfully");
|
||||
} else {
|
||||
debug!("User trash directory already exists");
|
||||
}
|
||||
|
||||
// Crear una ruta única para el archivo en la papelera
|
||||
Ok(trash_dir.join(file_id))
|
||||
// Create a unique path for the file in the trash
|
||||
let trash_file_path = user_trash_dir.join(file_id);
|
||||
debug!("Trash file path: {}", trash_file_path.display());
|
||||
|
||||
Ok(trash_file_path)
|
||||
}
|
||||
}
|
||||
|
||||
// Implementación de los métodos públicos del trait FileRepository relacionados con la papelera
|
||||
// Note: The FileRepository trait implementation has been moved to file_fs_repository.rs
|
||||
// to avoid duplicate implementations
|
||||
|
||||
// Implementation of internal methods for trash functionality
|
||||
// These will be enabled when the trash feature is re-enabled
|
||||
impl FileFsRepository {
|
||||
/// Helper method that will be used for trash functionality
|
||||
#[allow(dead_code)]
|
||||
pub(crate) async fn _trash_move_to_trash(&self, file_id: &str) -> FileRepositoryResult<()> {
|
||||
debug!("Moviendo archivo a la papelera: {}", file_id);
|
||||
|
||||
// Obtener la ruta física del archivo
|
||||
// Creamos un método independiente para acceder al servicio de mapeo de IDs
|
||||
debug!("Obteniendo ruta del archivo con ID: {}", file_id);
|
||||
let file_path = match self.id_mapping_service().get_file_path(file_id).await {
|
||||
Ok(path) => path,
|
||||
Ok(path) => {
|
||||
debug!("Ruta del archivo obtenida: {}", path.display());
|
||||
path
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Error obteniendo ruta del archivo {}: {:?}", file_id, e);
|
||||
return Err(FileRepositoryError::IdMappingError(format!("Failed to get file path: {}", e)));
|
||||
@@ -53,122 +87,279 @@ impl FileFsRepository {
|
||||
};
|
||||
|
||||
// Verificamos que el archivo existe
|
||||
debug!("Verificando que el archivo existe: {}", file_path.display());
|
||||
if !self.file_exists(&file_path).await? {
|
||||
error!("Archivo no encontrado en la ruta especificada: {}", file_path.display());
|
||||
return Err(FileRepositoryError::NotFound(format!("File not found: {}", file_id)));
|
||||
}
|
||||
debug!("Archivo encontrado, continuando con la operación");
|
||||
|
||||
// Crear directorio en la papelera si no existe
|
||||
debug!("Creando path para archivo en papelera");
|
||||
let trash_file_path = self.create_trash_file_path(file_id).await?;
|
||||
debug!("Path en papelera: {}", trash_file_path.display());
|
||||
|
||||
// Mover el archivo físicamente a la papelera (no actualiza mappings)
|
||||
debug!("Moviendo archivo físicamente a papelera: {} -> {}", file_path.display(), trash_file_path.display());
|
||||
match fs::rename(&file_path, &trash_file_path).await {
|
||||
Ok(_) => {
|
||||
debug!("Archivo movido a papelera: {} -> {}", file_path.display(), trash_file_path.display());
|
||||
debug!("Archivo movido a papelera exitosamente: {} -> {}", file_path.display(), trash_file_path.display());
|
||||
|
||||
// Invalidar la caché del archivo original
|
||||
debug!("Invalidando caché para: {}", file_path.display());
|
||||
self.metadata_cache().invalidate(&file_path).await;
|
||||
|
||||
// Actualizar el mapeo al nuevo path en la papelera
|
||||
debug!("Actualizando mapeo de ID a nuevo path en papelera");
|
||||
if let Err(e) = self.id_mapping_service().update_file_path(file_id, &trash_file_path).await {
|
||||
error!("Error actualizando mapeo de archivo en papelera: {}", e);
|
||||
return Err(FileRepositoryError::MappingError(format!("Failed to update mapping: {}", e)));
|
||||
}
|
||||
debug!("Mapeo actualizado exitosamente");
|
||||
|
||||
debug!("Operación de mover a papelera completada con éxito para el archivo: {}", file_id);
|
||||
Ok(())
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Error moviendo archivo a papelera: {}", e);
|
||||
error!("Error moviendo archivo a papelera: {} -> {}: {}",
|
||||
file_path.display(), trash_file_path.display(), e);
|
||||
Err(FileRepositoryError::IoError(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Restaura un archivo desde la papelera a su ubicación original
|
||||
#[allow(dead_code)]
|
||||
#[instrument(skip(self))]
|
||||
pub(crate) async fn _trash_restore_from_trash(&self, file_id: &str, original_path: &str) -> FileRepositoryResult<()> {
|
||||
debug!("Restaurando archivo {} a {}", file_id, original_path);
|
||||
|
||||
// Obtener la ruta actual en la papelera
|
||||
let current_path = match self.id_mapping_service().get_file_path(file_id).await {
|
||||
Ok(path) => path,
|
||||
Err(e) => {
|
||||
error!("Error obteniendo ruta actual del archivo {}: {:?}", file_id, e);
|
||||
return Err(FileRepositoryError::IdMappingError(format!("Failed to get file path: {}", e)));
|
||||
}
|
||||
};
|
||||
// Try to get the current path from the ID mapping service
|
||||
let current_path_result = self.id_mapping_service().get_file_path(file_id).await;
|
||||
|
||||
// Convertir la ruta original a PathBuf
|
||||
let original_path_buf = PathBuf::from(original_path);
|
||||
|
||||
// Asegurar que el directorio de destino existe
|
||||
if let Some(parent) = original_path_buf.parent() {
|
||||
if !parent.exists() {
|
||||
fs::create_dir_all(parent).await
|
||||
.map_err(|e| {
|
||||
error!("Error creando directorio padre para restauración: {}", e);
|
||||
FileRepositoryError::IoError(e)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
// Mover el archivo de la papelera a su ubicación original
|
||||
match fs::rename(¤t_path, &original_path_buf).await {
|
||||
Ok(_) => {
|
||||
debug!("Archivo restaurado: {} -> {}", current_path.display(), original_path_buf.display());
|
||||
match current_path_result {
|
||||
Ok(current_path) => {
|
||||
debug!("Ruta actual en papelera: {}", current_path.display());
|
||||
|
||||
// Invalidar la caché del archivo en la papelera
|
||||
self.metadata_cache().invalidate(¤t_path).await;
|
||||
// Check if the file exists in the trash
|
||||
let file_exists = match fs::metadata(¤t_path).await {
|
||||
Ok(_) => {
|
||||
debug!("Archivo existe en papelera");
|
||||
true
|
||||
},
|
||||
Err(e) => {
|
||||
debug!("Archivo no existe en papelera: {} - {}", current_path.display(), e);
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
// Actualizar el mapeo a la ruta original
|
||||
if let Err(e) = self.id_mapping_service().update_file_path(file_id, &original_path_buf).await {
|
||||
error!("Error actualizando mapeo de archivo restaurado: {}", e);
|
||||
return Err(FileRepositoryError::MappingError(format!("Failed to update mapping: {}", e)));
|
||||
if !file_exists {
|
||||
error!("El archivo no existe físicamente en la papelera: {}", current_path.display());
|
||||
return Err(FileRepositoryError::NotFound(format!("File not found in trash: {}", file_id)));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
// Parse the original path to a PathBuf
|
||||
let original_path_buf = PathBuf::from(original_path);
|
||||
debug!("Ruta original para restauración: {}", original_path_buf.display());
|
||||
|
||||
// Check if a file already exists at the destination
|
||||
let target_exists = fs::metadata(&original_path_buf).await.is_ok();
|
||||
if target_exists {
|
||||
debug!("Ya existe un archivo en la ruta de destino, generando ruta alternativa");
|
||||
|
||||
// Generate a unique path by adding a suffix
|
||||
// Extract filename and extension
|
||||
let file_name = original_path_buf.file_name()
|
||||
.map(|name| name.to_string_lossy().into_owned())
|
||||
.unwrap_or_else(|| "restored_file".to_string());
|
||||
|
||||
let parent_dir = original_path_buf.parent()
|
||||
.unwrap_or_else(|| std::path::Path::new(""));
|
||||
|
||||
let (stem, ext) = if let Some(dot_pos) = file_name.rfind('.') {
|
||||
(file_name[..dot_pos].to_string(), file_name[dot_pos..].to_string())
|
||||
} else {
|
||||
(file_name, "".to_string())
|
||||
};
|
||||
|
||||
// Create a new name with a timestamp
|
||||
let timestamp = chrono::Utc::now().timestamp();
|
||||
let new_name = format!("{}_{}{}", stem, timestamp, ext);
|
||||
|
||||
// Create the alternative path
|
||||
let alternative_path = parent_dir.join(new_name);
|
||||
debug!("Ruta alternativa para restauración: {}", alternative_path.display());
|
||||
|
||||
// Ensure the parent directory exists
|
||||
if let Some(parent) = alternative_path.parent() {
|
||||
if !parent.exists() {
|
||||
debug!("Creando directorio padre para restauración: {}", parent.display());
|
||||
match fs::create_dir_all(parent).await {
|
||||
Ok(_) => debug!("Directorio padre creado exitosamente"),
|
||||
Err(e) => {
|
||||
error!("Error creando directorio padre: {} - {}", parent.display(), e);
|
||||
return Err(FileRepositoryError::IoError(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Move the file from trash to the alternative location
|
||||
debug!("Moviendo archivo de papelera a ubicación alternativa: {} -> {}",
|
||||
current_path.display(), alternative_path.display());
|
||||
match fs::rename(¤t_path, &alternative_path).await {
|
||||
Ok(_) => {
|
||||
debug!("Archivo restaurado exitosamente a ubicación alternativa");
|
||||
|
||||
// Invalidate cache entries
|
||||
debug!("Invalidando caché para archivo en papelera");
|
||||
self.metadata_cache().invalidate(¤t_path).await;
|
||||
|
||||
// Update the ID mapping
|
||||
debug!("Actualizando mapeo de ID a nueva ubicación");
|
||||
if let Err(e) = self.id_mapping_service().update_file_path(file_id, &alternative_path).await {
|
||||
error!("Error actualizando mapeo de archivo restaurado: {}", e);
|
||||
return Err(FileRepositoryError::MappingError(
|
||||
format!("Failed to update mapping: {}", e)
|
||||
));
|
||||
}
|
||||
|
||||
debug!("Restauración a ubicación alternativa completada con éxito");
|
||||
Ok(())
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Error restaurando archivo a ubicación alternativa: {}", e);
|
||||
Err(FileRepositoryError::IoError(e))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Ensure the parent directory exists
|
||||
if let Some(parent) = original_path_buf.parent() {
|
||||
if !parent.exists() {
|
||||
debug!("Creando directorio padre para restauración: {}", parent.display());
|
||||
match fs::create_dir_all(parent).await {
|
||||
Ok(_) => debug!("Directorio padre creado exitosamente"),
|
||||
Err(e) => {
|
||||
error!("Error creando directorio padre: {} - {}", parent.display(), e);
|
||||
return Err(FileRepositoryError::IoError(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Move the file from trash to its original location
|
||||
debug!("Moviendo archivo de papelera a ubicación original: {} -> {}",
|
||||
current_path.display(), original_path_buf.display());
|
||||
match fs::rename(¤t_path, &original_path_buf).await {
|
||||
Ok(_) => {
|
||||
debug!("Archivo restaurado exitosamente a ubicación original");
|
||||
|
||||
// Invalidate cache entries
|
||||
debug!("Invalidando caché para archivo en papelera");
|
||||
self.metadata_cache().invalidate(¤t_path).await;
|
||||
|
||||
// Update the ID mapping
|
||||
debug!("Actualizando mapeo de ID a ubicación original");
|
||||
if let Err(e) = self.id_mapping_service().update_file_path(file_id, &original_path_buf).await {
|
||||
error!("Error actualizando mapeo de archivo restaurado: {}", e);
|
||||
return Err(FileRepositoryError::MappingError(
|
||||
format!("Failed to update mapping: {}", e)
|
||||
));
|
||||
}
|
||||
|
||||
debug!("Restauración a ubicación original completada con éxito");
|
||||
Ok(())
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Error restaurando archivo a ubicación original: {}", e);
|
||||
Err(FileRepositoryError::IoError(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Error restaurando archivo: {}", e);
|
||||
Err(FileRepositoryError::IoError(e))
|
||||
error!("Error obteniendo ruta actual del archivo {}: {:?}", file_id, e);
|
||||
|
||||
// Check if the error is because the ID was not found
|
||||
if format!("{}", e).contains("not found") {
|
||||
debug!("ID no encontrado en mapeo, archivo ya no existe en papelera");
|
||||
return Err(FileRepositoryError::NotFound(format!("File not found in trash: {}", file_id)));
|
||||
}
|
||||
|
||||
return Err(FileRepositoryError::IdMappingError(
|
||||
format!("Failed to get file path: {}", e)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Elimina un archivo permanentemente (usado por la papelera)
|
||||
#[instrument(skip(self))]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) async fn _trash_delete_file_permanently(&self, file_id: &str) -> FileRepositoryResult<()> {
|
||||
debug!("Eliminando archivo permanentemente: {}", file_id);
|
||||
|
||||
// Este es similar al delete_file pero no verifica permisos ni hace validaciones adicionales
|
||||
let file_path = match self.id_mapping_service().get_file_path(file_id).await {
|
||||
Ok(path) => path,
|
||||
// Get the file path using the ID mapping service
|
||||
let file_path_result = self.id_mapping_service().get_file_path(file_id).await;
|
||||
|
||||
match file_path_result {
|
||||
Ok(file_path) => {
|
||||
debug!("Encontrada ruta para archivo: {} -> {}", file_id, file_path.display());
|
||||
|
||||
// Check if the file physically exists before attempting to delete
|
||||
let file_exists = fs::metadata(&file_path).await.is_ok();
|
||||
|
||||
if file_exists {
|
||||
debug!("Archivo existe físicamente, eliminando: {}", file_path.display());
|
||||
|
||||
// Delete the file physically
|
||||
if let Err(e) = fs::remove_file(&file_path).await {
|
||||
error!("Error eliminando archivo permanentemente: {} - {}", file_path.display(), e);
|
||||
// Don't report error if the file already doesn't exist
|
||||
if e.kind() != std::io::ErrorKind::NotFound {
|
||||
return Err(FileRepositoryError::IoError(e));
|
||||
}
|
||||
} else {
|
||||
debug!("Archivo eliminado físicamente con éxito");
|
||||
}
|
||||
|
||||
// Invalidate cache for this file
|
||||
debug!("Invalidando caché para el archivo: {}", file_path.display());
|
||||
self.metadata_cache().invalidate(&file_path).await;
|
||||
} else {
|
||||
debug!("Archivo no existe físicamente, solo limpiando mapeos: {}", file_path.display());
|
||||
}
|
||||
|
||||
// Always remove the ID mapping regardless of whether the file exists
|
||||
debug!("Eliminando mapeo de ID: {}", file_id);
|
||||
match self.id_mapping_service().remove_id(file_id).await {
|
||||
Ok(_) => debug!("Mapeo de ID eliminado con éxito"),
|
||||
Err(e) => {
|
||||
error!("Error eliminando mapeo del archivo: {}", e);
|
||||
// Only return error for critical mapping errors, otherwise continue
|
||||
if format!("{}", e).contains("not found") {
|
||||
debug!("ID mapping not found, ignoring this error for deletion");
|
||||
} else {
|
||||
return Err(FileRepositoryError::MappingError(format!("Failed to remove mapping: {}", e)));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
debug!("Archivo eliminado permanentemente con éxito: {}", file_id);
|
||||
Ok(())
|
||||
},
|
||||
Err(e) => {
|
||||
// This could happen if the file is already deleted or wasn't properly indexed
|
||||
error!("Error obteniendo ruta del archivo {}: {:?}", file_id, e);
|
||||
|
||||
// Check if the error is because the ID was not found
|
||||
if format!("{}", e).contains("not found") {
|
||||
debug!("ID no encontrado en mapeo, considerando borrado exitoso: {}", file_id);
|
||||
// In this case, we consider the file already deleted
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
return Err(FileRepositoryError::IdMappingError(format!("Failed to get file path: {}", e)));
|
||||
}
|
||||
};
|
||||
|
||||
// Eliminar el archivo físicamente
|
||||
if let Err(e) = fs::remove_file(&file_path).await {
|
||||
error!("Error eliminando archivo permanentemente: {}", e);
|
||||
// No reporte error si el archivo ya no existe
|
||||
if e.kind() != std::io::ErrorKind::NotFound {
|
||||
return Err(FileRepositoryError::IoError(e));
|
||||
}
|
||||
}
|
||||
|
||||
// Invalidar caché
|
||||
self.metadata_cache().invalidate(&file_path).await;
|
||||
|
||||
// Eliminar el mapeo
|
||||
if let Err(e) = self.id_mapping_service().remove_id(file_id).await {
|
||||
error!("Error eliminando mapeo del archivo: {}", e);
|
||||
return Err(FileRepositoryError::MappingError(format!("Failed to remove mapping: {}", e)));
|
||||
}
|
||||
|
||||
debug!("Archivo eliminado permanentemente con éxito: {}", file_id);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::time::Duration;
|
||||
use async_trait::async_trait;
|
||||
use tokio::fs;
|
||||
use tokio::time::timeout;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::domain::entities::folder::{Folder, FolderError};
|
||||
use crate::domain::repositories::folder_repository::{
|
||||
@@ -326,23 +327,22 @@ impl FolderStoragePort for FolderFsRepository {
|
||||
|
||||
#[async_trait]
|
||||
impl FolderRepository for FolderFsRepository {
|
||||
// Temporary stubs for trash functionality
|
||||
async fn move_to_trash(&self, _folder_id: &str) -> FolderRepositoryResult<()> {
|
||||
Err(FolderRepositoryError::OperationNotSupported(
|
||||
"Trash feature temporarily disabled".to_string()
|
||||
))
|
||||
#[instrument(skip(self))]
|
||||
async fn move_to_trash(&self, folder_id: &str) -> FolderRepositoryResult<()> {
|
||||
// Use the private implementation from folder_fs_repository_trash.rs
|
||||
self._trash_move_to_trash(folder_id).await
|
||||
}
|
||||
|
||||
async fn restore_from_trash(&self, _folder_id: &str, _original_path: &str) -> FolderRepositoryResult<()> {
|
||||
Err(FolderRepositoryError::OperationNotSupported(
|
||||
"Trash feature temporarily disabled".to_string()
|
||||
))
|
||||
#[instrument(skip(self))]
|
||||
async fn restore_from_trash(&self, folder_id: &str, original_path: &str) -> FolderRepositoryResult<()> {
|
||||
// Use the private implementation from folder_fs_repository_trash.rs
|
||||
self._trash_restore_from_trash(folder_id, original_path).await
|
||||
}
|
||||
|
||||
async fn delete_folder_permanently(&self, _folder_id: &str) -> FolderRepositoryResult<()> {
|
||||
Err(FolderRepositoryError::OperationNotSupported(
|
||||
"Trash feature temporarily disabled".to_string()
|
||||
))
|
||||
#[instrument(skip(self))]
|
||||
async fn delete_folder_permanently(&self, folder_id: &str) -> FolderRepositoryResult<()> {
|
||||
// Use the private implementation from folder_fs_repository_trash.rs
|
||||
self._trash_delete_folder_permanently(folder_id).await
|
||||
}
|
||||
async fn create_folder(&self, name: String, parent_id: Option<String>) -> FolderRepositoryResult<Folder> {
|
||||
// Get the parent folder path (if any)
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use tokio::fs;
|
||||
use async_trait::async_trait;
|
||||
use tracing::{debug, error, instrument};
|
||||
use tracing::{debug, error};
|
||||
|
||||
use crate::domain::repositories::folder_repository::{FolderRepository, FolderRepositoryResult};
|
||||
use crate::common::errors::ErrorKind;
|
||||
use crate::domain::repositories::folder_repository::FolderRepositoryResult;
|
||||
use crate::infrastructure::repositories::folder_fs_repository::FolderFsRepository;
|
||||
|
||||
// Este archivo contiene la implementación de los métodos relacionados con la papelera
|
||||
@@ -37,8 +34,7 @@ impl FolderFsRepository {
|
||||
// Implementation of internal methods for trash functionality
|
||||
// These will be enabled when the trash feature is re-enabled
|
||||
impl FolderFsRepository {
|
||||
/// Helper method that will be used for trash functionality
|
||||
#[allow(dead_code)]
|
||||
/// Helper method that will be used for trash functionality
|
||||
pub(crate) async fn _trash_move_to_trash(&self, folder_id: &str) -> FolderRepositoryResult<()> {
|
||||
debug!("Moviendo carpeta a la papelera: {}", folder_id);
|
||||
|
||||
@@ -82,7 +78,6 @@ impl FolderFsRepository {
|
||||
}
|
||||
|
||||
/// Restaura una carpeta desde la papelera a su ubicación original
|
||||
#[allow(dead_code)]
|
||||
pub(crate) async fn _trash_restore_from_trash(&self, folder_id: &str, original_path: &str) -> FolderRepositoryResult<()> {
|
||||
debug!("Restaurando carpeta {} a {}", folder_id, original_path);
|
||||
|
||||
@@ -130,7 +125,6 @@ impl FolderFsRepository {
|
||||
}
|
||||
|
||||
/// Elimina una carpeta permanentemente (usado por la papelera)
|
||||
#[allow(dead_code)]
|
||||
pub(crate) async fn _trash_delete_folder_permanently(&self, folder_id: &str) -> FolderRepositoryResult<()> {
|
||||
debug!("Eliminando carpeta permanentemente: {}", folder_id);
|
||||
|
||||
|
||||
@@ -49,13 +49,59 @@ impl TrashFsRepository {
|
||||
|
||||
/// Asegura que existe el directorio de papelera
|
||||
async fn ensure_trash_dir(&self) -> Result<()> {
|
||||
debug!("Checking if trash directory exists: {}", self.trash_dir.display());
|
||||
if !self.trash_dir.exists() {
|
||||
debug!("Trash directory does not exist, creating it: {}", self.trash_dir.display());
|
||||
fs::create_dir_all(&self.trash_dir).await
|
||||
.map_err(|e| DomainError::new(
|
||||
ErrorKind::InternalError,
|
||||
"Trash",
|
||||
format!("Failed to create trash directory: {}", e)
|
||||
))?;
|
||||
.map_err(|e| {
|
||||
error!("Failed to create trash directory {}: {}", self.trash_dir.display(), e);
|
||||
DomainError::new(
|
||||
ErrorKind::InternalError,
|
||||
"Trash",
|
||||
format!("Failed to create trash directory {}: {}", self.trash_dir.display(), e)
|
||||
)
|
||||
})?;
|
||||
debug!("Trash directory created successfully");
|
||||
} else {
|
||||
debug!("Trash directory already exists");
|
||||
}
|
||||
|
||||
// Ensure the files directory exists
|
||||
let files_dir = self.trash_dir.join("files");
|
||||
debug!("Checking if trash files directory exists: {}", files_dir.display());
|
||||
if !files_dir.exists() {
|
||||
debug!("Trash files directory does not exist, creating it: {}", files_dir.display());
|
||||
fs::create_dir_all(&files_dir).await
|
||||
.map_err(|e| {
|
||||
error!("Failed to create trash files directory {}: {}", files_dir.display(), e);
|
||||
DomainError::new(
|
||||
ErrorKind::InternalError,
|
||||
"Trash",
|
||||
format!("Failed to create trash files directory {}: {}", files_dir.display(), e)
|
||||
)
|
||||
})?;
|
||||
debug!("Trash files directory created successfully");
|
||||
} else {
|
||||
debug!("Trash files directory already exists");
|
||||
}
|
||||
|
||||
// Also ensure the folders directory exists
|
||||
let folders_dir = self.trash_dir.join("folders");
|
||||
debug!("Checking if trash folders directory exists: {}", folders_dir.display());
|
||||
if !folders_dir.exists() {
|
||||
debug!("Trash folders directory does not exist, creating it: {}", folders_dir.display());
|
||||
fs::create_dir_all(&folders_dir).await
|
||||
.map_err(|e| {
|
||||
error!("Failed to create trash folders directory {}: {}", folders_dir.display(), e);
|
||||
DomainError::new(
|
||||
ErrorKind::InternalError,
|
||||
"Trash",
|
||||
format!("Failed to create trash folders directory {}: {}", folders_dir.display(), e)
|
||||
)
|
||||
})?;
|
||||
debug!("Trash folders directory created successfully");
|
||||
} else {
|
||||
debug!("Trash folders directory already exists");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -201,17 +247,36 @@ impl TrashRepository for TrashFsRepository {
|
||||
|
||||
// Aseguramos que existe el directorio de la papelera para este usuario
|
||||
let user_trash_dir = self.trash_dir.join("files").join(item.user_id.to_string());
|
||||
fs::create_dir_all(&user_trash_dir).await
|
||||
.map_err(|e| DomainError::new(
|
||||
ErrorKind::InternalError,
|
||||
"Trash",
|
||||
format!("Failed to create user trash directory: {}", e)
|
||||
))?;
|
||||
debug!("User trash directory path: {}", user_trash_dir.display());
|
||||
|
||||
// Añadimos la entrada al índice
|
||||
// Create the user-specific trash directory
|
||||
debug!("Creating user trash directory: {}", user_trash_dir.display());
|
||||
match fs::create_dir_all(&user_trash_dir).await {
|
||||
Ok(_) => debug!("User trash directory created successfully"),
|
||||
Err(e) => {
|
||||
error!("Failed to create user trash directory {}: {}", user_trash_dir.display(), e);
|
||||
return Err(DomainError::new(
|
||||
ErrorKind::InternalError,
|
||||
"Trash",
|
||||
format!("Failed to create user trash directory: {}", e)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Log the current trash entries before adding the new one
|
||||
let mut entries = self.get_trash_entries().await?;
|
||||
entries.push(self.trashed_item_to_entry(item));
|
||||
debug!("Current trash entries count: {}", entries.len());
|
||||
|
||||
// Create the entry for the trash index
|
||||
let entry = self.trashed_item_to_entry(item);
|
||||
debug!("Created trash entry: id={}, original_id={}, name={}",
|
||||
entry.id, entry.original_id, entry.name);
|
||||
|
||||
// Add the entry to the index and save
|
||||
entries.push(entry);
|
||||
debug!("Saving updated trash index with {} entries", entries.len());
|
||||
self.save_trash_entries(entries).await?;
|
||||
debug!("Trash index updated successfully");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -17,6 +17,14 @@ pub struct FileSystemI18nService {
|
||||
}
|
||||
|
||||
impl FileSystemI18nService {
|
||||
/// Create a dummy service for testing
|
||||
pub fn dummy() -> Self {
|
||||
Self {
|
||||
translations_dir: PathBuf::from("/tmp/dummy_translations"),
|
||||
cache: RwLock::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new file system i18n service
|
||||
pub fn new(translations_dir: PathBuf) -> Self {
|
||||
Self {
|
||||
|
||||
@@ -9,7 +9,7 @@ use serde::{Serialize, Deserialize};
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::domain::services::path_service::StoragePath;
|
||||
use crate::common::errors::{DomainError, ErrorKind, ErrorContext};
|
||||
use crate::common::errors::{DomainError, ErrorKind};
|
||||
use crate::application::ports::outbound::IdMappingPort;
|
||||
use crate::common::config::TimeoutConfig;
|
||||
|
||||
@@ -97,6 +97,19 @@ impl IdMappingService {
|
||||
}
|
||||
|
||||
/// Crea un servicio de mapeo de IDs en memoria (para pruebas)
|
||||
///
|
||||
/// Similar functionality as new_in_memory but with a simpler signature for dummy use
|
||||
pub fn dummy() -> Self {
|
||||
Self {
|
||||
map_path: PathBuf::from("/tmp/dummy_id_map.json"),
|
||||
id_map: RwLock::new(IdMap::default()),
|
||||
save_mutex: Mutex::new(()),
|
||||
timeouts: TimeoutConfig::default(),
|
||||
pending_save: RwLock::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
/// Crea un servicio de mapeo de IDs en memoria (para pruebas - versión original)
|
||||
pub fn new_in_memory() -> Self {
|
||||
Self {
|
||||
map_path: PathBuf::from("memory"),
|
||||
|
||||
Reference in New Issue
Block a user