diff --git a/lib/KOReaderSync/KOReaderDocumentId.cpp b/lib/KOReaderSync/KOReaderDocumentId.cpp index efb18d1b..434748c4 100644 --- a/lib/KOReaderSync/KOReaderDocumentId.cpp +++ b/lib/KOReaderSync/KOReaderDocumentId.cpp @@ -4,6 +4,8 @@ #include #include +#include + namespace { // Extract filename from path (everything after last '/') std::string getFilename(const std::string& path) { @@ -15,6 +17,69 @@ std::string getFilename(const std::string& path) { } } // namespace +std::string KOReaderDocumentId::getCacheFilePath(const std::string& filePath) { + // Mirror the Epub cache directory convention so the hash file shares the + // same per-book folder as other cached data. + return std::string("/.crosspoint/epub_") + + std::to_string(std::hash{}(filePath)) + + "/koreader_docid.txt"; +} + +std::string KOReaderDocumentId::loadCachedHash(const std::string& cacheFilePath, + const size_t fileSize) { + if (!Storage.exists(cacheFilePath.c_str())) { + return ""; + } + + const String content = Storage.readFile(cacheFilePath.c_str()); + if (content.isEmpty()) { + return ""; + } + + // Format: "\n<32-char-hex-hash>" + const int newlinePos = content.indexOf('\n'); + if (newlinePos < 0) { + return ""; + } + + const size_t cachedSize = static_cast(content.substring(0, newlinePos).toInt()); + if (cachedSize != fileSize) { + LOG_DBG("KODoc", "Hash cache invalidated: file size changed (%zu -> %zu)", cachedSize, fileSize); + return ""; + } + + std::string hash = content.substring(newlinePos + 1).c_str(); + // Trim any trailing whitespace / line endings + while (!hash.empty() && (hash.back() == '\n' || hash.back() == '\r' || hash.back() == ' ')) { + hash.pop_back(); + } + + if (hash.size() != 32) { + return ""; + } + + LOG_DBG("KODoc", "Hash cache hit: %s", hash.c_str()); + return hash; +} + +void KOReaderDocumentId::saveCachedHash(const std::string& cacheFilePath, + const size_t fileSize, + const std::string& hash) { + // Ensure the book's cache directory exists before writing + const size_t lastSlash = cacheFilePath.rfind('/'); + if (lastSlash != std::string::npos) { + Storage.ensureDirectoryExists(cacheFilePath.substr(0, lastSlash).c_str()); + } + + String content(std::to_string(fileSize).c_str()); + content += '\n'; + content += hash.c_str(); + + if (!Storage.writeFile(cacheFilePath.c_str(), content)) { + LOG_DBG("KODoc", "Failed to write hash cache to %s", cacheFilePath.c_str()); + } +} + std::string KOReaderDocumentId::calculateFromFilename(const std::string& filePath) { const std::string filename = getFilename(filePath); if (filename.empty()) { @@ -49,6 +114,15 @@ std::string KOReaderDocumentId::calculate(const std::string& filePath) { } const size_t fileSize = file.fileSize(); + + // Return persisted hash if the file size hasn't changed since it was cached + const std::string cacheFilePath = getCacheFilePath(filePath); + const std::string cached = loadCachedHash(cacheFilePath, fileSize); + if (!cached.empty()) { + file.close(); + return cached; + } + LOG_DBG("KODoc", "Calculating hash for file: %s (size: %zu)", filePath.c_str(), fileSize); // Initialize MD5 builder @@ -92,5 +166,7 @@ std::string KOReaderDocumentId::calculate(const std::string& filePath) { LOG_DBG("KODoc", "Hash calculated: %s (from %zu bytes)", result.c_str(), totalBytesRead); + saveCachedHash(cacheFilePath, fileSize, result); + return result; } diff --git a/lib/KOReaderSync/KOReaderDocumentId.h b/lib/KOReaderSync/KOReaderDocumentId.h index 2b6189e2..a78c134f 100644 --- a/lib/KOReaderSync/KOReaderDocumentId.h +++ b/lib/KOReaderSync/KOReaderDocumentId.h @@ -42,4 +42,15 @@ class KOReaderDocumentId { // Calculate offset for index i: 1024 << (2*i) static size_t getOffset(int i); + + // Hash cache helpers + // Returns the path to the per-book cache file that stores the precomputed hash. + // Uses the same directory convention as the Epub cache (/.crosspoint/epub_/). + static std::string getCacheFilePath(const std::string& filePath); + + // Returns the cached hash if the file size matches, or empty string on miss/invalidation. + static std::string loadCachedHash(const std::string& cacheFilePath, size_t fileSize); + + // Persists the computed hash alongside the file size used to compute it. + static void saveCachedHash(const std::string& cacheFilePath, size_t fileSize, const std::string& hash); };