fix: free Epub RAM and simplify KOSync navigation via ActivityManager (#1860)
## Summary * **What is the goal of this PR?** Fix KOSync failing with "Network error" on large/complex EPUBs, and simplify the sync navigation flow by removing the callback/result pattern. * **What changes are included?** This PR combines the approaches from #1855 and #1760 into a single, cleaner solution: **Memory fix (from #1855):** - `EpubReaderActivity` pre-computes the local KOReader position and chapter name, then explicitly releases `epub` and `section` before launching `KOReaderSyncActivity`. This frees ~65KB measured on device, giving the TLS handshake sufficient heap. The root cause was `MBEDTLS_ERR_X509_ALLOC_FAILED` (-0x2880) when a 3-cert chain consumed ~48KB during the handshake with only ~50KB available. - `KOReaderSyncActivity` no longer receives a `shared_ptr<Epub>` at construction — it lazy-loads the Epub after TLS only if remote progress is found (`ensureEpubLoaded()`). - Added `MIN_HEAP_FOR_TLS = 55000` guard in `KOReaderSyncClient` — returns `LOW_MEMORY` early if aggregate free heap is too low before attempting a TLS connection. **Navigation simplification (from #1760):** - Replaced `startActivityForResult` + callback with `activityManager.replaceActivity` / `activityManager.goToReader`. Progress is saved to `progress.bin` before the epub is released (cancel/upload paths) and in `saveProgressAndReturn` (apply remote path). The reader re-launches from the saved position naturally via `goToReader`, eliminating the need to reload the epub in a callback. - Extracted `ReaderUtils::saveProgress()` as a shared helper used by both `EpubReaderActivity` and `KOReaderSyncActivity`. - Added `STR_SAVE_PROGRESS_FAILED` to all 22 language files for the case where writing the synced position to SD fails. **Orientation fix (found during device testing):** - `EpubReaderActivity::onExit()` resets the renderer to portrait before destruction. With `replaceActivity` the reader is fully torn down before KOSync starts, so KOSync was always rendering in portrait even when reading in landscape. Fixed by calling `ReaderUtils::applyOrientation` in `KOReaderSyncActivity::onEnter()`. ## Additional Context Heap measurements on device (large EPUB with complex CSS): | Metric | Before | After | |---|---|---| | Heap before Epub release | 88,156 bytes | — | | Heap after Epub release | — | 153,892 bytes (+65,736) | | Heap at TLS handshake | ~50,000 bytes (fails) | ~116,384 bytes (passes) | | Min-free-ever during sync session | 2,600 bytes | 33,052 bytes | | TLS result | `MBEDTLS_ERR_X509_ALLOC_FAILED` | HTTP 200 | Tested on device: sync from inside a large EPUB in both portrait and landscape, cancel, apply remote progress, upload local progress. --- ### AI Usage Did you use AI tools to help write this code? _**YES**_ --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
7993b2bb97
commit
e64155ed63
@@ -18,9 +18,11 @@
|
||||
#include "EpubReaderChapterSelectionActivity.h"
|
||||
#include "EpubReaderFootnotesActivity.h"
|
||||
#include "EpubReaderPercentSelectionActivity.h"
|
||||
#include "EpubReaderUtils.h"
|
||||
#include "KOReaderCredentialStore.h"
|
||||
#include "KOReaderSyncActivity.h"
|
||||
#include "MappedInputManager.h"
|
||||
#include "ProgressMapper.h"
|
||||
#include "QrDisplayActivity.h"
|
||||
#include "ReaderUtils.h"
|
||||
#include "RecentBooksStore.h"
|
||||
@@ -392,7 +394,9 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
|
||||
section.reset();
|
||||
epub->clearCache();
|
||||
epub->setupCacheDir();
|
||||
saveProgress(backupSpine, backupPage, backupPageCount);
|
||||
if (!saveProgress(backupSpine, backupPage, backupPageCount)) {
|
||||
LOG_ERR("ERS", "Failed to save progress before cache clear");
|
||||
}
|
||||
}
|
||||
}
|
||||
onGoHome();
|
||||
@@ -418,23 +422,42 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
|
||||
paragraphIndex = *pIdx;
|
||||
}
|
||||
}
|
||||
startActivityForResult(
|
||||
std::make_unique<KOReaderSyncActivity>(renderer, mappedInput, epub, epub->getPath(), currentSpineIndex,
|
||||
currentPage, totalPages, paragraphIndex),
|
||||
[this](const ActivityResult& result) {
|
||||
if (!result.isCancelled) {
|
||||
const auto& sync = std::get<SyncResult>(result.data);
|
||||
if (currentSpineIndex != sync.spineIndex || (section && section->currentPage != sync.page)) {
|
||||
RenderLock lock(*this);
|
||||
currentSpineIndex = sync.spineIndex;
|
||||
nextPageNumber = sync.page;
|
||||
cachedChapterTotalPageCount = 0; // Prevent rescaling sync page
|
||||
pendingPageJump.reset();
|
||||
saveProgress(currentSpineIndex, nextPageNumber, 0);
|
||||
section.reset();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Pre-compute local KO position and chapter name while Epub is still in RAM.
|
||||
CrossPointPosition localPos = {currentSpineIndex, currentPage, totalPages};
|
||||
if (paragraphIndex.has_value()) {
|
||||
localPos.paragraphIndex = *paragraphIndex;
|
||||
localPos.hasParagraphIndex = true;
|
||||
}
|
||||
KOReaderPosition localKoPos = ProgressMapper::toKOReader(epub, localPos);
|
||||
const int tocIdx = epub->getTocIndexForSpineIndex(currentSpineIndex);
|
||||
std::string localChapterName = (tocIdx >= 0) ? epub->getTocItem(tocIdx).title : "";
|
||||
const std::string savedEpubPath = epub->getPath();
|
||||
|
||||
// Persist current position so the reader resumes at the right page on return.
|
||||
// goToReader() depends on this file, so abort the sync if the write fails.
|
||||
if (!saveProgress(currentSpineIndex, currentPage, totalPages)) {
|
||||
LOG_ERR("KOSync", "Aborting sync because current progress could not be saved");
|
||||
pendingSyncSaveError = true;
|
||||
requestUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
// Release Epub and Section to free ~65KB RAM for the TLS handshake.
|
||||
LOG_DBG("KOSync", "Releasing epub for sync (heap before: %u)", (unsigned)ESP.getFreeHeap());
|
||||
{
|
||||
RenderLock lock(*this);
|
||||
if (section) {
|
||||
nextPageNumber = section->currentPage;
|
||||
}
|
||||
section.reset();
|
||||
epub.reset();
|
||||
}
|
||||
LOG_DBG("KOSync", "Epub released (heap after: %u)", (unsigned)ESP.getFreeHeap());
|
||||
|
||||
activityManager.replaceActivity(std::make_unique<KOReaderSyncActivity>(
|
||||
renderer, mappedInput, savedEpubPath, currentSpineIndex, currentPage, totalPages, std::move(localKoPos),
|
||||
std::move(localChapterName), paragraphIndex));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -530,6 +553,12 @@ void EpubReaderActivity::render(RenderLock&& lock) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto showPendingSyncSaveError = [this]() {
|
||||
if (!pendingSyncSaveError) return;
|
||||
pendingSyncSaveError = false;
|
||||
GUI.drawPopup(renderer, tr(STR_SAVE_PROGRESS_FAILED));
|
||||
};
|
||||
|
||||
// edge case handling for sub-zero spine index
|
||||
if (currentSpineIndex < 0) {
|
||||
currentSpineIndex = 0;
|
||||
@@ -545,6 +574,7 @@ void EpubReaderActivity::render(RenderLock&& lock) {
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 300, tr(STR_END_OF_BOOK), true, EpdFontFamily::BOLD);
|
||||
renderer.displayBuffer();
|
||||
automaticPageTurnActive = false;
|
||||
showPendingSyncSaveError();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -592,6 +622,7 @@ void EpubReaderActivity::render(RenderLock&& lock) {
|
||||
SETTINGS.imageRendering, popupFn)) {
|
||||
LOG_ERR("ERS", "Failed to persist page data to SD");
|
||||
section.reset();
|
||||
showPendingSyncSaveError();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
@@ -655,6 +686,7 @@ void EpubReaderActivity::render(RenderLock&& lock) {
|
||||
renderStatusBar();
|
||||
renderer.displayBuffer();
|
||||
automaticPageTurnActive = false;
|
||||
showPendingSyncSaveError();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -664,6 +696,7 @@ void EpubReaderActivity::render(RenderLock&& lock) {
|
||||
renderStatusBar();
|
||||
renderer.displayBuffer();
|
||||
automaticPageTurnActive = false;
|
||||
showPendingSyncSaveError();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -676,6 +709,7 @@ void EpubReaderActivity::render(RenderLock&& lock) {
|
||||
requestUpdate(); // Try again after clearing cache
|
||||
// TODO: prevent infinite loop if the page keeps failing to load for some reason
|
||||
automaticPageTurnActive = false;
|
||||
showPendingSyncSaveError();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -689,6 +723,8 @@ void EpubReaderActivity::render(RenderLock&& lock) {
|
||||
silentIndexNextChapterIfNeeded(viewportWidth, viewportHeight);
|
||||
saveProgress(currentSpineIndex, section->currentPage, section->pageCount);
|
||||
|
||||
showPendingSyncSaveError();
|
||||
|
||||
if (pendingScreenshot) {
|
||||
pendingScreenshot = false;
|
||||
ScreenshotUtil::takeScreenshot(renderer);
|
||||
@@ -727,21 +763,8 @@ void EpubReaderActivity::silentIndexNextChapterIfNeeded(const uint16_t viewportW
|
||||
}
|
||||
}
|
||||
|
||||
void EpubReaderActivity::saveProgress(int spineIndex, int currentPage, int pageCount) {
|
||||
FsFile f;
|
||||
if (Storage.openFileForWrite("ERS", epub->getCachePath() + "/progress.bin", f)) {
|
||||
uint8_t data[6];
|
||||
data[0] = currentSpineIndex & 0xFF;
|
||||
data[1] = (currentSpineIndex >> 8) & 0xFF;
|
||||
data[2] = currentPage & 0xFF;
|
||||
data[3] = (currentPage >> 8) & 0xFF;
|
||||
data[4] = pageCount & 0xFF;
|
||||
data[5] = (pageCount >> 8) & 0xFF;
|
||||
f.write(data, 6);
|
||||
LOG_DBG("ERS", "Progress saved: Chapter %d, Page %d", spineIndex, currentPage);
|
||||
} else {
|
||||
LOG_ERR("ERS", "Could not save progress!");
|
||||
}
|
||||
bool EpubReaderActivity::saveProgress(int spineIndex, int currentPage, int pageCount) {
|
||||
return EpubReaderUtils::saveProgress(*epub, spineIndex, currentPage, pageCount);
|
||||
}
|
||||
void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int orientedMarginTop,
|
||||
const int orientedMarginRight, const int orientedMarginBottom,
|
||||
|
||||
Reference in New Issue
Block a user