diff --git a/src/infrastructure/repositories/file_fs_repository.rs b/src/infrastructure/repositories/file_fs_repository.rs
index f0539d70..2638ff10 100644
--- a/src/infrastructure/repositories/file_fs_repository.rs
+++ b/src/infrastructure/repositories/file_fs_repository.rs
@@ -677,9 +677,14 @@ impl FileRepository for FileFsRepository {
match self.storage_mediator.get_folder_path(id).await {
Ok(path) => {
tracing::info!("Using folder path: {:?} for folder_id: {:?}", path, id);
- // Convert to StoragePath
- let path_str = path.to_string_lossy().to_string();
- StoragePath::from_string(&path_str)
+ // Convert to StoragePath - use just the folder name to avoid path duplication
+ // Get just the folder name to avoid path duplication
+ let lossy = path.to_string_lossy().to_string();
+ let folder_name = path.file_name()
+ .and_then(|f| f.to_str())
+ .unwrap_or_else(|| &lossy);
+ tracing::info!("Using folder name: {} for StoragePath", folder_name);
+ StoragePath::from_string(folder_name)
},
Err(e) => {
tracing::error!("Error getting folder: {}", e);
@@ -863,13 +868,56 @@ impl FileRepository for FileFsRepository {
).await?;
// Ensure ID mapping is persisted - this is critical for later retrieval
- let save_result = self.id_mapping_service.save_changes().await;
- if let Err(e) = &save_result {
- tracing::error!("Failed to save ID mapping for file {}: {}", id, e);
- } else {
- tracing::info!("Successfully saved ID mapping for file ID: {} -> path: {}", id, path_string);
+ // Ejecutar múltiples intentos de guardado con verificación para garantizar persistencia
+ for attempt in 1..=3 {
+ match self.id_mapping_service.save_changes().await {
+ Ok(_) => {
+ tracing::info!("Successfully saved ID mapping for file ID: {} -> path: {} (attempt {})", id, path_string, attempt);
+
+ // Verificar que el mapeo se puede recuperar después de guardado
+ if let Ok(verified_path) = self.id_mapping_service.get_path_by_id(&id).await {
+ if verified_path.to_string() == path_string {
+ tracing::info!("Verified ID mapping is retrievable after save: {} -> {}", id, path_string);
+ break; // Guaradado correcto y verificado, salir del bucle
+ } else {
+ tracing::error!("Mapping verification failed: expected {} but got {}", path_string, verified_path.to_string());
+ if attempt < 3 {
+ tracing::info!("Will retry saving ID mapping (attempt {}/3)", attempt + 1);
+ tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
+ continue;
+ } else {
+ return Err(FileRepositoryError::Other(
+ format!("Failed to verify ID mapping for file: {} after 3 attempts", id)
+ ));
+ }
+ }
+ } else {
+ tracing::error!("Cannot verify mapping, ID {} not found after save", id);
+ if attempt < 3 {
+ tracing::info!("Will retry saving ID mapping (attempt {}/3)", attempt + 1);
+ tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
+ continue;
+ } else {
+ return Err(FileRepositoryError::Other(
+ format!("Failed to verify ID mapping for file: {} after 3 attempts", id)
+ ));
+ }
+ }
+ },
+ Err(e) => {
+ tracing::error!("Failed to save ID mapping for file {}: {} (attempt {})", id, e, attempt);
+ if attempt < 3 {
+ tracing::info!("Will retry saving ID mapping (attempt {}/3)", attempt + 1);
+ tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
+ continue;
+ } else {
+ return Err(FileRepositoryError::Other(
+ format!("Failed to save ID mapping for file: {} after 3 attempts - {}", id, e)
+ ));
+ }
+ }
+ }
}
- save_result?;
// Invalidate any directory cache entries for the parent folders
// to ensure directory listings show the new file
@@ -896,9 +944,14 @@ impl FileRepository for FileFsRepository {
match self.storage_mediator.get_folder_path(fid).await {
Ok(path) => {
tracing::info!("Using folder path: {:?} for folder_id: {:?}", path, fid);
- // Convert to StoragePath
- let path_str = path.to_string_lossy().to_string();
- StoragePath::from_string(&path_str)
+ // Convert to StoragePath - use just the folder name to avoid path duplication
+ // Get just the folder name to avoid path duplication
+ let lossy = path.to_string_lossy().to_string();
+ let folder_name = path.file_name()
+ .and_then(|f| f.to_str())
+ .unwrap_or_else(|| &lossy);
+ tracing::info!("Using folder name: {} for StoragePath", folder_name);
+ StoragePath::from_string(folder_name)
},
Err(e) => {
tracing::error!("Error getting folder: {}", e);
@@ -1143,8 +1196,14 @@ impl FileRepository for FileFsRepository {
match self.storage_mediator.get_folder_path(id).await {
Ok(path) => {
tracing::info!("Found folder with path: {:?}", path);
- let path_str = path.to_string_lossy().to_string();
- StoragePath::from_string(&path_str)
+ // Convert to StoragePath - use just the folder name to avoid path duplication
+ // Get just the folder name to avoid path duplication
+ let lossy = path.to_string_lossy().to_string();
+ let folder_name = path.file_name()
+ .and_then(|f| f.to_str())
+ .unwrap_or_else(|| &lossy);
+ tracing::info!("Using folder name: {} for StoragePath", folder_name);
+ StoragePath::from_string(folder_name)
},
Err(e) => {
tracing::error!("Error getting folder by ID: {}: {}", id, e);
@@ -1155,8 +1214,8 @@ impl FileRepository for FileFsRepository {
None => StoragePath::root(),
};
- // Get the absolute folder path
- let abs_folder_path = self.resolve_storage_path(&folder_storage_path);
+ // Get the absolute folder path without duplicate ./storage prefix
+ let abs_folder_path = self.path_service.resolve_path(&folder_storage_path);
tracing::info!("Absolute folder path: {:?}", abs_folder_path);
// Check if the directory exists
@@ -1475,8 +1534,14 @@ impl FileRepository for FileFsRepository {
Some(folder_id) => {
match self.storage_mediator.get_folder_path(folder_id).await {
Ok(path) => {
- let path_str = path.to_string_lossy().to_string();
- StoragePath::from_string(&path_str)
+ // Convert to StoragePath - use just the folder name to avoid path duplication
+ // Get just the folder name to avoid path duplication
+ let lossy = path.to_string_lossy().to_string();
+ let folder_name = path.file_name()
+ .and_then(|f| f.to_str())
+ .unwrap_or_else(|| &lossy);
+ tracing::info!("Target folder name: {} for StoragePath", folder_name);
+ StoragePath::from_string(folder_name)
},
Err(e) => {
return Err(FileRepositoryError::Other(
diff --git a/src/infrastructure/services/id_mapping_service.rs b/src/infrastructure/services/id_mapping_service.rs
index 446b17f9..d3395b7a 100644
--- a/src/infrastructure/services/id_mapping_service.rs
+++ b/src/infrastructure/services/id_mapping_service.rs
@@ -70,7 +70,7 @@ struct IdMap {
}
/// Constantes para configuración
-const SAVE_DEBOUNCE_MS: u64 = 300; // Tiempo para agrupar operaciones de guardado
+const SAVE_DEBOUNCE_MS: u64 = 0; // Sin debounce para garantizar guardado inmediato
/// Servicio para gestionar mapeos entre rutas y IDs únicos
pub struct IdMappingService {
@@ -389,7 +389,7 @@ impl IdMappingService {
}
}
- /// Guarda cambios pendientes al disco
+ /// Guarda cambios pendientes al disco inmediatamente, sin debounce
pub async fn save_pending_changes(&self) -> Result<(), IdMappingError> {
// Verificar si hay cambios pendientes
{
@@ -399,20 +399,55 @@ impl IdMappingService {
}
}
- // Implementar debounce para agrupación de guardados
- let map_path = self.map_path.clone();
- let self_clone = self.clone();
-
- tokio::spawn(async move {
- // Esperar un poco para permitir la agrupación de operaciones
- time::sleep(Duration::from_millis(SAVE_DEBOUNCE_MS)).await;
-
- if let Err(e) = self_clone.save_id_map().await {
- tracing::error!("Failed to save ID map to {}: {}", map_path.display(), e);
+ // Guardar inmediatamente (sin debounce ni spawn)
+ match self.save_id_map().await {
+ Ok(_) => {
+ tracing::info!("ID mappings saved successfully to disk at {}", self.map_path.display());
+
+ // Verificar explícitamente que el archivo existe y tiene tamaño
+ match std::fs::metadata(&self.map_path) {
+ Ok(metadata) => {
+ if metadata.len() > 0 {
+ tracing::info!("Verified saved map file exists with size: {} bytes", metadata.len());
+ } else {
+ tracing::warn!("Map file exists but has zero size - this might cause issues");
+ }
+ },
+ Err(e) => {
+ tracing::error!("Failed to verify saved map file: {}", e);
+ // Intentar un segundo guardado si la verificación falla
+ if let Err(retry_err) = self.save_id_map().await {
+ tracing::error!("Second save attempt also failed: {}", retry_err);
+ return Err(IdMappingError::IoError(std::io::Error::new(
+ std::io::ErrorKind::Other,
+ format!("Failed to verify and retry save: {}", retry_err)
+ )));
+ }
+ tracing::info!("Second save attempt succeeded");
+ }
+ }
+
+ Ok(())
+ },
+ Err(e) => {
+ tracing::error!("Failed to save ID map to {}: {}", self.map_path.display(), e);
+ // Intentar un segundo guardado con retraso en caso de error
+ tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
+ match self.save_id_map().await {
+ Ok(_) => {
+ tracing::info!("Second save attempt succeeded after initial failure");
+ Ok(())
+ },
+ Err(retry_e) => {
+ tracing::error!("Second save attempt also failed: {}", retry_e);
+ Err(IdMappingError::IoError(std::io::Error::new(
+ std::io::ErrorKind::Other,
+ format!("Failed to save ID mappings after retry: {}", retry_e)
+ )))
+ }
+ }
}
- });
-
- Ok(())
+ }
}
}
diff --git a/src/interfaces/api/handlers/file_handler.rs b/src/interfaces/api/handlers/file_handler.rs
index 47ce98d2..119ad3ee 100644
--- a/src/interfaces/api/handlers/file_handler.rs
+++ b/src/interfaces/api/handlers/file_handler.rs
@@ -122,9 +122,33 @@ impl FileHandler {
// Log additional debugging information
tracing::info!("Created file details: folder_id={:?}, size={}, path={}",
file.folder_id, file.size, file.path);
+
+ // VERIFICACIÓN ADICIONAL: Comprobar que el archivo es accesible inmediatamente después de subir
+ let file_id = file.id.clone(); // Clonar para uso en la verificación
+ match service.get_file(&file_id).await {
+ Ok(_) => tracing::info!("Verified file is immediately accessible after upload: {}", file_id),
+ Err(e) => {
+ tracing::warn!("File uploaded but not immediately accessible: {} - {}. This could cause issues in frontend.", file_id, e);
+ // Esperar un momento y comprobar de nuevo
+ tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
+ if let Err(retry_e) = service.get_file(&file_id).await {
+ tracing::error!("File still not accessible after retry: {} - {}", file_id, retry_e);
+ } else {
+ tracing::info!("File became accessible after short delay: {}", file_id);
+ }
+ }
+ }
- // Return success response with file information
- (StatusCode::CREATED, Json(file)).into_response()
+ // Añadir cabecera para evitar caché del navegador en respuestas
+ let response = Response::builder()
+ .status(StatusCode::CREATED)
+ .header("Cache-Control", "no-cache, no-store, must-revalidate")
+ .header("Pragma", "no-cache")
+ .header("Expires", "0")
+ .body(axum::body::Body::from(serde_json::to_string(&file).unwrap()))
+ .unwrap();
+
+ response
},
Err(err) => {
tracing::error!("Error uploading file '{}' through service: {}", filename, err);
@@ -434,8 +458,16 @@ impl FileHandler {
tracing::info!("No files found in folder through service");
}
- // Return the files as JSON response
- (StatusCode::OK, Json(files)).into_response()
+ // Devolver respuesta con cabeceras para evitar caché del navegador
+ let response = Response::builder()
+ .status(StatusCode::OK)
+ .header("Cache-Control", "no-cache, no-store, must-revalidate")
+ .header("Pragma", "no-cache")
+ .header("Expires", "0")
+ .body(axum::body::Body::from(serde_json::to_string(&files).unwrap()))
+ .unwrap();
+
+ response
},
Err(err) => {
tracing::error!("Error listing files through service: {}", err);
diff --git a/static/index.html b/static/index.html
index 8feb45f6..e04cf0a3 100644
--- a/static/index.html
+++ b/static/index.html
@@ -17,17 +17,18 @@
-
-
-
-
+
-
+
+
+
+
+
diff --git a/static/js/app.js b/static/js/app.js
index 9501b3b1..ce028788 100644
--- a/static/js/app.js
+++ b/static/js/app.js
@@ -383,8 +383,21 @@ function setupEventListeners() {
/**
* Load files and folders for the current path
*/
-async function loadFiles() {
+async function loadFiles(options = {}) {
try {
+ console.log("Iniciando loadFiles() - cargando archivos...", options);
+
+ // Flag para forzar el refresco completo ignorando caché
+ const forceRefresh = options.forceRefresh || false;
+
+ // Prevenir múltiples solicitudes de carga simultáneas
+ if (window.isLoadingFiles) {
+ console.log("Ya hay una carga de archivos en progreso, ignorando solicitud");
+ return;
+ }
+
+ window.isLoadingFiles = true;
+
// Always ensure a userHomeFolderId is set
if (!app.userHomeFolderId) {
// If we don't have a home folder ID yet, try to get the user's username
@@ -392,35 +405,51 @@ async function loadFiles() {
const userData = JSON.parse(localStorage.getItem(USER_DATA_KEY) || '{}');
if (userData.username) {
// Find user's home folder
+ console.log("Buscando carpeta de usuario para", userData.username);
await findUserHomeFolder(userData.username);
}
}
+ // Agregar timestamp para evitar caché
+ const timestamp = new Date().getTime();
let url;
+
// ALWAYS use the userHomeFolderId (current folder or home folder) to avoid showing root
if (!app.currentPath || app.currentPath === '') {
// If at root, force user to their home folder
if (app.userHomeFolderId) {
- url = `/api/folders/${app.userHomeFolderId}/contents`;
+ url = `/api/folders/${app.userHomeFolderId}/contents?t=${timestamp}`;
app.currentPath = app.userHomeFolderId;
ui.updateBreadcrumb(app.userHomeFolderName || 'Home');
+ console.log(`Cargando carpeta del usuario: ${app.userHomeFolderName} (${app.userHomeFolderId})`);
} else {
// Emergency fallback - this should rarely happen but prevents errors
- url = '/api/folders';
+ url = `/api/folders?t=${timestamp}`;
console.warn("Emergency fallback to root folder - this should not normally happen");
}
} else {
// Normal case - viewing subfolder contents
- url = `/api/folders/${app.currentPath}/contents`;
+ url = `/api/folders/${app.currentPath}/contents?t=${timestamp}`;
+ console.log(`Cargando contenido de subcarpeta: ${app.currentPath}`);
}
const token = localStorage.getItem('oxicloud_token');
const requestOptions = {
headers: {
- 'Authorization': `Bearer ${token}`
- }
+ 'Authorization': `Bearer ${token}`,
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
+ 'Pragma': 'no-cache'
+ },
+ cache: 'no-store' // Instruir al navegador a no usar caché
};
+ // Si se especifica forceRefresh, agregar un parámetro adicional para evitar caché
+ if (forceRefresh) {
+ url += `&force_refresh=true`;
+ requestOptions.headers['X-Force-Refresh'] = 'true';
+ console.log('Forzando refresco completo ignorando caché');
+ }
+
console.log(`Loading files from ${url}`);
const response = await fetch(url, requestOptions);
@@ -490,10 +519,12 @@ async function loadFiles() {
});
// Also load files in this folder
- let filesUrl = '/api/files';
+ const cacheTimestamp = new Date().getTime();
+ let filesUrl = `/api/files?t=${cacheTimestamp}`; // Agregar timestamp para evitar problemas de caché
if (app.currentPath) {
- filesUrl += `?folder_id=${app.currentPath}`;
+ filesUrl += `&folder_id=${app.currentPath}`;
}
+ console.log(`Cargando archivos desde: ${filesUrl}`);
try {
console.log(`Fetching files from: ${filesUrl}`);
@@ -532,6 +563,9 @@ async function loadFiles() {
} catch (error) {
console.error('Error loading folders:', error);
ui.showNotification('Error', 'Could not load files and folders');
+ } finally {
+ // Marcar que ya no estamos cargando archivos para permitir solicitudes futuras
+ window.isLoadingFiles = false;
}
}
diff --git a/static/js/fileOperations.js b/static/js/fileOperations.js
index ade9a2e1..0b040150 100644
--- a/static/js/fileOperations.js
+++ b/static/js/fileOperations.js
@@ -39,7 +39,13 @@ const fileOps = {
const response = await fetch('/api/files/upload', {
method: 'POST',
- body: formData
+ body: formData,
+ // Añadir cache: 'no-store' para evitar problemas de caché durante la subida
+ cache: 'no-store',
+ headers: {
+ // Agregar este encabezado para forzar recargas frescas
+ 'Cache-Control': 'no-cache, no-store, must-revalidate'
+ }
});
console.log('Respuesta del servidor:', {
@@ -55,11 +61,57 @@ const fileOps = {
if (response.ok) {
const responseData = await response.json();
console.log(`Successfully uploaded ${file.name}`, responseData);
+
+ // Agregar el archivo a la vista inmediatamente para mostrar retroalimentación instantánea
+ // Esto permite que el usuario vea el archivo aunque el refresco posterior falle
+ if (window.ui && window.ui.addFileToView) {
+ console.log('Añadiendo archivo subido directamente a la vista:', responseData);
+ window.ui.addFileToView(responseData);
+ window.ui.updateFileIcons();
+ }
if (i === totalFiles - 1) {
// Last file uploaded
console.log('Recargando lista de archivos después de subida');
- window.loadFiles();
+
+ try {
+ // Esperar 1500ms para asegurar que los mapeos de ID se guarden
+ // Tiempo aumentado significativamente para permitir al backend completar persistencia
+ await new Promise(resolve => setTimeout(resolve, 1500));
+
+ // Forzar recarga de archivos con parámetro de bypass de caché
+ const timestamp = new Date().getTime();
+ const filesUrl = `/api/files?t=${timestamp}&folder_id=${window.app.currentPath || ''}`;
+
+ console.log(`Recargando lista de archivos desde: ${filesUrl}`);
+ const filesResponse = await fetch(filesUrl, {
+ cache: 'no-store',
+ headers: {
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
+ 'Pragma': 'no-cache'
+ }
+ });
+
+ if (filesResponse.ok) {
+ const files = await filesResponse.json();
+ console.log(`Recarga de archivos completada, obtenidos ${files.length} archivos`);
+ }
+
+ // Esperar un momento más antes de la recarga final
+ await new Promise(resolve => setTimeout(resolve, 500));
+
+ // Hacer una única recarga final forzando refresco completo
+ await window.loadFiles({forceRefresh: true});
+
+ // Asegurarse de que no haya recargas adicionales o duplicados
+ } catch (reloadError) {
+ console.error("Error durante recarga de archivos:", reloadError);
+ // Intentar recargar lista de nuevo en caso de error
+ await window.loadFiles({forceRefresh: true});
+ // Segundo intento con retraso y forzando refresco
+ setTimeout(() => window.loadFiles({forceRefresh: true}), 500);
+ }
+
setTimeout(() => {
document.getElementById('dropzone').style.display = 'none';
uploadProgressDiv.style.display = 'none';
diff --git a/static/js/inlineViewer.js b/static/js/inlineViewer.js
index f44929e3..a5c06482 100644
--- a/static/js/inlineViewer.js
+++ b/static/js/inlineViewer.js
@@ -15,6 +15,13 @@ class InlineViewer {
return;
}
+ // Verify document.body exists
+ if (!document.body) {
+ console.warn('Document body not available yet for inline viewer, will retry later');
+ setTimeout(() => this.setupViewer(), 200);
+ return;
+ }
+
// Create modal container
const modal = document.createElement('div');
modal.id = 'inline-viewer-modal';
@@ -325,5 +332,21 @@ class InlineViewer {
}
}
-// Initialize viewer
-window.inlineViewer = new InlineViewer();
\ No newline at end of file
+// Initialize viewer when document is ready
+document.addEventListener('DOMContentLoaded', () => {
+ // Check if it's already initialized
+ if (!window.inlineViewer) {
+ console.log('Initializing inline viewer on DOMContentLoaded');
+ window.inlineViewer = new InlineViewer();
+ }
+});
+
+// Fallback initialization for cases where DOMContentLoaded already fired
+if (document.readyState === 'complete' || document.readyState === 'interactive') {
+ if (!window.inlineViewer) {
+ console.log('Fallback initialization for inline viewer');
+ setTimeout(() => {
+ window.inlineViewer = new InlineViewer();
+ }, 100);
+ }
+}
\ No newline at end of file
diff --git a/static/js/ui.js b/static/js/ui.js
index 0a00a609..786f9451 100644
--- a/static/js/ui.js
+++ b/static/js/ui.js
@@ -518,6 +518,15 @@ const ui = {
* @param {Object} folder - Folder object
*/
addFolderToView(folder) {
+ // Verificar si la carpeta ya existe en la vista para evitar duplicados
+ if (document.querySelector(`.file-card[data-folder-id="${folder.id}"]`) ||
+ document.querySelector(`.file-item[data-folder-id="${folder.id}"]`)) {
+ console.log(`Carpeta ${folder.name} (${folder.id}) ya existe en la vista, no duplicando`);
+ return;
+ }
+
+ console.log(`Añadiendo carpeta a la vista: ${folder.name} (${folder.id})`);
+
// Grid view element
const folderGridElement = document.createElement('div');
folderGridElement.className = 'file-card';
@@ -709,6 +718,15 @@ const ui = {
* @param {Object} file - File object
*/
addFileToView(file) {
+ // Verificar si el archivo ya existe en la vista para evitar duplicados
+ if (document.querySelector(`.file-card[data-file-id="${file.id}"]`) ||
+ document.querySelector(`.file-item[data-file-id="${file.id}"]`)) {
+ console.log(`Archivo ${file.name} (${file.id}) ya existe en la vista, no duplicando`);
+ return;
+ }
+
+ console.log(`Añadiendo archivo a la vista: ${file.name} (${file.id})`);
+
// Determine icon and type
let iconClass = 'fas fa-file';
let iconSpecialClass = '';