diff --git a/lib/FsHelpers/FsHelpers.h b/lib/FsHelpers/FsHelpers.h index b1aa0c41..575be23e 100644 --- a/lib/FsHelpers/FsHelpers.h +++ b/lib/FsHelpers/FsHelpers.h @@ -49,6 +49,9 @@ inline bool hasEpubExtension(const String& fileName) { // Check for either .xtc or .xtch extension (case-insensitive) bool hasXtcExtension(std::string_view fileName); +inline bool hasXtcExtension(const String& fileName) { + return hasXtcExtension(std::string_view{fileName.c_str(), fileName.length()}); +} // Check for .txt extension (case-insensitive) bool hasTxtExtension(std::string_view fileName); @@ -58,5 +61,8 @@ inline bool hasTxtExtension(const String& fileName) { // Check for .md extension (case-insensitive) bool hasMarkdownExtension(std::string_view fileName); +inline bool hasMarkdownExtension(const String& fileName) { + return hasMarkdownExtension(std::string_view{fileName.c_str(), fileName.length()}); +} } // namespace FsHelpers diff --git a/platformio.ini b/platformio.ini index 372760aa..9cd2f1f1 100644 --- a/platformio.ini +++ b/platformio.ini @@ -37,6 +37,7 @@ build_flags = # Default is (320*4+1)*2=2562, we need more for larger images -DPNG_MAX_BUFFERED_PIXELS=16416 -Wno-bidi-chars + -Wno-deprecated-declarations -Wl,--wrap=panic_print_backtrace,--wrap=panic_abort,--wrap=bootloader_common_check_efuse_blk_validity -fno-exceptions diff --git a/src/activities/home/HomeActivity.cpp b/src/activities/home/HomeActivity.cpp index f8c97d97..868650a4 100644 --- a/src/activities/home/HomeActivity.cpp +++ b/src/activities/home/HomeActivity.cpp @@ -28,11 +28,8 @@ #include "fontIds.h" namespace { -// Convert a sidecar JPG/PNG cover to a 1-bit BMP in the cache and return the BMP path, or "" on failure. -// fileName is the basename of the output file (without directory), e.g. "340x540.bmp" or "400.bmp". -std::string convertSidecarToBmp(const std::string& bookPath, const std::string& sidecarPath, int width, int height, +std::string convertSidecarToBmp(const std::string& cacheDir, const std::string& sidecarPath, int width, int height, const std::string& fileName) { - const std::string cacheDir = "/.crosspoint/sidecar_" + std::to_string(std::hash{}(bookPath)); Storage.mkdir(cacheDir.c_str()); const std::string bmpPath = cacheDir + "/" + fileName; if (Storage.exists(bmpPath.c_str())) return bmpPath; @@ -180,8 +177,7 @@ void HomeActivity::loadRecentBooks(int maxBooks) { // Also catches books registered before sidecar support (empty coverBmpPath). const std::string sidecar = ReaderActivity::sidecarCoverPath(book.path); if (!sidecar.empty()) { - const bool sidecarAlreadyStored = - book.coverBmpPath == sidecar || book.coverBmpPath.find("sidecar_") != std::string::npos; + const bool sidecarAlreadyStored = book.coverBmpPath == sidecar; LOG_DBG("HOME", "Sidecar for %s: stored=%s alreadyStored=%d", book.path.c_str(), book.coverBmpPath.c_str(), sidecarAlreadyStored ? 1 : 0); if (!sidecarAlreadyStored) { @@ -222,13 +218,13 @@ void HomeActivity::loadRecentCovers(int coverHeight) { // The cache will be rebuilt on the next render. UITheme::getInstance().getMutableTheme().invalidateFrameCache(); - const std::string cacheBase = "/.crosspoint/sidecar_" + std::to_string(std::hash{}(book.path)); + const std::string cacheBase = ReaderActivity::bookCacheDir(book.path); const std::string placeholder = cacheBase + "/[HEIGHT].bmp"; bool success = true; if (!thumbSizes.empty()) { for (const auto& sz : thumbSizes) { const std::string name = std::to_string(sz.first) + "x" + std::to_string(sz.second) + ".bmp"; - if (convertSidecarToBmp(book.path, book.coverBmpPath, sz.first, sz.second, name).empty()) { + if (convertSidecarToBmp(cacheBase, book.coverBmpPath, sz.first, sz.second, name).empty()) { success = false; break; } @@ -236,7 +232,7 @@ void HomeActivity::loadRecentCovers(int coverHeight) { } else { const int w = coverHeight * 6 / 10; const std::string name = std::to_string(coverHeight) + ".bmp"; - if (convertSidecarToBmp(book.path, book.coverBmpPath, w, coverHeight, name).empty()) success = false; + if (convertSidecarToBmp(cacheBase, book.coverBmpPath, w, coverHeight, name).empty()) success = false; } if (success) { LOG_DBG("HOME", "Sidecar converted, placeholder: %s", placeholder.c_str()); diff --git a/src/activities/reader/FinishedBookActivity.cpp b/src/activities/reader/FinishedBookActivity.cpp index 6e20d07a..c8455535 100644 --- a/src/activities/reader/FinishedBookActivity.cpp +++ b/src/activities/reader/FinishedBookActivity.cpp @@ -65,9 +65,8 @@ std::string findUniquePathWithSuffix(const std::string& basePath) { std::string findUniqueCompletedSidecarPath(const std::string& basePath) { return findUniquePathWithSuffix(basePath); } -std::string convertSidecarToBmp(const std::string& bookPath, const std::string& sidecarPath, int width, int height, +std::string convertSidecarToBmp(const std::string& cacheDir, const std::string& sidecarPath, int width, int height, const std::string& fileName) { - const std::string cacheDir = "/.crosspoint/sidecar_" + std::to_string(std::hash{}(bookPath)); if (!Storage.exists(cacheDir.c_str())) { Storage.mkdir(cacheDir.c_str()); } @@ -120,7 +119,7 @@ std::string getSidecarCoverBmpPath(const std::string& bookPath, int width, int h } const std::string fileName = "thumb_" + std::to_string(width) + "x" + std::to_string(height) + ".bmp"; - return convertSidecarToBmp(bookPath, sidecarPath, width, height, fileName); + return convertSidecarToBmp(ReaderActivity::bookCacheDir(bookPath), sidecarPath, width, height, fileName); } bool moveSidecarFilesToCompleted(const std::string& currentBookPath, const std::string& targetBookPath) { diff --git a/src/activities/reader/ReaderActivity.cpp b/src/activities/reader/ReaderActivity.cpp index b3a8d3af..06c2bdb6 100644 --- a/src/activities/reader/ReaderActivity.cpp +++ b/src/activities/reader/ReaderActivity.cpp @@ -71,6 +71,12 @@ std::string ReaderActivity::sidecarCoverPath(const std::string& bookPath) { return ""; } +std::string ReaderActivity::bookCacheDir(const std::string& bookPath) { + if (FsHelpers::hasEpubExtension(bookPath)) return Epub(bookPath, "/.crosspoint").getCachePath(); + if (FsHelpers::hasXtcExtension(bookPath)) return Xtc(bookPath, "/.crosspoint").getCachePath(); + return Txt(bookPath, "/.crosspoint").getCachePath(); +} + std::unique_ptr ReaderActivity::loadEpub(const std::string& path) { if (!Storage.exists(path.c_str())) { LOG_ERR("READER", "File does not exist: %s", path.c_str()); diff --git a/src/activities/reader/ReaderActivity.h b/src/activities/reader/ReaderActivity.h index bc24c94a..84325d18 100644 --- a/src/activities/reader/ReaderActivity.h +++ b/src/activities/reader/ReaderActivity.h @@ -31,6 +31,7 @@ class ReaderActivity final : public Activity { public: static std::string sidecarCoverPath(const std::string& bookPath); + static std::string bookCacheDir(const std::string& bookPath); explicit ReaderActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::string initialBookPath) : Activity("Reader", renderer, mappedInput), initialBookPath(std::move(initialBookPath)) {} diff --git a/src/activities/settings/ClearCacheActivity.cpp b/src/activities/settings/ClearCacheActivity.cpp index 698e0a46..e6a7cc2a 100644 --- a/src/activities/settings/ClearCacheActivity.cpp +++ b/src/activities/settings/ClearCacheActivity.cpp @@ -105,8 +105,8 @@ void ClearCacheActivity::clearCache() { file.getName(name, sizeof(name)); String itemName(name); - // Only delete directories starting with epub_ or xtc_ - if (file.isDirectory() && (itemName.startsWith("epub_") || itemName.startsWith("xtc_"))) { + if (file.isDirectory() && + (itemName.startsWith("epub_") || itemName.startsWith("xtc_") || itemName.startsWith("txt_"))) { String fullPath = "/.crosspoint/" + itemName; LOG_DBG("CLEAR_CACHE", "Removing cache: %s", fullPath.c_str()); diff --git a/src/network/CrossPointWebServer.cpp b/src/network/CrossPointWebServer.cpp index 0350d967..15f3e9e9 100644 --- a/src/network/CrossPointWebServer.cpp +++ b/src/network/CrossPointWebServer.cpp @@ -5,7 +5,9 @@ #include #include #include +#include #include +#include #include #include @@ -58,16 +60,25 @@ String wsLastCompleteName; size_t wsLastCompleteSize = 0; unsigned long wsLastCompleteAt = 0; -// Helper function to clear epub cache after upload -void clearEpubCacheIfNeeded(const String& filePath) { +void clearBookCacheIfNeeded(const String& filePath) { if (FsHelpers::hasEpubExtension(filePath)) { Epub(filePath.c_str(), "/.crosspoint").clearCache(); LOG_DBG("WEB", "Cleared epub cache for: %s", filePath.c_str()); + } else if (FsHelpers::hasXtcExtension(filePath)) { + Xtc(filePath.c_str(), "/.crosspoint").clearCache(); + LOG_DBG("WEB", "Cleared xtc cache for: %s", filePath.c_str()); + } else if (FsHelpers::hasTxtExtension(filePath) || FsHelpers::hasMarkdownExtension(filePath)) { + const Txt txt(filePath.c_str(), "/.crosspoint"); + const String cachePath = txt.getCachePath().c_str(); + if (Storage.exists(cachePath.c_str())) { + Storage.removeDir(cachePath.c_str()); + LOG_DBG("WEB", "Cleared txt cache for: %s", filePath.c_str()); + } } } -// Recursively clear epub caches for all EPUBs inside a directory -void clearEpubCachesInDirectory(const String& dirPath) { +// Recursively clear book caches for all ebooks inside a directory +void clearBookCachesInDirectory(const String& dirPath) { esp_task_wdt_reset(); yield(); FsFile dir = Storage.open(dirPath.c_str()); @@ -86,10 +97,10 @@ void clearEpubCachesInDirectory(const String& dirPath) { childPath += name; if (entry.isDirectory()) { entry.close(); - clearEpubCachesInDirectory(childPath); + clearBookCachesInDirectory(childPath); } else { entry.close(); - clearEpubCacheIfNeeded(childPath); + clearBookCacheIfNeeded(childPath); } entry = dir.openNextFile(); } @@ -847,7 +858,7 @@ void CrossPointWebServer::handleUpload(UploadState& state) const { String filePath = state.path; if (!filePath.endsWith("/")) filePath += "/"; filePath += state.fileName; - clearEpubCacheIfNeeded(filePath); + clearBookCacheIfNeeded(filePath); } } } else if (upload.status == UPLOAD_FILE_ABORTED) { @@ -990,9 +1001,9 @@ void CrossPointWebServer::handleRename() const { } if (isDir) { - clearEpubCachesInDirectory(itemPath); + clearBookCachesInDirectory(itemPath); } else { - clearEpubCacheIfNeeded(itemPath); + clearBookCacheIfNeeded(itemPath); } const bool success = file.rename(newPath.c_str()); file.close(); @@ -1088,9 +1099,9 @@ void CrossPointWebServer::handleMove() const { } if (isDir) { - clearEpubCachesInDirectory(itemPath); + clearBookCachesInDirectory(itemPath); } else { - clearEpubCacheIfNeeded(itemPath); + clearBookCacheIfNeeded(itemPath); } const bool success = file.rename(newPath.c_str()); file.close(); @@ -1210,7 +1221,7 @@ void CrossPointWebServer::handleDelete() const { // It's a file (or couldn't open as dir) — remove file if (f) f.close(); success = Storage.remove(itemPath.c_str()); - clearEpubCacheIfNeeded(itemPath); + clearBookCacheIfNeeded(itemPath); } if (!success) { @@ -2397,7 +2408,7 @@ void CrossPointWebServer::onWebSocketEvent(uint8_t num, WStype_t type, uint8_t* wsLastCompleteSize = 0; wsLastCompleteAt = millis(); LOG_DBG("WS", "Zero-byte upload complete: %s", filePath.c_str()); - clearEpubCacheIfNeeded(filePath); + clearBookCacheIfNeeded(filePath); wsServer->sendTXT(num, "DONE"); wsLastProgressSent = 0; break; @@ -2465,7 +2476,7 @@ void CrossPointWebServer::onWebSocketEvent(uint8_t num, WStype_t type, uint8_t* String filePath = wsUploadPath; if (!filePath.endsWith("/")) filePath += "/"; filePath += wsUploadFileName; - clearEpubCacheIfNeeded(filePath); + clearBookCacheIfNeeded(filePath); wsServer->sendTXT(num, "DONE"); wsLastProgressSent = 0; diff --git a/src/network/WebDAVHandler.cpp b/src/network/WebDAVHandler.cpp index b6c8a5e1..46ae4450 100644 --- a/src/network/WebDAVHandler.cpp +++ b/src/network/WebDAVHandler.cpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include namespace { @@ -802,6 +804,16 @@ void WebDAVHandler::clearEpubCacheIfNeeded(const String& path) const { if (FsHelpers::hasEpubExtension(path)) { Epub(path.c_str(), "/.crosspoint").clearCache(); LOG_DBG("DAV", "Cleared epub cache for: %s", path.c_str()); + } else if (FsHelpers::hasXtcExtension(path)) { + Xtc(path.c_str(), "/.crosspoint").clearCache(); + LOG_DBG("DAV", "Cleared xtc cache for: %s", path.c_str()); + } else if (FsHelpers::hasTxtExtension(path) || FsHelpers::hasMarkdownExtension(path)) { + const Txt txt(path.c_str(), "/.crosspoint"); + const String cachePath = txt.getCachePath().c_str(); + if (Storage.exists(cachePath.c_str())) { + Storage.removeDir(cachePath.c_str()); + LOG_DBG("DAV", "Cleared txt cache for: %s", path.c_str()); + } } }