From 0b0e04b9fa2b9f90c6c235451bbd514d430e4c53 Mon Sep 17 00:00:00 2001 From: jpirnay Date: Wed, 8 Apr 2026 19:41:52 +0200 Subject: [PATCH] Allow renaming and moving folders in web file manager (PR #1617 by alkk) --- src/network/CrossPointWebServer.cpp | 77 ++++++++++++++++++++++------- src/network/html/FilesPage.html | 16 ++++-- 2 files changed, 70 insertions(+), 23 deletions(-) diff --git a/src/network/CrossPointWebServer.cpp b/src/network/CrossPointWebServer.cpp index da5fad30..04822a38 100644 --- a/src/network/CrossPointWebServer.cpp +++ b/src/network/CrossPointWebServer.cpp @@ -46,13 +46,42 @@ unsigned long wsLastCompleteAt = 0; // Helper function to clear epub cache after upload void clearEpubCacheIfNeeded(const String& filePath) { - // Only clear cache for .epub files if (FsHelpers::hasEpubExtension(filePath)) { Epub(filePath.c_str(), "/.crosspoint").clearCache(); LOG_DBG("WEB", "Cleared epub cache for: %s", filePath.c_str()); } } +// Recursively clear epub caches for all EPUBs inside a directory +void clearEpubCachesInDirectory(const String& dirPath) { + esp_task_wdt_reset(); + yield(); + FsFile dir = Storage.open(dirPath.c_str()); + if (!dir || !dir.isDirectory()) { + if (dir) dir.close(); + return; + } + char name[500]; + FsFile entry = dir.openNextFile(); + while (entry) { + esp_task_wdt_reset(); + yield(); + entry.getName(name, sizeof(name)); + String childPath = dirPath; + if (!childPath.endsWith("/")) childPath += "/"; + childPath += name; + if (entry.isDirectory()) { + entry.close(); + clearEpubCachesInDirectory(childPath); + } else { + entry.close(); + clearEpubCacheIfNeeded(childPath); + } + entry = dir.openNextFile(); + } + dir.close(); +} + String normalizeWebPath(const String& inputPath) { if (inputPath.isEmpty() || inputPath == "/") { return "/"; @@ -849,11 +878,7 @@ void CrossPointWebServer::handleRename() const { server->send(500, "text/plain", "Failed to open file"); return; } - if (file.isDirectory()) { - file.close(); - server->send(400, "text/plain", "Only files can be renamed"); - return; - } + const bool isDir = file.isDirectory(); String parentPath = itemPath.substring(0, itemPath.lastIndexOf('/')); if (parentPath.isEmpty()) { @@ -871,16 +896,20 @@ void CrossPointWebServer::handleRename() const { return; } - clearEpubCacheIfNeeded(itemPath); + if (isDir) { + clearEpubCachesInDirectory(itemPath); + } else { + clearEpubCacheIfNeeded(itemPath); + } const bool success = file.rename(newPath.c_str()); file.close(); if (success) { - LOG_DBG("WEB", "Renamed file: %s -> %s", itemPath.c_str(), newPath.c_str()); + LOG_DBG("WEB", "Renamed: %s -> %s", itemPath.c_str(), newPath.c_str()); server->send(200, "text/plain", "Renamed successfully"); } else { - LOG_ERR("WEB", "Failed to rename file: %s -> %s", itemPath.c_str(), newPath.c_str()); - server->send(500, "text/plain", "Failed to rename file"); + LOG_ERR("WEB", "Failed to rename: %s -> %s", itemPath.c_str(), newPath.c_str()); + server->send(500, "text/plain", "Failed to rename"); } } @@ -925,10 +954,18 @@ void CrossPointWebServer::handleMove() const { server->send(500, "text/plain", "Failed to open file"); return; } - if (file.isDirectory()) { - file.close(); - server->send(400, "text/plain", "Only files can be moved"); - return; + const bool isDir = file.isDirectory(); + + if (isDir) { + String destWithSlash = destPath; + if (!destWithSlash.endsWith("/")) destWithSlash += "/"; + String itemWithSlash = itemPath; + if (!itemWithSlash.endsWith("/")) itemWithSlash += "/"; + if (destPath == itemPath || destWithSlash.startsWith(itemWithSlash)) { + file.close(); + server->send(400, "text/plain", "Cannot move folder into itself"); + return; + } } if (!Storage.exists(destPath.c_str())) { @@ -964,16 +1001,20 @@ void CrossPointWebServer::handleMove() const { return; } - clearEpubCacheIfNeeded(itemPath); + if (isDir) { + clearEpubCachesInDirectory(itemPath); + } else { + clearEpubCacheIfNeeded(itemPath); + } const bool success = file.rename(newPath.c_str()); file.close(); if (success) { - LOG_DBG("WEB", "Moved file: %s -> %s", itemPath.c_str(), newPath.c_str()); + LOG_DBG("WEB", "Moved: %s -> %s", itemPath.c_str(), newPath.c_str()); server->send(200, "text/plain", "Moved successfully"); } else { - LOG_ERR("WEB", "Failed to move file: %s -> %s", itemPath.c_str(), newPath.c_str()); - server->send(500, "text/plain", "Failed to move file"); + LOG_ERR("WEB", "Failed to move: %s -> %s", itemPath.c_str(), newPath.c_str()); + server->send(500, "text/plain", "Failed to move"); } } diff --git a/src/network/html/FilesPage.html b/src/network/html/FilesPage.html index d574063d..54830b0f 100644 --- a/src/network/html/FilesPage.html +++ b/src/network/html/FilesPage.html @@ -2197,7 +2197,11 @@ fileTableContent += `📁${escapeHtml(file.name)}FOLDER`; fileTableContent += 'Folder'; fileTableContent += '-'; - fileTableContent += `
`; + fileTableContent += `
`; + fileTableContent += ``; + fileTableContent += ``; + fileTableContent += ``; + fileTableContent += `
`; fileTableContent += ''; } else { let filePath = currentPath; @@ -5367,8 +5371,9 @@ } // Rename functions - function openRenameModal(name, path) { - document.getElementById('renameItemName').textContent = '📄 ' + name; + function openRenameModal(name, path, isFolder = false) { + const icon = isFolder ? '📁' : '📄'; + document.getElementById('renameItemName').textContent = icon + ' ' + name; document.getElementById('renameItemPath').value = path; document.getElementById('renameNewName').value = name; document.getElementById('renameModal').classList.add('open'); @@ -5482,8 +5487,9 @@ }); } - function openMoveModal(name, path) { - document.getElementById('moveItemName').textContent = '📄 ' + name; + function openMoveModal(name, path, isFolder = false) { + const icon = isFolder ? '📁' : '📄'; + document.getElementById('moveItemName').textContent = icon + ' ' + name; document.getElementById('moveItemPath').value = path; document.getElementById('moveDestPath').value = currentPath === '/' ? '/' : currentPath; document.getElementById('moveModal').classList.add('open');