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
This commit is contained in:
parent
5464d9de3a
commit
448ce55bb4
@ -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<std::string> BookManager::listArchivedBooks() {
|
||||
std::vector<std::string> archivedBooks;
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -503,17 +503,23 @@ 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
|
||||
// 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;
|
||||
@ -521,6 +527,7 @@ void MyLibraryActivity::executeAction() {
|
||||
selectorIndex = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uiState = UIState::Normal;
|
||||
updateRequired = true;
|
||||
@ -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);
|
||||
|
||||
// Recent tab only: Remove from Recents and Clear All Recents
|
||||
if (isRecentTab) {
|
||||
// Remove from Recents option
|
||||
// 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, "Remove from Recents",
|
||||
menuSelection != 2);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, menuStartY + menuLineHeight * 2, "Clear Cache", menuSelection != 2);
|
||||
|
||||
// Clear All Recents option
|
||||
// Recent tab only: Remove from Recents and Clear All Recents
|
||||
if (isRecentTab) {
|
||||
// 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();
|
||||
|
||||
@ -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<RecentBook> 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<void()>& onGoHome,
|
||||
|
||||
@ -4,6 +4,9 @@
|
||||
#include <HardwareSerial.h>
|
||||
#include <SDCardManager.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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<std::string> filesToDelete;
|
||||
std::vector<std::string> 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<std::string> 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++;
|
||||
cacheDirs.push_back("/.crosspoint/" + std::string(name));
|
||||
}
|
||||
} else {
|
||||
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);
|
||||
|
||||
@ -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
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user