Allow renaming and moving folders in web file manager (PR #1617 by alkk)
This commit is contained in:
@@ -46,13 +46,42 @@ unsigned long wsLastCompleteAt = 0;
|
|||||||
|
|
||||||
// Helper function to clear epub cache after upload
|
// Helper function to clear epub cache after upload
|
||||||
void clearEpubCacheIfNeeded(const String& filePath) {
|
void clearEpubCacheIfNeeded(const String& filePath) {
|
||||||
// Only clear cache for .epub files
|
|
||||||
if (FsHelpers::hasEpubExtension(filePath)) {
|
if (FsHelpers::hasEpubExtension(filePath)) {
|
||||||
Epub(filePath.c_str(), "/.crosspoint").clearCache();
|
Epub(filePath.c_str(), "/.crosspoint").clearCache();
|
||||||
LOG_DBG("WEB", "Cleared epub cache for: %s", filePath.c_str());
|
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) {
|
String normalizeWebPath(const String& inputPath) {
|
||||||
if (inputPath.isEmpty() || inputPath == "/") {
|
if (inputPath.isEmpty() || inputPath == "/") {
|
||||||
return "/";
|
return "/";
|
||||||
@@ -849,11 +878,7 @@ void CrossPointWebServer::handleRename() const {
|
|||||||
server->send(500, "text/plain", "Failed to open file");
|
server->send(500, "text/plain", "Failed to open file");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (file.isDirectory()) {
|
const bool isDir = file.isDirectory();
|
||||||
file.close();
|
|
||||||
server->send(400, "text/plain", "Only files can be renamed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String parentPath = itemPath.substring(0, itemPath.lastIndexOf('/'));
|
String parentPath = itemPath.substring(0, itemPath.lastIndexOf('/'));
|
||||||
if (parentPath.isEmpty()) {
|
if (parentPath.isEmpty()) {
|
||||||
@@ -871,16 +896,20 @@ void CrossPointWebServer::handleRename() const {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
clearEpubCacheIfNeeded(itemPath);
|
if (isDir) {
|
||||||
|
clearEpubCachesInDirectory(itemPath);
|
||||||
|
} else {
|
||||||
|
clearEpubCacheIfNeeded(itemPath);
|
||||||
|
}
|
||||||
const bool success = file.rename(newPath.c_str());
|
const bool success = file.rename(newPath.c_str());
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
if (success) {
|
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");
|
server->send(200, "text/plain", "Renamed successfully");
|
||||||
} else {
|
} else {
|
||||||
LOG_ERR("WEB", "Failed to rename file: %s -> %s", itemPath.c_str(), newPath.c_str());
|
LOG_ERR("WEB", "Failed to rename: %s -> %s", itemPath.c_str(), newPath.c_str());
|
||||||
server->send(500, "text/plain", "Failed to rename file");
|
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");
|
server->send(500, "text/plain", "Failed to open file");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (file.isDirectory()) {
|
const bool isDir = file.isDirectory();
|
||||||
file.close();
|
|
||||||
server->send(400, "text/plain", "Only files can be moved");
|
if (isDir) {
|
||||||
return;
|
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())) {
|
if (!Storage.exists(destPath.c_str())) {
|
||||||
@@ -964,16 +1001,20 @@ void CrossPointWebServer::handleMove() const {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
clearEpubCacheIfNeeded(itemPath);
|
if (isDir) {
|
||||||
|
clearEpubCachesInDirectory(itemPath);
|
||||||
|
} else {
|
||||||
|
clearEpubCacheIfNeeded(itemPath);
|
||||||
|
}
|
||||||
const bool success = file.rename(newPath.c_str());
|
const bool success = file.rename(newPath.c_str());
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
if (success) {
|
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");
|
server->send(200, "text/plain", "Moved successfully");
|
||||||
} else {
|
} else {
|
||||||
LOG_ERR("WEB", "Failed to move file: %s -> %s", itemPath.c_str(), newPath.c_str());
|
LOG_ERR("WEB", "Failed to move: %s -> %s", itemPath.c_str(), newPath.c_str());
|
||||||
server->send(500, "text/plain", "Failed to move file");
|
server->send(500, "text/plain", "Failed to move");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2197,7 +2197,11 @@
|
|||||||
fileTableContent += `<td><span class="file-icon">📁</span><a href="/files?path=${encodeURIComponent(folderPath)}" class="folder-link">${escapeHtml(file.name)}</a><span class="folder-badge">FOLDER</span></td>`;
|
fileTableContent += `<td><span class="file-icon">📁</span><a href="/files?path=${encodeURIComponent(folderPath)}" class="folder-link">${escapeHtml(file.name)}</a><span class="folder-badge">FOLDER</span></td>`;
|
||||||
fileTableContent += '<td>Folder</td>';
|
fileTableContent += '<td>Folder</td>';
|
||||||
fileTableContent += '<td>-</td>';
|
fileTableContent += '<td>-</td>';
|
||||||
fileTableContent += `<td class="actions-col"><div class="action-icon-group"><button class="delete-btn" onclick="openDeleteModal('${file.name.replaceAll("'", "\\'")}', '${folderPath.replaceAll("'", "\\'")}', true)" title="Delete folder">🗑️</button></div></td>`;
|
fileTableContent += `<td class="actions-col"><div class="action-icon-group">`;
|
||||||
|
fileTableContent += `<button class="move-btn" onclick="openMoveModal('${file.name.replaceAll("'", "\\'")}', '${folderPath.replaceAll("'", "\\'")}', true)" title="Move folder">📂</button>`;
|
||||||
|
fileTableContent += `<button class="rename-btn" onclick="openRenameModal('${file.name.replaceAll("'", "\\'")}', '${folderPath.replaceAll("'", "\\'")}', true)" title="Rename folder">✏️</button>`;
|
||||||
|
fileTableContent += `<button class="delete-btn" onclick="openDeleteModal('${file.name.replaceAll("'", "\\'")}', '${folderPath.replaceAll("'", "\\'")}', true)" title="Delete folder">🗑️</button>`;
|
||||||
|
fileTableContent += `</div></td>`;
|
||||||
fileTableContent += '</tr>';
|
fileTableContent += '</tr>';
|
||||||
} else {
|
} else {
|
||||||
let filePath = currentPath;
|
let filePath = currentPath;
|
||||||
@@ -5367,8 +5371,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Rename functions
|
// Rename functions
|
||||||
function openRenameModal(name, path) {
|
function openRenameModal(name, path, isFolder = false) {
|
||||||
document.getElementById('renameItemName').textContent = '📄 ' + name;
|
const icon = isFolder ? '📁' : '📄';
|
||||||
|
document.getElementById('renameItemName').textContent = icon + ' ' + name;
|
||||||
document.getElementById('renameItemPath').value = path;
|
document.getElementById('renameItemPath').value = path;
|
||||||
document.getElementById('renameNewName').value = name;
|
document.getElementById('renameNewName').value = name;
|
||||||
document.getElementById('renameModal').classList.add('open');
|
document.getElementById('renameModal').classList.add('open');
|
||||||
@@ -5482,8 +5487,9 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function openMoveModal(name, path) {
|
function openMoveModal(name, path, isFolder = false) {
|
||||||
document.getElementById('moveItemName').textContent = '📄 ' + name;
|
const icon = isFolder ? '📁' : '📄';
|
||||||
|
document.getElementById('moveItemName').textContent = icon + ' ' + name;
|
||||||
document.getElementById('moveItemPath').value = path;
|
document.getElementById('moveItemPath').value = path;
|
||||||
document.getElementById('moveDestPath').value = currentPath === '/' ? '/' : currentPath;
|
document.getElementById('moveDestPath').value = currentPath === '/' ? '/' : currentPath;
|
||||||
document.getElementById('moveModal').classList.add('open');
|
document.getElementById('moveModal').classList.add('open');
|
||||||
|
|||||||
Reference in New Issue
Block a user