From 913c24407b521a8c29cdb1bc15125f61c7e1c07d Mon Sep 17 00:00:00 2001 From: jpirnay Date: Thu, 19 Mar 2026 16:43:34 +0100 Subject: [PATCH 1/2] case fix --- lib/KOReaderSync/ChapterXPathIndexer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/KOReaderSync/ChapterXPathIndexer.cpp b/lib/KOReaderSync/ChapterXPathIndexer.cpp index 4a6dbc35..a9e27a49 100644 --- a/lib/KOReaderSync/ChapterXPathIndexer.cpp +++ b/lib/KOReaderSync/ChapterXPathIndexer.cpp @@ -45,7 +45,7 @@ struct ParserState { std::vector> siblingCounters; std::vector anchors; - std::string baseXPath() const { return "/body/DocFragment[" + std::to_string(spineIndex + 1) + "]/body"; } + std::string baseXPath() const { return "/body/docfragment[" + std::to_string(spineIndex + 1) + "]/body"; } // Canonicalize incoming KOReader XPath before matching: // - remove all whitespace @@ -135,7 +135,7 @@ struct ParserState { const std::string& anchorPath = ignoreIndices ? anchor.xpathNoIndex : anchor.xpath; if (anchorPath == probe) { const int depth = pathDepth(anchorPath); - if (!found || depth > bestDepth || (depth == bestDepth && anchor.textOffset < bestOffset)) { + if (!found || depth > bestDepth || (depth == bestDepth && anchor.textOffset > bestOffset)) { found = true; bestDepth = depth; bestOffset = anchor.textOffset; From 5dec3d7f95e2108d7d12822b4e33bcd4b7a361b6 Mon Sep 17 00:00:00 2001 From: jpirnay Date: Fri, 20 Mar 2026 09:43:26 +0100 Subject: [PATCH 2/2] Reuse spine --- lib/KOReaderSync/ChapterXPathIndexer.cpp | 56 +++++++++++++++++++++++- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/lib/KOReaderSync/ChapterXPathIndexer.cpp b/lib/KOReaderSync/ChapterXPathIndexer.cpp index a9e27a49..96325fd2 100644 --- a/lib/KOReaderSync/ChapterXPathIndexer.cpp +++ b/lib/KOReaderSync/ChapterXPathIndexer.cpp @@ -463,11 +463,63 @@ static std::optional parseSpineItem(const std::shared_ptr& ep return state; } +// Keep a single parsed spine in memory to avoid repeated decompress+parse work +// in the KO sync flow. +// +// Why this helps here: +// - Sync result screen calls both mapping directions (remote->local for compare, +// local->KOReader for display). +// - If the user chooses Upload, local->KOReader mapping is performed again. +// - Without caching, the same chapter can be decompressed and reparsed multiple +// times during one activity session. +// +// Why the cache is exactly one entry: +// - We only need to accelerate immediate repeat lookups for the current chapter. +// - A single-entry cache bounds RAM use and avoids long-lived heap growth on +// ESP32-C3. +static const ParserState* getParsedStateCached(const std::shared_ptr& epub, const int spineIndex) { + static const Epub* cachedEpub = nullptr; + static int cachedSpineIndex = -1; + static std::string cachedHref; + static std::optional cachedState; + + if (!epub || spineIndex < 0 || spineIndex >= epub->getSpineItemsCount()) { + return nullptr; + } + + const auto spineItem = epub->getSpineItem(spineIndex); + if (spineItem.href.empty()) { + return nullptr; + } + + const bool cacheHit = cachedState.has_value() && cachedEpub == epub.get() && cachedSpineIndex == spineIndex && + cachedHref == spineItem.href; + if (cacheHit) { + LOG_DBG("KOX", "Cache hit: spine=%d anchors=%zu textBytes=%zu", spineIndex, cachedState->anchors.size(), + cachedState->totalTextBytes); + return &cachedState.value(); + } + + auto parsed = parseSpineItem(epub, spineIndex); + if (!parsed) { + return nullptr; + } + + cachedEpub = epub.get(); + cachedSpineIndex = spineIndex; + cachedHref = spineItem.href; + cachedState = std::move(parsed); + + LOG_DBG("KOX", "Cache store: spine=%d anchors=%zu textBytes=%zu", spineIndex, cachedState->anchors.size(), + cachedState->totalTextBytes); + return &cachedState.value(); +} + } // namespace std::string ChapterXPathIndexer::findXPathForProgress(const std::shared_ptr& epub, const int spineIndex, const float intraSpineProgress) { - const auto state = parseSpineItem(epub, spineIndex); + const auto* state = getParsedStateCached(epub, spineIndex); if (!state) { return ""; } @@ -488,7 +540,7 @@ bool ChapterXPathIndexer::findProgressForXPath(const std::shared_ptr& epub return false; } - const auto state = parseSpineItem(epub, spineIndex); + const auto* state = getParsedStateCached(epub, spineIndex); if (!state) { return false; }