From 448ce55bb4b480324e79bd4ea8f330ed9b048895 Mon Sep 17 00:00:00 2001 From: cottongin Date: Fri, 30 Jan 2026 22:22:22 -0500 Subject: [PATCH] Add individual book cache clearing with preserve progress option - Add "Clear Cache" option to book action menu in MyLibrary (both Recent and Files tabs) - Prompt user to preserve reading progress when clearing cache - Always preserve bookmarks when clearing individual book cache - Add preserve progress option to system-level Clear Cache in Settings - Implement BookManager::clearBookCache() for selective cache clearing --- src/BookManager.cpp | 79 +++++++++ src/BookManager.h | 8 + src/activities/home/MyLibraryActivity.cpp | 162 +++++++++++++++--- src/activities/home/MyLibraryActivity.h | 9 +- .../settings/ClearCacheActivity.cpp | 137 ++++++++++++--- src/activities/settings/ClearCacheActivity.h | 2 + 6 files changed, 345 insertions(+), 52 deletions(-) diff --git a/src/BookManager.cpp b/src/BookManager.cpp index 83fe271..3b20a08 100644 --- a/src/BookManager.cpp +++ b/src/BookManager.cpp @@ -303,6 +303,85 @@ bool BookManager::deleteBook(const std::string& bookPath, bool isArchived) { return true; } +bool BookManager::clearBookCache(const std::string& bookPath, bool preserveProgress) { + Serial.printf("[%lu] [%s] Clearing cache for: %s (preserveProgress=%d)\n", millis(), LOG_TAG, bookPath.c_str(), + preserveProgress); + + const std::string cacheDir = getCacheDir(bookPath); + if (cacheDir.empty()) { + Serial.printf("[%lu] [%s] No cache directory for unsupported format\n", millis(), LOG_TAG); + return true; // Nothing to clear, not an error + } + + if (!SdMan.exists(cacheDir.c_str())) { + Serial.printf("[%lu] [%s] Cache directory doesn't exist: %s\n", millis(), LOG_TAG, cacheDir.c_str()); + return true; // Nothing to clear, not an error + } + + FsFile dir = SdMan.open(cacheDir.c_str()); + if (!dir || !dir.isDirectory()) { + Serial.printf("[%lu] [%s] Failed to open cache directory\n", millis(), LOG_TAG); + if (dir) dir.close(); + return false; + } + + // Files to preserve (always keep bookmarks, optionally keep progress) + const auto shouldPreserve = [preserveProgress](const char* name) { + // Always preserve bookmarks + if (strcmp(name, "bookmarks.bin") == 0) return true; + // Optionally preserve progress + if (preserveProgress && strcmp(name, "progress.bin") == 0) return true; + return false; + }; + + int deletedCount = 0; + int failedCount = 0; + char name[128]; + + // First pass: delete files (not directories) + for (FsFile entry = dir.openNextFile(); entry; entry = dir.openNextFile()) { + entry.getName(name, sizeof(name)); + const bool isDir = entry.isDirectory(); + entry.close(); + + if (!isDir && !shouldPreserve(name)) { + std::string fullPath = cacheDir + "/" + name; + if (SdMan.remove(fullPath.c_str())) { + deletedCount++; + } else { + Serial.printf("[%lu] [%s] Failed to delete: %s\n", millis(), LOG_TAG, fullPath.c_str()); + failedCount++; + } + } + } + dir.close(); + + // Second pass: delete subdirectories (like "sections/") + dir = SdMan.open(cacheDir.c_str()); + if (dir && dir.isDirectory()) { + for (FsFile entry = dir.openNextFile(); entry; entry = dir.openNextFile()) { + entry.getName(name, sizeof(name)); + const bool isDir = entry.isDirectory(); + entry.close(); + + if (isDir) { + std::string fullPath = cacheDir + "/" + name; + if (SdMan.removeDir(fullPath.c_str())) { + deletedCount++; + Serial.printf("[%lu] [%s] Deleted subdirectory: %s\n", millis(), LOG_TAG, fullPath.c_str()); + } else { + Serial.printf("[%lu] [%s] Failed to delete subdirectory: %s\n", millis(), LOG_TAG, fullPath.c_str()); + failedCount++; + } + } + } + dir.close(); + } + + Serial.printf("[%lu] [%s] Cache cleared: %d items deleted, %d failed\n", millis(), LOG_TAG, deletedCount, failedCount); + return failedCount == 0; +} + std::vector BookManager::listArchivedBooks() { std::vector archivedBooks; diff --git a/src/BookManager.h b/src/BookManager.h index 4588667..fdca7f2 100644 --- a/src/BookManager.h +++ b/src/BookManager.h @@ -57,6 +57,14 @@ class BookManager { */ static std::string getCacheDir(const std::string& bookPath); + /** + * Clear cache for a single book, optionally preserving reading progress + * @param bookPath Full path to the book file + * @param preserveProgress If true, keeps progress.bin and bookmarks.bin + * @return true if successful (or if no cache exists) + */ + static bool clearBookCache(const std::string& bookPath, bool preserveProgress); + private: // Extract filename from a full path static std::string getFilename(const std::string& path); diff --git a/src/activities/home/MyLibraryActivity.cpp b/src/activities/home/MyLibraryActivity.cpp index dc35823..8d32fa1 100644 --- a/src/activities/home/MyLibraryActivity.cpp +++ b/src/activities/home/MyLibraryActivity.cpp @@ -503,22 +503,29 @@ void MyLibraryActivity::executeAction() { } else if (selectedAction == ActionType::RemoveFromRecents) { // Just remove from recents list, don't touch the file success = RECENT_BOOKS.removeBook(actionTargetPath); + } else if (selectedAction == ActionType::ClearCache) { + // Clear cache for this book, optionally preserving progress + success = BookManager::clearBookCache(actionTargetPath, clearCachePreserveProgress); + // Also clear thumbnail existence cache since thumbnails may have been deleted + clearThumbExistsCache(); } // Note: ClearAllRecents is handled directly in loop() via ClearAllRecentsConfirming state if (success) { // Reload data loadRecentBooks(); - if (selectedAction != ActionType::RemoveFromRecents) { - loadFiles(); // Only reload files for Archive/Delete + if (selectedAction != ActionType::RemoveFromRecents && selectedAction != ActionType::ClearCache) { + loadFiles(); // Only reload files for Archive/Delete (not needed for cache clear) } - // Adjust selector if needed - const int itemCount = getCurrentItemCount(); - if (selectorIndex >= itemCount && itemCount > 0) { - selectorIndex = itemCount - 1; - } else if (itemCount == 0) { - selectorIndex = 0; + // Adjust selector if needed (not needed for ClearCache since item count doesn't change) + if (selectedAction != ActionType::ClearCache) { + const int itemCount = getCurrentItemCount(); + if (selectorIndex >= itemCount && itemCount > 0) { + selectorIndex = itemCount - 1; + } else if (itemCount == 0) { + selectorIndex = 0; + } } } @@ -577,8 +584,8 @@ void MyLibraryActivity::executeListAction() { void MyLibraryActivity::loop() { // Handle action menu state if (uiState == UIState::ActionMenu) { - // Menu has 4 options in Recent tab, 2 options in Files tab - const int maxMenuSelection = (currentTab == Tab::Recent) ? 3 : 1; + // Menu has 5 options in Recent tab, 3 options in Files tab + const int maxMenuSelection = (currentTab == Tab::Recent) ? 4 : 2; if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { uiState = UIState::Normal; @@ -608,7 +615,7 @@ void MyLibraryActivity::loop() { // Map menu selection to action type if (currentTab == Tab::Recent) { - // Recent tab: Archive(0), Delete(1), Remove from Recents(2), Clear All Recents(3) + // Recent tab: Archive(0), Delete(1), Clear Cache(2), Remove from Recents(3), Clear All Recents(4) switch (menuSelection) { case 0: selectedAction = ActionType::Archive; @@ -617,20 +624,37 @@ void MyLibraryActivity::loop() { selectedAction = ActionType::Delete; break; case 2: - selectedAction = ActionType::RemoveFromRecents; + selectedAction = ActionType::ClearCache; break; case 3: + selectedAction = ActionType::RemoveFromRecents; + break; + case 4: selectedAction = ActionType::ClearAllRecents; break; } } else { - // Files tab: Archive(0), Delete(1) - selectedAction = (menuSelection == 0) ? ActionType::Archive : ActionType::Delete; + // Files tab: Archive(0), Delete(1), Clear Cache(2) + switch (menuSelection) { + case 0: + selectedAction = ActionType::Archive; + break; + case 1: + selectedAction = ActionType::Delete; + break; + case 2: + selectedAction = ActionType::ClearCache; + break; + } } // Clear All Recents needs its own confirmation dialog if (selectedAction == ActionType::ClearAllRecents) { uiState = UIState::ClearAllRecentsConfirming; + } else if (selectedAction == ActionType::ClearCache) { + // Clear Cache shows options dialog first + clearCachePreserveProgress = true; // Default to preserving progress + uiState = UIState::ClearCacheOptionsConfirming; } else { uiState = UIState::Confirming; } @@ -735,6 +759,30 @@ void MyLibraryActivity::loop() { return; } + // Handle clear cache options confirmation state + if (uiState == UIState::ClearCacheOptionsConfirming) { + if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { + uiState = UIState::ActionMenu; + updateRequired = true; + return; + } + + // Up/Down toggle between Yes/No for preserve progress + if (mappedInput.wasReleased(MappedInputManager::Button::Up) || + mappedInput.wasReleased(MappedInputManager::Button::Down)) { + clearCachePreserveProgress = !clearCachePreserveProgress; + updateRequired = true; + return; + } + + if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { + executeAction(); + return; + } + + return; + } + // Normal state handling const int itemCount = getCurrentItemCount(); const int pageItems = getPageItems(); @@ -1303,6 +1351,12 @@ void MyLibraryActivity::render() const { return; } + if (uiState == UIState::ClearCacheOptionsConfirming) { + renderClearCacheOptionsConfirmation(); + renderer.displayBuffer(); + return; + } + // Calculate bezel-adjusted margins const int bezelTop = renderer.getBezelOffsetTop(); const int bezelBottom = renderer.getBezelOffsetBottom(); @@ -1661,40 +1715,46 @@ void MyLibraryActivity::renderActionMenu() const { renderer.truncatedText(UI_10_FONT_ID, actionTargetName.c_str(), pageWidth - 40 - bezelLeft - bezelRight); renderer.drawCenteredText(UI_10_FONT_ID, filenameY, truncatedName.c_str()); - // Menu options - 4 for Recent tab, 2 for Files tab + // Menu options - 5 for Recent tab, 3 for Files tab const bool isRecentTab = (currentTab == Tab::Recent); - const int menuItemCount = isRecentTab ? 4 : 2; + const int menuItemCount = isRecentTab ? 5 : 3; constexpr int menuLineHeight = 35; constexpr int menuItemWidth = 160; const int menuX = (pageWidth - menuItemWidth) / 2; const int menuStartY = pageHeight / 2 - (menuItemCount * menuLineHeight) / 2; - // Archive option + // Archive option (index 0) if (menuSelection == 0) { renderer.fillRect(menuX - 10, menuStartY - 5, menuItemWidth + 20, menuLineHeight); } renderer.drawCenteredText(UI_10_FONT_ID, menuStartY, "Archive", menuSelection != 0); - // Delete option + // Delete option (index 1) if (menuSelection == 1) { renderer.fillRect(menuX - 10, menuStartY + menuLineHeight - 5, menuItemWidth + 20, menuLineHeight); } renderer.drawCenteredText(UI_10_FONT_ID, menuStartY + menuLineHeight, "Delete", menuSelection != 1); + // Clear Cache option (index 2) - available in both tabs + if (menuSelection == 2) { + renderer.fillRect(menuX - 10, menuStartY + menuLineHeight * 2 - 5, menuItemWidth + 20, menuLineHeight); + } + renderer.drawCenteredText(UI_10_FONT_ID, menuStartY + menuLineHeight * 2, "Clear Cache", menuSelection != 2); + // Recent tab only: Remove from Recents and Clear All Recents if (isRecentTab) { - // Remove from Recents option - if (menuSelection == 2) { - renderer.fillRect(menuX - 10, menuStartY + menuLineHeight * 2 - 5, menuItemWidth + 20, menuLineHeight); - } - renderer.drawCenteredText(UI_10_FONT_ID, menuStartY + menuLineHeight * 2, "Remove from Recents", - menuSelection != 2); - - // Clear All Recents option + // Remove from Recents option (index 3) if (menuSelection == 3) { renderer.fillRect(menuX - 10, menuStartY + menuLineHeight * 3 - 5, menuItemWidth + 20, menuLineHeight); } - renderer.drawCenteredText(UI_10_FONT_ID, menuStartY + menuLineHeight * 3, "Clear All Recents", menuSelection != 3); + renderer.drawCenteredText(UI_10_FONT_ID, menuStartY + menuLineHeight * 3, "Remove from Recents", + menuSelection != 3); + + // Clear All Recents option (index 4) + if (menuSelection == 4) { + renderer.fillRect(menuX - 10, menuStartY + menuLineHeight * 4 - 5, menuItemWidth + 20, menuLineHeight); + } + renderer.drawCenteredText(UI_10_FONT_ID, menuStartY + menuLineHeight * 4, "Clear All Recents", menuSelection != 4); } // Draw side button hints (up/down navigation) @@ -1828,6 +1888,54 @@ void MyLibraryActivity::renderClearAllRecentsConfirmation() const { renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); } +void MyLibraryActivity::renderClearCacheOptionsConfirmation() const { + const auto pageWidth = renderer.getScreenWidth(); + const auto pageHeight = renderer.getScreenHeight(); + + // Bezel compensation + const int bezelTop = renderer.getBezelOffsetTop(); + + // Title + renderer.drawCenteredText(UI_12_FONT_ID, 20 + bezelTop, "Clear Book Cache", true, EpdFontFamily::BOLD); + + // Show filename + const int filenameY = 60 + bezelTop; + const int bezelLeft = renderer.getBezelOffsetLeft(); + const int bezelRight = renderer.getBezelOffsetRight(); + auto truncatedName = + renderer.truncatedText(UI_10_FONT_ID, actionTargetName.c_str(), pageWidth - 40 - bezelLeft - bezelRight); + renderer.drawCenteredText(UI_10_FONT_ID, filenameY, truncatedName.c_str()); + + // Question text + const int questionY = pageHeight / 2 - 50; + renderer.drawCenteredText(UI_10_FONT_ID, questionY, "Preserve reading progress?"); + + // Yes/No options + constexpr int optionLineHeight = 35; + constexpr int optionWidth = 100; + const int optionX = (pageWidth - optionWidth) / 2; + const int optionStartY = questionY + 40; + + // Yes option + if (clearCachePreserveProgress) { + renderer.fillRect(optionX - 10, optionStartY - 5, optionWidth + 20, optionLineHeight); + } + renderer.drawCenteredText(UI_10_FONT_ID, optionStartY, "Yes", !clearCachePreserveProgress); + + // No option + if (!clearCachePreserveProgress) { + renderer.fillRect(optionX - 10, optionStartY + optionLineHeight - 5, optionWidth + 20, optionLineHeight); + } + renderer.drawCenteredText(UI_10_FONT_ID, optionStartY + optionLineHeight, "No", clearCachePreserveProgress); + + // Draw side button hints (up/down navigation) + renderer.drawSideButtonHints(UI_10_FONT_ID, ">", "<"); + + // Draw bottom button hints + const auto labels = mappedInput.mapLabels("« Cancel", "Clear", "", ""); + renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); +} + void MyLibraryActivity::renderBookmarksTab() const { const auto pageWidth = renderer.getScreenWidth(); const int pageItems = getPageItems(); diff --git a/src/activities/home/MyLibraryActivity.h b/src/activities/home/MyLibraryActivity.h index bf15cc2..b09eacb 100644 --- a/src/activities/home/MyLibraryActivity.h +++ b/src/activities/home/MyLibraryActivity.h @@ -44,9 +44,10 @@ class MyLibraryActivity final : public Activity { Confirming, ListActionMenu, ListConfirmingDelete, - ClearAllRecentsConfirming + ClearAllRecentsConfirming, + ClearCacheOptionsConfirming }; - enum class ActionType { Archive, Delete, RemoveFromRecents, ClearAllRecents }; + enum class ActionType { Archive, Delete, RemoveFromRecents, ClearCache, ClearAllRecents }; private: TaskHandle_t displayTaskHandle = nullptr; @@ -64,6 +65,7 @@ class MyLibraryActivity final : public Activity { std::string actionTargetName; int menuSelection = 0; // 0 = Archive, 1 = Delete bool ignoreNextConfirmRelease = false; // Prevents immediate selection after long-press opens menu + bool clearCachePreserveProgress = true; // For Clear Cache: whether to preserve reading progress // Recent tab state std::vector recentBooks; @@ -153,6 +155,9 @@ class MyLibraryActivity final : public Activity { // Clear all recents confirmation void renderClearAllRecentsConfirmation() const; + // Clear cache options confirmation + void renderClearCacheOptionsConfirmation() const; + public: explicit MyLibraryActivity( GfxRenderer& renderer, MappedInputManager& mappedInput, const std::function& onGoHome, diff --git a/src/activities/settings/ClearCacheActivity.cpp b/src/activities/settings/ClearCacheActivity.cpp index 4aeff73..a783462 100644 --- a/src/activities/settings/ClearCacheActivity.cpp +++ b/src/activities/settings/ClearCacheActivity.cpp @@ -4,6 +4,9 @@ #include #include +#include +#include + #include "MappedInputManager.h" #include "activities/home/HomeActivity.h" #include "activities/home/MyLibraryActivity.h" @@ -19,6 +22,7 @@ void ClearCacheActivity::onEnter() { renderingMutex = xSemaphoreCreateMutex(); state = WARNING; + preserveProgress = true; // Default to preserving progress updateRequired = true; xTaskCreate(&ClearCacheActivity::taskTrampoline, "ClearCacheActivityTask", @@ -56,6 +60,7 @@ void ClearCacheActivity::displayTaskLoop() { } void ClearCacheActivity::render() { + const auto pageWidth = renderer.getScreenWidth(); const auto pageHeight = renderer.getScreenHeight(); // Bezel compensation @@ -67,11 +72,32 @@ void ClearCacheActivity::render() { renderer.drawCenteredText(UI_12_FONT_ID, 15 + bezelTop, "Clear Cache", true, EpdFontFamily::BOLD); if (state == WARNING) { - renderer.drawCenteredText(UI_10_FONT_ID, centerY - 60, "This will clear all cached book data.", true); - renderer.drawCenteredText(UI_10_FONT_ID, centerY - 30, "All reading progress will be lost!", true, - EpdFontFamily::BOLD); - renderer.drawCenteredText(UI_10_FONT_ID, centerY + 10, "Books will need to be re-indexed", true); - renderer.drawCenteredText(UI_10_FONT_ID, centerY + 30, "when opened again.", true); + renderer.drawCenteredText(UI_10_FONT_ID, centerY - 70, "This will clear all cached book data.", true); + renderer.drawCenteredText(UI_10_FONT_ID, centerY - 45, "Books will need to be re-indexed.", true); + + // Preserve progress option + renderer.drawCenteredText(UI_10_FONT_ID, centerY - 5, "Preserve reading progress?"); + + // Yes/No options + constexpr int optionLineHeight = 30; + constexpr int optionWidth = 80; + const int optionX = (pageWidth - optionWidth) / 2; + const int optionStartY = centerY + 25; + + // Yes option + if (preserveProgress) { + renderer.fillRect(optionX - 10, optionStartY - 5, optionWidth + 20, optionLineHeight); + } + renderer.drawCenteredText(UI_10_FONT_ID, optionStartY, "Yes", !preserveProgress); + + // No option + if (!preserveProgress) { + renderer.fillRect(optionX - 10, optionStartY + optionLineHeight - 5, optionWidth + 20, optionLineHeight); + } + renderer.drawCenteredText(UI_10_FONT_ID, optionStartY + optionLineHeight, "No", preserveProgress); + + // Draw side button hints (up/down navigation) + renderer.drawSideButtonHints(UI_10_FONT_ID, ">", "<"); const auto labels = mappedInput.mapLabels("« Cancel", "Clear", "", ""); renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); @@ -110,8 +136,68 @@ void ClearCacheActivity::render() { } } +void ClearCacheActivity::clearCacheDirectory(const char* dirPath) { + // Helper to check if a file should be preserved + const auto shouldPreserve = [this](const char* name) { + if (!preserveProgress) return false; + // Preserve progress and bookmarks when preserveProgress is enabled + return (strcmp(name, "progress.bin") == 0 || strcmp(name, "bookmarks.bin") == 0); + }; + + FsFile dir = SdMan.open(dirPath); + if (!dir || !dir.isDirectory()) { + if (dir) dir.close(); + failedCount++; + return; + } + + char name[128]; + std::vector filesToDelete; + std::vector dirsToDelete; + + // First pass: collect files and directories to delete + for (FsFile entry = dir.openNextFile(); entry; entry = dir.openNextFile()) { + entry.getName(name, sizeof(name)); + const bool isDir = entry.isDirectory(); + entry.close(); + + std::string fullPath = std::string(dirPath) + "/" + name; + if (isDir) { + dirsToDelete.push_back(fullPath); + } else if (!shouldPreserve(name)) { + filesToDelete.push_back(fullPath); + } + } + dir.close(); + + // Delete files + for (const auto& path : filesToDelete) { + if (SdMan.remove(path.c_str())) { + clearedCount++; + } else { + Serial.printf("[%lu] [CLEAR_CACHE] Failed to delete file: %s\n", millis(), path.c_str()); + failedCount++; + } + } + + // Delete subdirectories (like "sections/") + for (const auto& path : dirsToDelete) { + if (SdMan.removeDir(path.c_str())) { + clearedCount++; + } else { + Serial.printf("[%lu] [CLEAR_CACHE] Failed to delete dir: %s\n", millis(), path.c_str()); + failedCount++; + } + } + + // If not preserving progress, try to remove the now-empty directory + if (!preserveProgress) { + SdMan.rmdir(dirPath); // This will fail if directory is not empty, which is fine + } +} + void ClearCacheActivity::clearCache() { - Serial.printf("[%lu] [CLEAR_CACHE] Clearing cache...\n", millis()); + Serial.printf("[%lu] [CLEAR_CACHE] Clearing cache (preserveProgress=%d)...\n", millis(), preserveProgress); // Open .crosspoint directory auto root = SdMan.open("/.crosspoint"); @@ -127,35 +213,31 @@ void ClearCacheActivity::clearCache() { failedCount = 0; char name[128]; - // Iterate through all entries in the directory + // Collect all book cache directories first + std::vector cacheDirs; for (auto file = root.openNextFile(); file; file = root.openNextFile()) { file.getName(name, sizeof(name)); String itemName(name); - // Only delete directories starting with epub_ or txt_ + // Only process directories starting with epub_ or txt_ if (file.isDirectory() && (itemName.startsWith("epub_") || itemName.startsWith("txt_"))) { - String fullPath = "/.crosspoint/" + itemName; - Serial.printf("[%lu] [CLEAR_CACHE] Removing cache: %s\n", millis(), fullPath.c_str()); - - file.close(); // Close before attempting to delete - - if (SdMan.removeDir(fullPath.c_str())) { - clearedCount++; - } else { - Serial.printf("[%lu] [CLEAR_CACHE] Failed to remove: %s\n", millis(), fullPath.c_str()); - failedCount++; - } - } else { - file.close(); + cacheDirs.push_back("/.crosspoint/" + std::string(name)); } + file.close(); } root.close(); + // Now clear each cache directory + for (const auto& cacheDir : cacheDirs) { + Serial.printf("[%lu] [CLEAR_CACHE] Clearing: %s\n", millis(), cacheDir.c_str()); + clearCacheDirectory(cacheDir.c_str()); + } + // Also clear in-memory caches since disk cache is gone HomeActivity::freeCoverBufferIfAllocated(); MyLibraryActivity::clearThumbExistsCache(); - Serial.printf("[%lu] [CLEAR_CACHE] Cache cleared: %d removed, %d failed\n", millis(), clearedCount, failedCount); + Serial.printf("[%lu] [CLEAR_CACHE] Cache cleared: %d items removed, %d failed\n", millis(), clearedCount, failedCount); state = SUCCESS; updateRequired = true; @@ -163,8 +245,17 @@ void ClearCacheActivity::clearCache() { void ClearCacheActivity::loop() { if (state == WARNING) { + // Up/Down toggle preserve progress option + if (mappedInput.wasPressed(MappedInputManager::Button::Up) || + mappedInput.wasPressed(MappedInputManager::Button::Down)) { + preserveProgress = !preserveProgress; + updateRequired = true; + return; + } + if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) { - Serial.printf("[%lu] [CLEAR_CACHE] User confirmed, starting cache clear\n", millis()); + Serial.printf("[%lu] [CLEAR_CACHE] User confirmed (preserveProgress=%d), starting cache clear\n", millis(), + preserveProgress); xSemaphoreTake(renderingMutex, portMAX_DELAY); state = CLEARING; xSemaphoreGive(renderingMutex); diff --git a/src/activities/settings/ClearCacheActivity.h b/src/activities/settings/ClearCacheActivity.h index 31795a9..8e8a3f5 100644 --- a/src/activities/settings/ClearCacheActivity.h +++ b/src/activities/settings/ClearCacheActivity.h @@ -29,9 +29,11 @@ class ClearCacheActivity final : public ActivityWithSubactivity { int clearedCount = 0; int failedCount = 0; + bool preserveProgress = true; // Whether to keep progress.bin and bookmarks.bin static void taskTrampoline(void* param); [[noreturn]] void displayTaskLoop(); void render(); void clearCache(); + void clearCacheDirectory(const char* dirPath); // Helper to clear a single book's cache };