Improving KOReader sync

This commit is contained in:
jpirnay
2026-05-10 15:46:33 +02:00
parent b5ea8d3550
commit fe60b12d86
18 changed files with 252 additions and 54 deletions
+7
View File
@@ -52,6 +52,11 @@ struct KOReaderSyncSessionState {
int resultPage = 0;
uint16_t resultParagraphIndex = 0;
bool resultHasParagraphIndex = false;
// Running <li> count for the matched element, used by EpubReaderActivity to snap a
// KOReader-supplied list-item XPath to the precise page via Section::getPageForListItemIndex.
// Preferred over resultParagraphIndex when the deepest target element is /li[N].
uint16_t resultListItemIndex = 0;
bool resultHasListItemIndex = false;
// When true (auto-push-on-close), the sync activity goes to home instead of the reader on
// completion. Without this, AUTO_PUSH would bounce back into the reader the user just left.
bool exitToHomeAfterSync = false;
@@ -74,6 +79,8 @@ struct KOReaderSyncSessionState {
resultPage = 0;
resultParagraphIndex = 0;
resultHasParagraphIndex = false;
resultListItemIndex = 0;
resultHasListItemIndex = false;
exitToHomeAfterSync = false;
autoPullEpubPath.clear();
}
+4
View File
@@ -101,6 +101,8 @@ bool JsonSettingsIO::saveState(const CrossPointState& s, const char* path) {
sync["resultPage"] = s.koReaderSyncSession.resultPage;
sync["resultParagraphIndex"] = s.koReaderSyncSession.resultParagraphIndex;
sync["resultHasParagraphIndex"] = s.koReaderSyncSession.resultHasParagraphIndex;
sync["resultListItemIndex"] = s.koReaderSyncSession.resultListItemIndex;
sync["resultHasListItemIndex"] = s.koReaderSyncSession.resultHasListItemIndex;
sync["exitToHomeAfterSync"] = s.koReaderSyncSession.exitToHomeAfterSync;
sync["autoPullEpubPath"] = s.koReaderSyncSession.autoPullEpubPath;
// Information about a pending bookmark jump
@@ -158,6 +160,8 @@ bool JsonSettingsIO::loadState(CrossPointState& s, const char* json) {
s.koReaderSyncSession.resultPage = sync["resultPage"] | 0;
s.koReaderSyncSession.resultParagraphIndex = sync["resultParagraphIndex"] | (uint16_t)0;
s.koReaderSyncSession.resultHasParagraphIndex = sync["resultHasParagraphIndex"] | false;
s.koReaderSyncSession.resultListItemIndex = sync["resultListItemIndex"] | (uint16_t)0;
s.koReaderSyncSession.resultHasListItemIndex = sync["resultHasListItemIndex"] | false;
s.koReaderSyncSession.exitToHomeAfterSync = sync["exitToHomeAfterSync"] | false;
s.koReaderSyncSession.autoPullEpubPath = sync["autoPullEpubPath"] | std::string("");
if (s.koReaderSyncSession.autoPullEpubPath.empty() && (sync["autoPullOnOpen"] | false)) {
+2
View File
@@ -50,6 +50,8 @@ struct SyncResult {
int page = 0; // estimated page (fallback)
uint16_t paragraphIndex = 0; // 1-based <p> index from XPath
bool hasParagraphIndex = false; // true when paragraphIndex is available
uint16_t listItemIndex = 0; // running <li> count when XPath ends in /li[N]
bool hasListItemIndex = false; // true when listItemIndex is available
};
enum class NetworkMode;
+26 -5
View File
@@ -925,6 +925,8 @@ void EpubReaderActivity::applyPendingSyncSession() {
int restorePage = sync.page;
pendingParagraphLookup = false;
pendingParagraphIndex = 0;
pendingListItemLookup = false;
pendingListItemIndex = 0;
if (restoreSpineIndex < 0 || restoreSpineIndex >= epub->getSpineItemsCount()) {
LOG_ERR("ERS", "Invalid sync restore spine index %d, resetting to 0", restoreSpineIndex);
@@ -932,6 +934,8 @@ void EpubReaderActivity::applyPendingSyncSession() {
restorePage = 0;
pendingParagraphLookup = false;
pendingParagraphIndex = 0;
pendingListItemLookup = false;
pendingListItemIndex = 0;
}
if (sync.outcome == KOReaderSyncOutcomeState::APPLIED_REMOTE) {
@@ -939,8 +943,11 @@ void EpubReaderActivity::applyPendingSyncSession() {
restorePage = sync.resultPage;
pendingParagraphLookup = sync.resultHasParagraphIndex;
pendingParagraphIndex = sync.resultParagraphIndex;
LOG_DBG("ERS", "Applied synced remote position: spine=%d page=%d paragraph=%u hasParagraph=%s", restoreSpineIndex,
restorePage, pendingParagraphIndex, pendingParagraphLookup ? "yes" : "no");
pendingListItemLookup = sync.resultHasListItemIndex;
pendingListItemIndex = sync.resultListItemIndex;
LOG_DBG("ERS", "Applied synced remote position: spine=%d page=%d paragraph=%u hasParagraph=%s liIdx=%u hasLi=%s",
restoreSpineIndex, restorePage, pendingParagraphIndex, pendingParagraphLookup ? "yes" : "no",
pendingListItemIndex, pendingListItemLookup ? "yes" : "no");
} else {
LOG_DBG("ERS", "Restored local pre-sync position: spine=%d page=%d", restoreSpineIndex, restorePage);
}
@@ -1369,16 +1376,30 @@ void EpubReaderActivity::render(RenderLock&& lock) {
pendingAnchor.clear();
}
// Resolve pending KOReader sync paragraph index to accurate page via Section paragraph LUT
if (pendingParagraphLookup) {
// Resolve pending KOReader sync position via Section LUTs.
// <li>-anchored XPaths can't be expressed in the body-child <p> LUT, so try the
// li LUT first when set; fall back to the paragraph LUT (which handles direct
// <p> children of <body>) on miss.
bool resolvedFromLut = false;
if (pendingListItemLookup) {
if (const auto page = section->getPageForListItemIndex(pendingListItemIndex)) {
section->currentPage = *page;
LOG_DBG("ERS", "Resolved li[%u] to page %d (was %d)", pendingListItemIndex, *page, nextPageNumber);
resolvedFromLut = true;
} else {
LOG_DBG("ERS", "Li index %u not found in section LUT", pendingListItemIndex);
}
pendingListItemLookup = false;
}
if (!resolvedFromLut && pendingParagraphLookup) {
if (const auto page = section->getPageForParagraphIndex(pendingParagraphIndex)) {
section->currentPage = *page;
LOG_DBG("ERS", "Resolved p[%u] to page %d (was %d)", pendingParagraphIndex, *page, nextPageNumber);
} else {
LOG_DBG("ERS", "Paragraph LUT not available, using estimated page %d", nextPageNumber);
}
pendingParagraphLookup = false;
}
pendingParagraphLookup = false;
// handles changes in reader settings and reset to approximate position based on cached progress
if (cachedChapterTotalPageCount > 0) {
@@ -122,6 +122,11 @@ class EpubReaderActivity final : public Activity {
// Pending paragraph index from KOReader sync (resolved to page via Section paragraph LUT)
bool pendingParagraphLookup = false;
uint16_t pendingParagraphIndex = 0;
// Pending list-item index for KOReader-supplied XPaths whose deepest element is /li[N].
// Preferred over pendingParagraphLookup when set because <li>-anchored XPaths are not
// representable in the body-child <p> LUT.
bool pendingListItemLookup = false;
uint16_t pendingListItemIndex = 0;
bool pendingScreenshot = false;
bool skipNextButtonCheck = false; // Skip button processing for one frame after subactivity exit
ReaderUtils::InputDrainGuard inputDrainGuard;
+11 -2
View File
@@ -220,6 +220,8 @@ void KOReaderSyncActivity::performFetchAndCompare() {
remotePosition.totalPages = 0;
remotePosition.paragraphIndex = 0;
remotePosition.hasParagraphIndex = false;
remotePosition.listItemIndex = 0;
remotePosition.hasListItemIndex = false;
remoteChapterLabel.clear();
if (syncIntent == KOReaderSyncIntentState::PULL_REMOTE || syncIntent == KOReaderSyncIntentState::AUTO_PULL) {
@@ -249,6 +251,8 @@ void KOReaderSyncActivity::performFetchAndCompare() {
sync.resultPage = remotePosition.pageNumber;
sync.resultParagraphIndex = remotePosition.paragraphIndex;
sync.resultHasParagraphIndex = remotePosition.hasParagraphIndex;
sync.resultListItemIndex = remotePosition.listItemIndex;
sync.resultHasListItemIndex = remotePosition.hasListItemIndex;
APP_STATE.saveToFile();
if (syncIntent == KOReaderSyncIntentState::AUTO_PULL) {
@@ -503,6 +507,8 @@ void KOReaderSyncActivity::resumeReader(const KOReaderSyncOutcomeState outcome,
sync.resultPage = appliedResult->page;
sync.resultParagraphIndex = appliedResult->paragraphIndex;
sync.resultHasParagraphIndex = appliedResult->hasParagraphIndex;
sync.resultListItemIndex = appliedResult->listItemIndex;
sync.resultHasListItemIndex = appliedResult->hasListItemIndex;
} else if (outcome != KOReaderSyncOutcomeState::APPLIED_REMOTE) {
// Only zero the result fields when not resuming an already-applied remote
// position. The PULL_REMOTE path pre-saves the mapped result into APP_STATE
@@ -511,6 +517,8 @@ void KOReaderSyncActivity::resumeReader(const KOReaderSyncOutcomeState outcome,
sync.resultPage = 0;
sync.resultParagraphIndex = 0;
sync.resultHasParagraphIndex = false;
sync.resultListItemIndex = 0;
sync.resultHasListItemIndex = false;
}
// Honor exit-to-home flag set by reader-close auto-sync — bouncing back into the reader
// the user just left would be jarring. The session state is consumed and cleared by the
@@ -792,8 +800,9 @@ void KOReaderSyncActivity::loop() {
return;
}
// Wifi will be turned off in onExit()
const SyncResult result = {remotePosition.spineIndex, remotePosition.pageNumber, remotePosition.paragraphIndex,
remotePosition.hasParagraphIndex};
const SyncResult result = {remotePosition.spineIndex, remotePosition.pageNumber,
remotePosition.paragraphIndex, remotePosition.hasParagraphIndex,
remotePosition.listItemIndex, remotePosition.hasListItemIndex};
resumeReader(KOReaderSyncOutcomeState::APPLIED_REMOTE, &result);
} else if (selectedOption == 1) {
// Upload local progress
+2
View File
@@ -149,6 +149,8 @@ void ReaderActivity::onGoToEpubReader(std::unique_ptr<Epub> epub) {
sync.resultPage = 0;
sync.resultParagraphIndex = 0;
sync.resultHasParagraphIndex = false;
sync.resultListItemIndex = 0;
sync.resultHasListItemIndex = false;
sync.exitToHomeAfterSync = false;
APP_STATE.saveToFile();
// Drop the loaded Epub before TLS — sync activity will reload it for remote-position