Cache KOReader document hash
This commit is contained in:
@@ -4,6 +4,8 @@
|
|||||||
#include <Logging.h>
|
#include <Logging.h>
|
||||||
#include <MD5Builder.h>
|
#include <MD5Builder.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
// Extract filename from path (everything after last '/')
|
// Extract filename from path (everything after last '/')
|
||||||
std::string getFilename(const std::string& path) {
|
std::string getFilename(const std::string& path) {
|
||||||
@@ -15,6 +17,69 @@ std::string getFilename(const std::string& path) {
|
|||||||
}
|
}
|
||||||
} // namespace
|
} // 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<std::string>{}(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: "<filesize>\n<32-char-hex-hash>"
|
||||||
|
const int newlinePos = content.indexOf('\n');
|
||||||
|
if (newlinePos < 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t cachedSize = static_cast<size_t>(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) {
|
std::string KOReaderDocumentId::calculateFromFilename(const std::string& filePath) {
|
||||||
const std::string filename = getFilename(filePath);
|
const std::string filename = getFilename(filePath);
|
||||||
if (filename.empty()) {
|
if (filename.empty()) {
|
||||||
@@ -49,6 +114,15 @@ std::string KOReaderDocumentId::calculate(const std::string& filePath) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const size_t fileSize = file.fileSize();
|
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);
|
LOG_DBG("KODoc", "Calculating hash for file: %s (size: %zu)", filePath.c_str(), fileSize);
|
||||||
|
|
||||||
// Initialize MD5 builder
|
// 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);
|
LOG_DBG("KODoc", "Hash calculated: %s (from %zu bytes)", result.c_str(), totalBytesRead);
|
||||||
|
|
||||||
|
saveCachedHash(cacheFilePath, fileSize, result);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,4 +42,15 @@ class KOReaderDocumentId {
|
|||||||
|
|
||||||
// Calculate offset for index i: 1024 << (2*i)
|
// Calculate offset for index i: 1024 << (2*i)
|
||||||
static size_t getOffset(int 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_<hash>/).
|
||||||
|
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);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user