Files
Oxicloud/src/infrastructure/repositories/folder_fs_repository.rs
T
2025-03-17 21:28:08 +01:00

399 lines
14 KiB
Rust

use std::path::{Path, PathBuf};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use async_trait::async_trait;
use tokio::fs;
use uuid::Uuid;
use serde::{Serialize, Deserialize};
use crate::domain::entities::folder::Folder;
use crate::domain::repositories::folder_repository::{
FolderRepository, FolderRepositoryError, FolderRepositoryResult
};
/// Estructura para almacenar la relación entre paths y IDs
#[derive(Debug, Serialize, Deserialize, Default)]
struct FolderIdMap {
path_to_id: HashMap<String, String>,
}
/// Filesystem implementation of the FolderRepository interface
pub struct FolderFsRepository {
root_path: PathBuf,
id_map: Arc<Mutex<FolderIdMap>>,
}
impl FolderFsRepository {
/// Creates a new filesystem-based folder repository
pub fn new(root_path: PathBuf) -> Self {
// Crear o cargar el mapeo de IDs
let id_map = Arc::new(Mutex::new(FolderIdMap::default()));
// Intentar cargar el mapeo existente si existe
let map_path = root_path.join("folder_ids.json");
if let Ok(contents) = std::fs::read_to_string(map_path) {
if let Ok(loaded_map) = serde_json::from_str::<FolderIdMap>(&contents) {
let mut map = id_map.lock().unwrap();
*map = loaded_map;
tracing::info!("Loaded folder ID map with {} entries", map.path_to_id.len());
}
}
Self { root_path, id_map }
}
/// Guarda el mapeo de IDs a disco
fn save_id_map(&self) {
let map_path = self.root_path.join("folder_ids.json");
let map = self.id_map.lock().unwrap();
if let Ok(json) = serde_json::to_string_pretty(&*map) {
std::fs::write(map_path, json).ok();
}
}
/// Obtiene o genera un ID para un path
fn get_or_create_id(&self, path: &Path) -> String {
let path_str = path.to_string_lossy().to_string();
let mut map = self.id_map.lock().unwrap();
if let Some(id) = map.path_to_id.get(&path_str) {
return id.clone();
}
// Si no existe, genera un nuevo ID
let id = Uuid::new_v4().to_string();
map.path_to_id.insert(path_str, id.clone());
// Guardar el mapa actualizado
drop(map); // Liberar el mutex antes de guardar
self.save_id_map();
id
}
/// Generates a unique ID for a folder
#[allow(dead_code)]
fn generate_id(&self) -> String {
Uuid::new_v4().to_string()
}
/// Resolves a relative path to an absolute path
fn resolve_path(&self, relative_path: &Path) -> PathBuf {
self.root_path.join(relative_path)
}
/// Creates the physical directory on the filesystem
async fn create_directory(&self, path: &Path) -> Result<(), std::io::Error> {
fs::create_dir_all(path).await
}
}
#[async_trait]
impl FolderRepository for FolderFsRepository {
async fn create_folder(&self, name: String, parent_path: Option<PathBuf>) -> FolderRepositoryResult<Folder> {
// Calculate the new folder path
let path = match &parent_path {
Some(parent) => parent.join(&name),
None => PathBuf::from(&name),
};
// Check if folder already exists
if self.folder_exists(&path).await? {
return Err(FolderRepositoryError::AlreadyExists(path.to_string_lossy().to_string()));
}
// Create the physical directory
let abs_path = self.resolve_path(&path);
self.create_directory(&abs_path).await
.map_err(FolderRepositoryError::IoError)?;
// Determine parent ID if any
let parent_id = if let Some(parent) = &parent_path {
if !parent.as_os_str().is_empty() {
let parent_folder = self.get_folder_by_path(parent).await?;
Some(parent_folder.id)
} else {
None
}
} else {
None
};
// Create and return the folder entity with a persisted ID
let id = self.get_or_create_id(&path);
let folder = Folder::new(id, name, path, parent_id);
tracing::debug!("Created folder with ID: {}", folder.id);
Ok(folder)
}
async fn get_folder_by_id(&self, id: &str) -> FolderRepositoryResult<Folder> {
tracing::debug!("Buscando carpeta con ID: {}", id);
// First try to find the path associated with this ID
let path_opt = {
let map = self.id_map.lock().unwrap();
// Invertir el mapeo para buscar por ID
map.path_to_id.iter()
.find_map(|(path, folder_id)| if folder_id == id { Some(path.clone()) } else { None })
};
if let Some(path_str) = path_opt {
let path = PathBuf::from(path_str);
tracing::debug!("Encontrado path para ID {}: {:?}", id, path);
return self.get_folder_by_path(&path).await;
}
// Fallback: buscar a través de todas las carpetas
tracing::debug!("ID {} no encontrado en el mapa, buscando a través de todas las carpetas", id);
let all_folders = self.list_folders(None).await?;
// Imprimir IDs disponibles para depuración
for folder in &all_folders {
tracing::debug!("Carpeta disponible - ID: {}, Nombre: {}", folder.id, folder.name);
}
// Find the folder with the matching ID
all_folders.into_iter()
.find(|folder| folder.id == id)
.ok_or_else(|| FolderRepositoryError::NotFound(id.to_string()))
}
async fn get_folder_by_path(&self, path: &PathBuf) -> FolderRepositoryResult<Folder> {
// Check if the physical directory exists
let abs_path = self.resolve_path(path);
if !abs_path.exists() || !abs_path.is_dir() {
return Err(FolderRepositoryError::NotFound(path.to_string_lossy().to_string()));
}
// Extract folder name and parent path
let name = path.file_name()
.ok_or_else(|| FolderRepositoryError::InvalidPath(path.to_string_lossy().to_string()))?
.to_string_lossy()
.to_string();
let parent_path = path.parent().map(|p| p.to_path_buf());
// Determine parent ID if any
let parent_id = if let Some(parent) = &parent_path {
if !parent.as_os_str().is_empty() {
match self.get_folder_by_path(parent).await {
Ok(parent_folder) => Some(parent_folder.id),
Err(_) => None,
}
} else {
None
}
} else {
None
};
// Get a consistent ID for this path
let id = self.get_or_create_id(path);
tracing::debug!("Found folder with path: {:?}, assigned ID: {}", path, id);
// Get folder metadata for timestamps
let metadata = fs::metadata(&abs_path).await
.map_err(FolderRepositoryError::IoError)?;
let created_at = metadata.created()
.map(|time| time.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs())
.unwrap_or_else(|_| 0);
let modified_at = metadata.modified()
.map(|time| time.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs())
.unwrap_or_else(|_| 0);
// Create and return the folder entity
let mut folder = Folder::new(id, name, path.clone(), parent_id);
folder.created_at = created_at;
folder.modified_at = modified_at;
Ok(folder)
}
async fn list_folders(&self, parent_id: Option<&str>) -> FolderRepositoryResult<Vec<Folder>> {
let parent_path = match parent_id {
Some(id) => {
let parent = self.get_folder_by_id(id).await?;
parent.path
},
None => PathBuf::from(""),
};
let abs_parent_path = self.resolve_path(&parent_path);
let mut folders = Vec::new();
// Read directory entries
let mut entries = fs::read_dir(abs_parent_path).await
.map_err(FolderRepositoryError::IoError)?;
while let Some(entry) = entries.next_entry().await
.map_err(FolderRepositoryError::IoError)? {
let metadata = entry.metadata().await
.map_err(FolderRepositoryError::IoError)?;
// Only include directories
if metadata.is_dir() {
let path = if parent_path.as_os_str().is_empty() {
PathBuf::from(entry.file_name())
} else {
parent_path.join(entry.file_name())
};
match self.get_folder_by_path(&path).await {
Ok(folder) => folders.push(folder),
Err(_) => continue,
}
}
}
Ok(folders)
}
async fn rename_folder(&self, id: &str, new_name: String) -> FolderRepositoryResult<Folder> {
let folder = self.get_folder_by_id(id).await?;
tracing::debug!("Renombrando carpeta con ID: {}, Nombre: {}", id, folder.name);
// Calculate new path
let parent_path = folder.path.parent()
.map(|p| p.to_path_buf())
.unwrap_or_else(|| PathBuf::from(""));
let new_path = if parent_path.as_os_str().is_empty() {
PathBuf::from(&new_name)
} else {
parent_path.join(&new_name)
};
// Check if target already exists
if self.folder_exists(&new_path).await? {
return Err(FolderRepositoryError::AlreadyExists(new_path.to_string_lossy().to_string()));
}
// Rename the physical directory
let abs_old_path = self.resolve_path(&folder.path);
let abs_new_path = self.resolve_path(&new_path);
fs::rename(&abs_old_path, &abs_new_path).await
.map_err(FolderRepositoryError::IoError)?;
// Actualizar el mapa de IDs - eliminar la entrada antigua y añadir la nueva
let path_str = new_path.to_string_lossy().to_string();
{
let mut map = self.id_map.lock().unwrap();
let old_path_str = folder.path.to_string_lossy().to_string();
map.path_to_id.remove(&old_path_str);
map.path_to_id.insert(path_str.clone(), id.to_string());
}
// Guardar el mapa actualizado
self.save_id_map();
// Create and return updated folder entity
let mut updated_folder = Folder::new(
folder.id.clone(),
new_name,
new_path.clone(),
folder.parent_id.clone(),
);
updated_folder.created_at = folder.created_at;
updated_folder.touch();
tracing::debug!("Carpeta renombrada exitosamente: ID={}, Nuevo nombre={}", id, updated_folder.name);
Ok(updated_folder)
}
async fn move_folder(&self, id: &str, new_parent_id: Option<&str>) -> FolderRepositoryResult<Folder> {
let folder = self.get_folder_by_id(id).await?;
tracing::debug!("Moviendo carpeta con ID: {}, Nombre: {}", id, folder.name);
// Get new parent path
let new_parent_path = match new_parent_id {
Some(parent_id) => {
let parent = self.get_folder_by_id(parent_id).await?;
parent.path
},
None => PathBuf::from(""),
};
// Calculate new path
let new_path = if new_parent_path.as_os_str().is_empty() {
PathBuf::from(&folder.name)
} else {
new_parent_path.join(&folder.name)
};
// Check if target already exists
if self.folder_exists(&new_path).await? {
return Err(FolderRepositoryError::AlreadyExists(new_path.to_string_lossy().to_string()));
}
// Move the physical directory
let abs_old_path = self.resolve_path(&folder.path);
let abs_new_path = self.resolve_path(&new_path);
fs::rename(&abs_old_path, &abs_new_path).await
.map_err(FolderRepositoryError::IoError)?;
// Actualizar el mapa de IDs - eliminar la entrada antigua y añadir la nueva
let path_str = new_path.to_string_lossy().to_string();
{
let mut map = self.id_map.lock().unwrap();
let old_path_str = folder.path.to_string_lossy().to_string();
map.path_to_id.remove(&old_path_str);
map.path_to_id.insert(path_str.clone(), id.to_string());
}
// Guardar el mapa actualizado
self.save_id_map();
// Create and return updated folder entity
let new_parent_id = if let Some(parent_id) = new_parent_id {
Some(parent_id.to_string())
} else {
None
};
let mut updated_folder = Folder::new(
folder.id.clone(),
folder.name.clone(),
new_path.clone(),
new_parent_id,
);
updated_folder.created_at = folder.created_at;
updated_folder.touch();
tracing::debug!("Carpeta movida exitosamente: ID={}, Nueva ruta={:?}", id, new_path);
Ok(updated_folder)
}
async fn delete_folder(&self, id: &str) -> FolderRepositoryResult<()> {
let folder = self.get_folder_by_id(id).await?;
tracing::debug!("Eliminando carpeta con ID: {}, Nombre: {}", id, folder.name);
// Delete the physical directory
let abs_path = self.resolve_path(&folder.path);
fs::remove_dir_all(abs_path).await
.map_err(FolderRepositoryError::IoError)?;
// Actualizar el mapa de IDs - eliminar la entrada
{
let mut map = self.id_map.lock().unwrap();
let path_str = folder.path.to_string_lossy().to_string();
map.path_to_id.remove(&path_str);
}
// Guardar el mapa actualizado
self.save_id_map();
tracing::debug!("Carpeta eliminada exitosamente: ID={}", id);
Ok(())
}
async fn folder_exists(&self, path: &PathBuf) -> FolderRepositoryResult<bool> {
let abs_path = self.resolve_path(path);
Ok(abs_path.exists() && abs_path.is_dir())
}
}