Attempt at closing reader and sync
This commit is contained in:
@@ -32,6 +32,30 @@ esp_http_client_handle_t g_sessionClient = nullptr;
|
|||||||
// numbers, and HTTP status. Single-threaded sync flow makes static safe.
|
// numbers, and HTTP status. Single-threaded sync flow makes static safe.
|
||||||
char g_failureDetailBuf[160] = {0};
|
char g_failureDetailBuf[160] = {0};
|
||||||
|
|
||||||
|
std::string previewBody(const char* body, const size_t maxLen = 120) {
|
||||||
|
if (!body || !*body) {
|
||||||
|
return "<empty>";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string preview;
|
||||||
|
preview.reserve(maxLen);
|
||||||
|
for (const char* p = body; *p && preview.size() < maxLen; ++p) {
|
||||||
|
const unsigned char c = static_cast<unsigned char>(*p);
|
||||||
|
if (c == '\r' || c == '\n' || c == '\t') {
|
||||||
|
preview.push_back(' ');
|
||||||
|
} else if (std::isprint(c)) {
|
||||||
|
preview.push_back(static_cast<char>(c));
|
||||||
|
} else {
|
||||||
|
preview.push_back('?');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen(body) > preview.size()) {
|
||||||
|
preview += "...";
|
||||||
|
}
|
||||||
|
return preview;
|
||||||
|
}
|
||||||
|
|
||||||
// Reset the static diagnostic state at the start of each request and capture pre-flight
|
// Reset the static diagnostic state at the start of each request and capture pre-flight
|
||||||
// heap so failure reporting always reflects what was available when the request started.
|
// heap so failure reporting always reflects what was available when the request started.
|
||||||
void beginRequest(const char* operation) {
|
void beginRequest(const char* operation) {
|
||||||
@@ -447,7 +471,12 @@ KOReaderSyncClient::Error KOReaderSyncClient::getProgress(const std::string& doc
|
|||||||
esp_http_client_cleanup(client);
|
esp_http_client_cleanup(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_DBG("KOSync", "Get progress response: %d (err: %s) [attempt %d]", httpCode, esp_err_to_name(err), attempt);
|
const size_t bodyLen = activeBuf->data ? strlen(activeBuf->data) : 0;
|
||||||
|
LOG_DBG("KOSync", "GET %s -> %d (err: %s) [attempt %d body_len=%u]", url.c_str(), httpCode, esp_err_to_name(err),
|
||||||
|
attempt, static_cast<unsigned>(bodyLen));
|
||||||
|
if (err == ESP_OK && (httpCode < 200 || httpCode >= 300)) {
|
||||||
|
LOG_ERR("KOSync", "GET failure body preview: %s", previewBody(activeBuf->data).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
// Retry exactly once for connect-level failures only.
|
// Retry exactly once for connect-level failures only.
|
||||||
// Why: this recovers short AP/roaming hiccups without masking persistent
|
// Why: this recovers short AP/roaming hiccups without masking persistent
|
||||||
@@ -497,7 +526,10 @@ KOReaderSyncClient::Error KOReaderSyncClient::getProgress(const std::string& doc
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (httpCode == 401) return AUTH_FAILED;
|
if (httpCode == 401) return AUTH_FAILED;
|
||||||
if (httpCode == 404) return NOT_FOUND;
|
if (httpCode == 404) {
|
||||||
|
LOG_DBG("KOSync", "GET progress returned 404 for %s - treating as NOT_FOUND", url.c_str());
|
||||||
|
return NOT_FOUND;
|
||||||
|
}
|
||||||
return SERVER_ERROR;
|
return SERVER_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -561,7 +593,14 @@ KOReaderSyncClient::Error KOReaderSyncClient::updateProgress(const KOReaderProgr
|
|||||||
esp_http_client_cleanup(client);
|
esp_http_client_cleanup(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_DBG("KOSync", "Update progress response: %d (err: %s) [attempt %d]", httpCode, esp_err_to_name(err), attempt);
|
const size_t bodyLen = activeBuf->data ? strlen(activeBuf->data) : 0;
|
||||||
|
LOG_DBG("KOSync", "PUT %s -> %d (err: %s) [attempt %d body_len=%u]", url.c_str(), httpCode, esp_err_to_name(err),
|
||||||
|
attempt, static_cast<unsigned>(bodyLen));
|
||||||
|
if (err == ESP_OK && (httpCode < 200 || httpCode >= 300)) {
|
||||||
|
LOG_ERR("KOSync", "PUT failure body preview: %s", previewBody(activeBuf->data).c_str());
|
||||||
|
LOG_ERR("KOSync", "PUT failure request summary: document=%s percentage=%.4f progress=%s", progress.document.c_str(),
|
||||||
|
progress.percentage, progress.progress.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
// Retry exactly once for connect-level failures only.
|
// Retry exactly once for connect-level failures only.
|
||||||
// Why: same policy as GET keeps behavior predictable across both endpoints.
|
// Why: same policy as GET keeps behavior predictable across both endpoints.
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ bool CrossPointState::loadFromBinaryFile() {
|
|||||||
lastSleepFromReader = false;
|
lastSleepFromReader = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
koReaderSyncSession.clear();
|
||||||
|
|
||||||
inputFile.close();
|
inputFile.close();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,53 @@
|
|||||||
#include <iosfwd>
|
#include <iosfwd>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
enum class KOReaderSyncIntentState : uint8_t {
|
||||||
|
COMPARE = 0,
|
||||||
|
PULL_REMOTE = 1,
|
||||||
|
PUSH_LOCAL = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class KOReaderSyncOutcomeState : uint8_t {
|
||||||
|
NONE = 0,
|
||||||
|
PENDING = 1,
|
||||||
|
CANCELLED = 2,
|
||||||
|
FAILED = 3,
|
||||||
|
UPLOAD_COMPLETE = 4,
|
||||||
|
APPLIED_REMOTE = 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct KOReaderSyncSessionState {
|
||||||
|
bool active = false;
|
||||||
|
std::string epubPath;
|
||||||
|
int spineIndex = 0;
|
||||||
|
int page = 0;
|
||||||
|
int totalPagesInSpine = 0;
|
||||||
|
uint16_t paragraphIndex = 0;
|
||||||
|
bool hasParagraphIndex = false;
|
||||||
|
KOReaderSyncIntentState intent = KOReaderSyncIntentState::COMPARE;
|
||||||
|
KOReaderSyncOutcomeState outcome = KOReaderSyncOutcomeState::NONE;
|
||||||
|
int resultSpineIndex = 0;
|
||||||
|
int resultPage = 0;
|
||||||
|
uint16_t resultParagraphIndex = 0;
|
||||||
|
bool resultHasParagraphIndex = false;
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
active = false;
|
||||||
|
epubPath.clear();
|
||||||
|
spineIndex = 0;
|
||||||
|
page = 0;
|
||||||
|
totalPagesInSpine = 0;
|
||||||
|
paragraphIndex = 0;
|
||||||
|
hasParagraphIndex = false;
|
||||||
|
intent = KOReaderSyncIntentState::COMPARE;
|
||||||
|
outcome = KOReaderSyncOutcomeState::NONE;
|
||||||
|
resultSpineIndex = 0;
|
||||||
|
resultPage = 0;
|
||||||
|
resultParagraphIndex = 0;
|
||||||
|
resultHasParagraphIndex = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class CrossPointState {
|
class CrossPointState {
|
||||||
// Static instance
|
// Static instance
|
||||||
static CrossPointState instance;
|
static CrossPointState instance;
|
||||||
@@ -12,6 +59,7 @@ class CrossPointState {
|
|||||||
size_t lastSleepImage = SIZE_MAX; // SIZE_MAX = unset sentinel
|
size_t lastSleepImage = SIZE_MAX; // SIZE_MAX = unset sentinel
|
||||||
uint8_t readerActivityLoadCount = 0;
|
uint8_t readerActivityLoadCount = 0;
|
||||||
bool lastSleepFromReader = false;
|
bool lastSleepFromReader = false;
|
||||||
|
KOReaderSyncSessionState koReaderSyncSession;
|
||||||
~CrossPointState() = default;
|
~CrossPointState() = default;
|
||||||
|
|
||||||
// Get singleton instance
|
// Get singleton instance
|
||||||
|
|||||||
@@ -73,6 +73,20 @@ bool JsonSettingsIO::saveState(const CrossPointState& s, const char* path) {
|
|||||||
doc["lastSleepImage"] = s.lastSleepImage;
|
doc["lastSleepImage"] = s.lastSleepImage;
|
||||||
doc["readerActivityLoadCount"] = s.readerActivityLoadCount;
|
doc["readerActivityLoadCount"] = s.readerActivityLoadCount;
|
||||||
doc["lastSleepFromReader"] = s.lastSleepFromReader;
|
doc["lastSleepFromReader"] = s.lastSleepFromReader;
|
||||||
|
JsonObject sync = doc["koReaderSyncSession"].to<JsonObject>();
|
||||||
|
sync["active"] = s.koReaderSyncSession.active;
|
||||||
|
sync["epubPath"] = s.koReaderSyncSession.epubPath;
|
||||||
|
sync["spineIndex"] = s.koReaderSyncSession.spineIndex;
|
||||||
|
sync["page"] = s.koReaderSyncSession.page;
|
||||||
|
sync["totalPagesInSpine"] = s.koReaderSyncSession.totalPagesInSpine;
|
||||||
|
sync["paragraphIndex"] = s.koReaderSyncSession.paragraphIndex;
|
||||||
|
sync["hasParagraphIndex"] = s.koReaderSyncSession.hasParagraphIndex;
|
||||||
|
sync["intent"] = static_cast<uint8_t>(s.koReaderSyncSession.intent);
|
||||||
|
sync["outcome"] = static_cast<uint8_t>(s.koReaderSyncSession.outcome);
|
||||||
|
sync["resultSpineIndex"] = s.koReaderSyncSession.resultSpineIndex;
|
||||||
|
sync["resultPage"] = s.koReaderSyncSession.resultPage;
|
||||||
|
sync["resultParagraphIndex"] = s.koReaderSyncSession.resultParagraphIndex;
|
||||||
|
sync["resultHasParagraphIndex"] = s.koReaderSyncSession.resultHasParagraphIndex;
|
||||||
|
|
||||||
String json;
|
String json;
|
||||||
serializeJson(doc, json);
|
serializeJson(doc, json);
|
||||||
@@ -91,6 +105,22 @@ bool JsonSettingsIO::loadState(CrossPointState& s, const char* json) {
|
|||||||
s.lastSleepImage = doc["lastSleepImage"] | SIZE_MAX;
|
s.lastSleepImage = doc["lastSleepImage"] | SIZE_MAX;
|
||||||
s.readerActivityLoadCount = doc["readerActivityLoadCount"] | (uint8_t)0;
|
s.readerActivityLoadCount = doc["readerActivityLoadCount"] | (uint8_t)0;
|
||||||
s.lastSleepFromReader = doc["lastSleepFromReader"] | false;
|
s.lastSleepFromReader = doc["lastSleepFromReader"] | false;
|
||||||
|
JsonObject sync = doc["koReaderSyncSession"].as<JsonObject>();
|
||||||
|
s.koReaderSyncSession.active = sync["active"] | false;
|
||||||
|
s.koReaderSyncSession.epubPath = sync["epubPath"] | std::string("");
|
||||||
|
s.koReaderSyncSession.spineIndex = sync["spineIndex"] | 0;
|
||||||
|
s.koReaderSyncSession.page = sync["page"] | 0;
|
||||||
|
s.koReaderSyncSession.totalPagesInSpine = sync["totalPagesInSpine"] | 0;
|
||||||
|
s.koReaderSyncSession.paragraphIndex = sync["paragraphIndex"] | (uint16_t)0;
|
||||||
|
s.koReaderSyncSession.hasParagraphIndex = sync["hasParagraphIndex"] | false;
|
||||||
|
s.koReaderSyncSession.intent =
|
||||||
|
static_cast<KOReaderSyncIntentState>(sync["intent"] | static_cast<uint8_t>(KOReaderSyncIntentState::COMPARE));
|
||||||
|
s.koReaderSyncSession.outcome =
|
||||||
|
static_cast<KOReaderSyncOutcomeState>(sync["outcome"] | static_cast<uint8_t>(KOReaderSyncOutcomeState::NONE));
|
||||||
|
s.koReaderSyncSession.resultSpineIndex = sync["resultSpineIndex"] | 0;
|
||||||
|
s.koReaderSyncSession.resultPage = sync["resultPage"] | 0;
|
||||||
|
s.koReaderSyncSession.resultParagraphIndex = sync["resultParagraphIndex"] | (uint16_t)0;
|
||||||
|
s.koReaderSyncSession.resultHasParagraphIndex = sync["resultHasParagraphIndex"] | false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <HalClock.h>
|
#include <HalClock.h>
|
||||||
#include <HalPowerManager.h>
|
#include <HalPowerManager.h>
|
||||||
|
|
||||||
|
#include "CrossPointState.h"
|
||||||
#include "boot_sleep/BootActivity.h"
|
#include "boot_sleep/BootActivity.h"
|
||||||
#include "boot_sleep/SleepActivity.h"
|
#include "boot_sleep/SleepActivity.h"
|
||||||
#include "browser/OpdsBookBrowserActivity.h"
|
#include "browser/OpdsBookBrowserActivity.h"
|
||||||
@@ -11,6 +12,7 @@
|
|||||||
#include "home/HomeActivity.h"
|
#include "home/HomeActivity.h"
|
||||||
#include "home/RecentBooksActivity.h"
|
#include "home/RecentBooksActivity.h"
|
||||||
#include "network/CrossPointWebServerActivity.h"
|
#include "network/CrossPointWebServerActivity.h"
|
||||||
|
#include "reader/KOReaderSyncActivity.h"
|
||||||
#include "reader/ReaderActivity.h"
|
#include "reader/ReaderActivity.h"
|
||||||
#include "settings/SettingsActivity.h"
|
#include "settings/SettingsActivity.h"
|
||||||
#include "util/FullScreenMessageActivity.h"
|
#include "util/FullScreenMessageActivity.h"
|
||||||
@@ -227,6 +229,19 @@ void ActivityManager::goToReader(std::string path) {
|
|||||||
replaceActivity(std::make_unique<ReaderActivity>(renderer, mappedInput, std::move(path)));
|
replaceActivity(std::make_unique<ReaderActivity>(renderer, mappedInput, std::move(path)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ActivityManager::goToKOReaderSync() {
|
||||||
|
const auto& sync = APP_STATE.koReaderSyncSession;
|
||||||
|
if (!sync.active || sync.epubPath.empty()) {
|
||||||
|
LOG_ERR("ACT", "Cannot launch KOReader sync without an active EPUB handoff");
|
||||||
|
goHome();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceActivity(std::make_unique<KOReaderSyncActivity>(renderer, mappedInput, sync.epubPath, sync.spineIndex,
|
||||||
|
sync.page, sync.totalPagesInSpine, sync.paragraphIndex,
|
||||||
|
sync.hasParagraphIndex, sync.intent));
|
||||||
|
}
|
||||||
|
|
||||||
void ActivityManager::pushReader(std::string path) {
|
void ActivityManager::pushReader(std::string path) {
|
||||||
pushActivity(std::make_unique<ReaderActivity>(renderer, mappedInput, std::move(path)));
|
pushActivity(std::make_unique<ReaderActivity>(renderer, mappedInput, std::move(path)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ class ActivityManager {
|
|||||||
void goToRecentBooks();
|
void goToRecentBooks();
|
||||||
void goToBrowser();
|
void goToBrowser();
|
||||||
void goToReader(std::string path);
|
void goToReader(std::string path);
|
||||||
|
void goToKOReaderSync();
|
||||||
void pushReader(std::string path);
|
void pushReader(std::string path);
|
||||||
void goToSleep();
|
void goToSleep();
|
||||||
void goToBoot();
|
void goToBoot();
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <HalStorage.h>
|
#include <HalStorage.h>
|
||||||
#include <I18n.h>
|
#include <I18n.h>
|
||||||
#include <Logging.h>
|
#include <Logging.h>
|
||||||
|
#include <esp_heap_caps.h>
|
||||||
#include <esp_system.h>
|
#include <esp_system.h>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -18,7 +19,6 @@
|
|||||||
#include "EpubReaderFootnotesActivity.h"
|
#include "EpubReaderFootnotesActivity.h"
|
||||||
#include "EpubReaderPercentSelectionActivity.h"
|
#include "EpubReaderPercentSelectionActivity.h"
|
||||||
#include "KOReaderCredentialStore.h"
|
#include "KOReaderCredentialStore.h"
|
||||||
#include "KOReaderSyncActivity.h"
|
|
||||||
#include "MappedInputManager.h"
|
#include "MappedInputManager.h"
|
||||||
#include "QrDisplayActivity.h"
|
#include "QrDisplayActivity.h"
|
||||||
#include "ReaderUtils.h"
|
#include "ReaderUtils.h"
|
||||||
@@ -33,6 +33,31 @@ constexpr unsigned long skipChapterMs = 700;
|
|||||||
// pages per minute, first item is 1 to prevent division by zero if accessed
|
// pages per minute, first item is 1 to prevent division by zero if accessed
|
||||||
const std::vector<int> PAGE_TURN_LABELS = {1, 1, 3, 6, 12};
|
const std::vector<int> PAGE_TURN_LABELS = {1, 1, 3, 6, 12};
|
||||||
|
|
||||||
|
void logReaderMemSnapshot(const char* stage) {
|
||||||
|
const uint32_t freeHeap = esp_get_free_heap_size();
|
||||||
|
const uint32_t contigHeap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | MALLOC_CAP_DEFAULT);
|
||||||
|
LOG_DBG("ERS", "Reader mem[%s]: free=%lu contig=%lu", stage, freeHeap, contigHeap);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool writeReaderProgressCache(const std::string& cachePath, const int spineIndex, const int currentPage, const int pageCount) {
|
||||||
|
FsFile f;
|
||||||
|
if (!Storage.openFileForWrite("ERS", cachePath + "/progress.bin", f)) {
|
||||||
|
LOG_ERR("ERS", "Failed to open progress cache for sync restore: %s", cachePath.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t data[6];
|
||||||
|
data[0] = spineIndex & 0xFF;
|
||||||
|
data[1] = (spineIndex >> 8) & 0xFF;
|
||||||
|
data[2] = currentPage & 0xFF;
|
||||||
|
data[3] = (currentPage >> 8) & 0xFF;
|
||||||
|
data[4] = pageCount & 0xFF;
|
||||||
|
data[5] = (pageCount >> 8) & 0xFF;
|
||||||
|
f.write(data, 6);
|
||||||
|
f.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
int clampPercent(int percent) {
|
int clampPercent(int percent) {
|
||||||
if (percent < 0) {
|
if (percent < 0) {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -47,6 +72,7 @@ int clampPercent(int percent) {
|
|||||||
|
|
||||||
void EpubReaderActivity::onEnter() {
|
void EpubReaderActivity::onEnter() {
|
||||||
Activity::onEnter();
|
Activity::onEnter();
|
||||||
|
logReaderMemSnapshot("onEnter_begin");
|
||||||
|
|
||||||
// Drop any input events that arrived from the activity that launched us (e.g. a wake-up power
|
// Drop any input events that arrived from the activity that launched us (e.g. a wake-up power
|
||||||
// button hold) before they reach detectPageTurn() — see ReaderUtils::InputDrainGuard.
|
// button hold) before they reach detectPageTurn() — see ReaderUtils::InputDrainGuard.
|
||||||
@@ -64,6 +90,7 @@ void EpubReaderActivity::onEnter() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
epub->setupCacheDir();
|
epub->setupCacheDir();
|
||||||
|
applyPendingSyncSession();
|
||||||
|
|
||||||
FsFile f;
|
FsFile f;
|
||||||
if (Storage.openFileForRead("ERS", epub->getCachePath() + "/progress.bin", f)) {
|
if (Storage.openFileForRead("ERS", epub->getCachePath() + "/progress.bin", f)) {
|
||||||
@@ -104,10 +131,12 @@ void EpubReaderActivity::onEnter() {
|
|||||||
|
|
||||||
// Trigger first update
|
// Trigger first update
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
|
logReaderMemSnapshot("onEnter_ready");
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderActivity::onExit() {
|
void EpubReaderActivity::onExit() {
|
||||||
Activity::onExit();
|
Activity::onExit();
|
||||||
|
logReaderMemSnapshot("onExit_before_release");
|
||||||
|
|
||||||
// Reset orientation back to portrait for the rest of the UI
|
// Reset orientation back to portrait for the rest of the UI
|
||||||
renderer.setOrientation(GfxRenderer::Orientation::Portrait);
|
renderer.setOrientation(GfxRenderer::Orientation::Portrait);
|
||||||
@@ -116,6 +145,9 @@ void EpubReaderActivity::onExit() {
|
|||||||
APP_STATE.saveToFile();
|
APP_STATE.saveToFile();
|
||||||
section.reset();
|
section.reset();
|
||||||
epub.reset();
|
epub.reset();
|
||||||
|
currentPageFootnotes.clear();
|
||||||
|
currentPageFootnotes.shrink_to_fit();
|
||||||
|
logReaderMemSnapshot("onExit_after_release");
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderActivity::loop() {
|
void EpubReaderActivity::loop() {
|
||||||
@@ -498,70 +530,77 @@ void EpubReaderActivity::launchKOReaderSync(const SyncLaunchMode mode) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string syncEpubPath = epub->getPath();
|
|
||||||
const int currentPage = section ? section->currentPage : 0;
|
const int currentPage = section ? section->currentPage : 0;
|
||||||
const int totalPages = section ? section->pageCount : 0;
|
const int totalPages = section ? section->pageCount : 0;
|
||||||
|
KOReaderSyncIntentState syncIntent = KOReaderSyncIntentState::COMPARE;
|
||||||
{
|
|
||||||
// Drop large reader state before TLS-heavy sync to improve contiguous heap
|
|
||||||
// and reduce long-run fragmentation across repeated sync attempts.
|
|
||||||
RenderLock lock(*this);
|
|
||||||
nextPageNumber = currentPage;
|
|
||||||
cachedSpineIndex = currentSpineIndex;
|
|
||||||
cachedChapterTotalPageCount = totalPages;
|
|
||||||
section.reset();
|
|
||||||
epub.reset();
|
|
||||||
currentPageFootnotes.clear();
|
|
||||||
currentPageFootnotes.shrink_to_fit();
|
|
||||||
}
|
|
||||||
deferredSyncEpubPath = syncEpubPath;
|
|
||||||
|
|
||||||
renderer.cleanupGrayscaleWithFrameBuffer();
|
|
||||||
if (auto* cacheManager = renderer.getFontCacheManager()) {
|
|
||||||
cacheManager->clearCache();
|
|
||||||
cacheManager->resetStats();
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DBG("ERS", "Pre-sync trim: spine=%d page=%d/%d heap=%lu", currentSpineIndex, currentPage, totalPages,
|
|
||||||
static_cast<unsigned long>(esp_get_free_heap_size()));
|
|
||||||
|
|
||||||
// Map reader-level launch mode to activity-level intent once, then pass a
|
|
||||||
// stable intent into KOReaderSyncActivity so it can own the sync state machine.
|
|
||||||
KOReaderSyncActivity::SyncIntent syncIntent = KOReaderSyncActivity::SyncIntent::COMPARE;
|
|
||||||
if (mode == SyncLaunchMode::PULL_REMOTE) {
|
if (mode == SyncLaunchMode::PULL_REMOTE) {
|
||||||
syncIntent = KOReaderSyncActivity::SyncIntent::PULL_REMOTE;
|
syncIntent = KOReaderSyncIntentState::PULL_REMOTE;
|
||||||
} else if (mode == SyncLaunchMode::PUSH_LOCAL) {
|
} else if (mode == SyncLaunchMode::PUSH_LOCAL) {
|
||||||
syncIntent = KOReaderSyncActivity::SyncIntent::PUSH_LOCAL;
|
syncIntent = KOReaderSyncIntentState::PUSH_LOCAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
startActivityForResult(
|
auto& sync = APP_STATE.koReaderSyncSession;
|
||||||
std::make_unique<KOReaderSyncActivity>(renderer, mappedInput, std::shared_ptr<Epub>{}, syncEpubPath,
|
sync.active = true;
|
||||||
currentSpineIndex, currentPage, totalPages, 0, false, syncIntent),
|
sync.epubPath = epub->getPath();
|
||||||
[this](const ActivityResult& result) { handleSyncResult(result); });
|
sync.spineIndex = currentSpineIndex;
|
||||||
|
sync.page = currentPage;
|
||||||
|
sync.totalPagesInSpine = totalPages;
|
||||||
|
sync.paragraphIndex = 0;
|
||||||
|
sync.hasParagraphIndex = false;
|
||||||
|
sync.intent = syncIntent;
|
||||||
|
sync.outcome = KOReaderSyncOutcomeState::PENDING;
|
||||||
|
sync.resultSpineIndex = 0;
|
||||||
|
sync.resultPage = 0;
|
||||||
|
sync.resultParagraphIndex = 0;
|
||||||
|
sync.resultHasParagraphIndex = false;
|
||||||
|
APP_STATE.saveToFile();
|
||||||
|
|
||||||
|
LOG_DBG("ERS", "Standalone sync handoff: spine=%d page=%d/%d", currentSpineIndex, currentPage, totalPages);
|
||||||
|
logReaderMemSnapshot("before_replace_with_sync");
|
||||||
|
activityManager.goToKOReaderSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderActivity::handleSyncResult(const ActivityResult& result) {
|
void EpubReaderActivity::applyPendingSyncSession() {
|
||||||
if (!epub && !deferredSyncEpubPath.empty()) {
|
auto& sync = APP_STATE.koReaderSyncSession;
|
||||||
epub = std::make_shared<Epub>(deferredSyncEpubPath, "/.crosspoint");
|
if (!sync.active || !epub || sync.epubPath != epub->getPath()) {
|
||||||
if (!epub->load(true, true)) {
|
return;
|
||||||
LOG_ERR("ERS", "Failed to reload EPUB after sync: %s", deferredSyncEpubPath.c_str());
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
epub->setupCacheDir();
|
|
||||||
LOG_DBG("ERS", "Reloaded EPUB after sync: %s", deferredSyncEpubPath.c_str());
|
|
||||||
deferredSyncEpubPath.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!result.isCancelled) {
|
LOG_DBG("ERS", "Applying pending sync session outcome=%d path=%s", static_cast<int>(sync.outcome), sync.epubPath.c_str());
|
||||||
const auto& sync = std::get<SyncResult>(result.data);
|
|
||||||
if (currentSpineIndex != sync.spineIndex || (section && section->currentPage != sync.page)) {
|
int restoreSpineIndex = sync.spineIndex;
|
||||||
RenderLock lock(*this);
|
int restorePage = sync.page;
|
||||||
currentSpineIndex = sync.spineIndex;
|
pendingParagraphLookup = sync.hasParagraphIndex;
|
||||||
nextPageNumber = sync.page;
|
pendingParagraphIndex = sync.paragraphIndex;
|
||||||
section.reset();
|
|
||||||
}
|
if (sync.outcome == KOReaderSyncOutcomeState::APPLIED_REMOTE) {
|
||||||
|
restoreSpineIndex = sync.resultSpineIndex;
|
||||||
|
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");
|
||||||
|
} else {
|
||||||
|
LOG_DBG("ERS", "Restored local pre-sync position: spine=%d page=%d paragraph=%u hasParagraph=%s", restoreSpineIndex,
|
||||||
|
restorePage, pendingParagraphIndex, pendingParagraphLookup ? "yes" : "no");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (writeReaderProgressCache(epub->getCachePath(), restoreSpineIndex, restorePage, sync.totalPagesInSpine)) {
|
||||||
|
cachedSpineIndex = restoreSpineIndex;
|
||||||
|
cachedChapterTotalPageCount = sync.totalPagesInSpine;
|
||||||
|
LOG_DBG("ERS", "Prepared progress.bin for sync restore: spine=%d page=%d/%d", restoreSpineIndex, restorePage,
|
||||||
|
sync.totalPagesInSpine);
|
||||||
|
} else {
|
||||||
|
// Fall back to directly seeding live state if cache write fails.
|
||||||
|
currentSpineIndex = restoreSpineIndex;
|
||||||
|
nextPageNumber = restorePage;
|
||||||
|
cachedSpineIndex = restoreSpineIndex;
|
||||||
|
cachedChapterTotalPageCount = sync.totalPagesInSpine;
|
||||||
|
}
|
||||||
|
|
||||||
|
sync.clear();
|
||||||
|
APP_STATE.saveToFile();
|
||||||
|
logReaderMemSnapshot("after_apply_pending_sync_session");
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderActivity::applyOrientation(const uint8_t orientation) {
|
void EpubReaderActivity::applyOrientation(const uint8_t orientation) {
|
||||||
@@ -914,8 +953,8 @@ void EpubReaderActivity::saveProgress(int spineIndex, int currentPage, int pageC
|
|||||||
FsFile f;
|
FsFile f;
|
||||||
if (Storage.openFileForWrite("ERS", epub->getCachePath() + "/progress.bin", f)) {
|
if (Storage.openFileForWrite("ERS", epub->getCachePath() + "/progress.bin", f)) {
|
||||||
uint8_t data[6];
|
uint8_t data[6];
|
||||||
data[0] = currentSpineIndex & 0xFF;
|
data[0] = spineIndex & 0xFF;
|
||||||
data[1] = (currentSpineIndex >> 8) & 0xFF;
|
data[1] = (spineIndex >> 8) & 0xFF;
|
||||||
data[2] = currentPage & 0xFF;
|
data[2] = currentPage & 0xFF;
|
||||||
data[3] = (currentPage >> 8) & 0xFF;
|
data[3] = (currentPage >> 8) & 0xFF;
|
||||||
data[4] = pageCount & 0xFF;
|
data[4] = pageCount & 0xFF;
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ class EpubReaderActivity final : public Activity {
|
|||||||
bool skipNextButtonCheck = false; // Skip button processing for one frame after subactivity exit
|
bool skipNextButtonCheck = false; // Skip button processing for one frame after subactivity exit
|
||||||
ReaderUtils::InputDrainGuard inputDrainGuard;
|
ReaderUtils::InputDrainGuard inputDrainGuard;
|
||||||
bool automaticPageTurnActive = false;
|
bool automaticPageTurnActive = false;
|
||||||
std::string deferredSyncEpubPath;
|
|
||||||
// -1 means use global SETTINGS value.
|
// -1 means use global SETTINGS value.
|
||||||
int8_t bookEmbeddedStyleOverride = -1;
|
int8_t bookEmbeddedStyleOverride = -1;
|
||||||
int8_t bookImageRenderingOverride = -1;
|
int8_t bookImageRenderingOverride = -1;
|
||||||
@@ -72,7 +71,7 @@ class EpubReaderActivity final : public Activity {
|
|||||||
void jumpToPercent(int percent);
|
void jumpToPercent(int percent);
|
||||||
void onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction action);
|
void onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction action);
|
||||||
void launchKOReaderSync(SyncLaunchMode mode = SyncLaunchMode::COMPARE);
|
void launchKOReaderSync(SyncLaunchMode mode = SyncLaunchMode::COMPARE);
|
||||||
void handleSyncResult(const ActivityResult& result);
|
void applyPendingSyncSession();
|
||||||
void applyOrientation(uint8_t orientation);
|
void applyOrientation(uint8_t orientation);
|
||||||
void applyTextDarkness(uint8_t textDarkness);
|
void applyTextDarkness(uint8_t textDarkness);
|
||||||
void toggleAutoPageTurn(uint8_t selectedPageTurnOption);
|
void toggleAutoPageTurn(uint8_t selectedPageTurnOption);
|
||||||
|
|||||||
@@ -57,11 +57,8 @@ bool shouldSyncNtpNow() {
|
|||||||
|
|
||||||
void KOReaderSyncActivity::onWifiSelectionComplete(const bool success) {
|
void KOReaderSyncActivity::onWifiSelectionComplete(const bool success) {
|
||||||
if (!success) {
|
if (!success) {
|
||||||
LOG_DBG("KOSync", "WiFi connection failed, exiting");
|
LOG_DBG("KOSync", "WiFi connection failed, resuming reader");
|
||||||
ActivityResult result;
|
resumeReader(KOReaderSyncOutcomeState::CANCELLED);
|
||||||
result.isCancelled = true;
|
|
||||||
setResult(std::move(result));
|
|
||||||
finish();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +115,7 @@ void KOReaderSyncActivity::performSync() {
|
|||||||
|
|
||||||
// Local mapping is only needed for compare/upload paths.
|
// Local mapping is only needed for compare/upload paths.
|
||||||
// Pull-only mode can skip this expensive step and go straight to remote fetch.
|
// Pull-only mode can skip this expensive step and go straight to remote fetch.
|
||||||
if (syncIntent != SyncIntent::PULL_REMOTE) {
|
if (syncIntent != KOReaderSyncIntentState::PULL_REMOTE) {
|
||||||
// Precompute local mapping before first network request so the expensive
|
// Precompute local mapping before first network request so the expensive
|
||||||
// inflate/index work happens before TLS. This avoids a second local mapping
|
// inflate/index work happens before TLS. This avoids a second local mapping
|
||||||
// pass later and keeps the upload path lightweight.
|
// pass later and keeps the upload path lightweight.
|
||||||
@@ -143,7 +140,7 @@ void KOReaderSyncActivity::performSync() {
|
|||||||
|
|
||||||
// Push intent skips comparison UI but still warms an HTTP/TLS session first
|
// Push intent skips comparison UI but still warms an HTTP/TLS session first
|
||||||
// so PUT can reuse the connection instead of forcing a fresh handshake.
|
// so PUT can reuse the connection instead of forcing a fresh handshake.
|
||||||
if (syncIntent == SyncIntent::PUSH_LOCAL) {
|
if (syncIntent == KOReaderSyncIntentState::PUSH_LOCAL) {
|
||||||
// Direct push previously started with no reusable HTTP/TLS session, forcing
|
// Direct push previously started with no reusable HTTP/TLS session, forcing
|
||||||
// a fresh handshake in updateProgress. Compare flow often succeeds because
|
// a fresh handshake in updateProgress. Compare flow often succeeds because
|
||||||
// upload reuses the GET session. Warm the session here so push can take the
|
// upload reuses the GET session. Warm the session here so push can take the
|
||||||
@@ -184,7 +181,7 @@ void KOReaderSyncActivity::performSync() {
|
|||||||
const auto result = KOReaderSyncClient::getProgress(documentHash, remoteProgress);
|
const auto result = KOReaderSyncClient::getProgress(documentHash, remoteProgress);
|
||||||
|
|
||||||
if (result == KOReaderSyncClient::NOT_FOUND) {
|
if (result == KOReaderSyncClient::NOT_FOUND) {
|
||||||
if (syncIntent == SyncIntent::PULL_REMOTE) {
|
if (syncIntent == KOReaderSyncIntentState::PULL_REMOTE) {
|
||||||
// Pull intent must not silently fall back to upload when server has no
|
// Pull intent must not silently fall back to upload when server has no
|
||||||
// remote progress. Failing explicitly keeps action semantics predictable.
|
// remote progress. Failing explicitly keeps action semantics predictable.
|
||||||
KOReaderSyncClient::endPersistentSession();
|
KOReaderSyncClient::endPersistentSession();
|
||||||
@@ -236,7 +233,7 @@ void KOReaderSyncActivity::performSync() {
|
|||||||
remotePosition.hasParagraphIndex = false;
|
remotePosition.hasParagraphIndex = false;
|
||||||
remoteChapterLabel.clear();
|
remoteChapterLabel.clear();
|
||||||
|
|
||||||
if (syncIntent == SyncIntent::PULL_REMOTE) {
|
if (syncIntent == KOReaderSyncIntentState::PULL_REMOTE) {
|
||||||
// Pull intent applies immediately and exits. We bypass chooser UI to keep
|
// Pull intent applies immediately and exits. We bypass chooser UI to keep
|
||||||
// reader menu actions deterministic ("pull" always means apply remote).
|
// reader menu actions deterministic ("pull" always means apply remote).
|
||||||
if (!ensureRemotePositionMapped()) {
|
if (!ensureRemotePositionMapped()) {
|
||||||
@@ -251,8 +248,13 @@ void KOReaderSyncActivity::performSync() {
|
|||||||
|
|
||||||
// Preserve the apply result and show explicit confirmation before returning
|
// Preserve the apply result and show explicit confirmation before returning
|
||||||
// to the reader so users can tell pull succeeded.
|
// to the reader so users can tell pull succeeded.
|
||||||
setResult(SyncResult{remotePosition.spineIndex, remotePosition.pageNumber, remotePosition.paragraphIndex,
|
auto& sync = APP_STATE.koReaderSyncSession;
|
||||||
remotePosition.hasParagraphIndex});
|
sync.outcome = KOReaderSyncOutcomeState::APPLIED_REMOTE;
|
||||||
|
sync.resultSpineIndex = remotePosition.spineIndex;
|
||||||
|
sync.resultPage = remotePosition.pageNumber;
|
||||||
|
sync.resultParagraphIndex = remotePosition.paragraphIndex;
|
||||||
|
sync.resultHasParagraphIndex = remotePosition.hasParagraphIndex;
|
||||||
|
APP_STATE.saveToFile();
|
||||||
{
|
{
|
||||||
RenderLock lock(*this);
|
RenderLock lock(*this);
|
||||||
state = APPLY_COMPLETE;
|
state = APPLY_COMPLETE;
|
||||||
@@ -370,6 +372,8 @@ void KOReaderSyncActivity::performUpload() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
HalClock::wifiOff(true);
|
HalClock::wifiOff(true);
|
||||||
|
APP_STATE.koReaderSyncSession.outcome = KOReaderSyncOutcomeState::UPLOAD_COMPLETE;
|
||||||
|
APP_STATE.saveToFile();
|
||||||
{
|
{
|
||||||
RenderLock lock(*this);
|
RenderLock lock(*this);
|
||||||
state = UPLOAD_COMPLETE;
|
state = UPLOAD_COMPLETE;
|
||||||
@@ -380,6 +384,9 @@ void KOReaderSyncActivity::performUpload() {
|
|||||||
|
|
||||||
void KOReaderSyncActivity::onEnter() {
|
void KOReaderSyncActivity::onEnter() {
|
||||||
Activity::onEnter();
|
Activity::onEnter();
|
||||||
|
logSyncMemSnapshot("onEnter_begin");
|
||||||
|
LOG_DBG("KOSync", "Standalone sync start: path=%s spine=%d page=%d/%d intent=%d", epubPath.c_str(), currentSpineIndex,
|
||||||
|
currentPage, totalPagesInSpine, static_cast<int>(syncIntent));
|
||||||
|
|
||||||
// Check for credentials first
|
// Check for credentials first
|
||||||
if (!KOREADER_STORE.hasCredentials()) {
|
if (!KOREADER_STORE.hasCredentials()) {
|
||||||
@@ -404,8 +411,11 @@ void KOReaderSyncActivity::onEnter() {
|
|||||||
void KOReaderSyncActivity::onExit() {
|
void KOReaderSyncActivity::onExit() {
|
||||||
Activity::onExit();
|
Activity::onExit();
|
||||||
|
|
||||||
|
logSyncMemSnapshot("onExit_before_cleanup");
|
||||||
KOReaderSyncClient::endPersistentSession();
|
KOReaderSyncClient::endPersistentSession();
|
||||||
HalClock::wifiOff(true);
|
HalClock::wifiOff(true);
|
||||||
|
releaseEpubForMapping();
|
||||||
|
logSyncMemSnapshot("onExit_after_cleanup");
|
||||||
}
|
}
|
||||||
|
|
||||||
void KOReaderSyncActivity::closeCancelled() {
|
void KOReaderSyncActivity::closeCancelled() {
|
||||||
@@ -413,11 +423,31 @@ void KOReaderSyncActivity::closeCancelled() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resumeReader(KOReaderSyncOutcomeState::CANCELLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
void KOReaderSyncActivity::resumeReader(const KOReaderSyncOutcomeState outcome, const SyncResult* appliedResult) {
|
||||||
|
if (closeRequested) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
closeRequested = true;
|
closeRequested = true;
|
||||||
ActivityResult result;
|
auto& sync = APP_STATE.koReaderSyncSession;
|
||||||
result.isCancelled = true;
|
sync.outcome = outcome;
|
||||||
setResult(std::move(result));
|
if (appliedResult) {
|
||||||
finish();
|
sync.resultSpineIndex = appliedResult->spineIndex;
|
||||||
|
sync.resultPage = appliedResult->page;
|
||||||
|
sync.resultParagraphIndex = appliedResult->paragraphIndex;
|
||||||
|
sync.resultHasParagraphIndex = appliedResult->hasParagraphIndex;
|
||||||
|
} else {
|
||||||
|
sync.resultSpineIndex = 0;
|
||||||
|
sync.resultPage = 0;
|
||||||
|
sync.resultParagraphIndex = 0;
|
||||||
|
sync.resultHasParagraphIndex = false;
|
||||||
|
}
|
||||||
|
APP_STATE.saveToFile();
|
||||||
|
logSyncMemSnapshot("before_resume_reader");
|
||||||
|
activityManager.goToReader(epubPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
void KOReaderSyncActivity::render(RenderLock&&) {
|
void KOReaderSyncActivity::render(RenderLock&&) {
|
||||||
@@ -626,22 +656,23 @@ void KOReaderSyncActivity::computeRemoteChapter() {
|
|||||||
void KOReaderSyncActivity::loop() {
|
void KOReaderSyncActivity::loop() {
|
||||||
if (state == NO_CREDENTIALS || state == SYNC_FAILED || state == UPLOAD_COMPLETE || state == APPLY_COMPLETE) {
|
if (state == NO_CREDENTIALS || state == SYNC_FAILED || state == UPLOAD_COMPLETE || state == APPLY_COMPLETE) {
|
||||||
if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
||||||
// APPLY_COMPLETE already has a valid SyncResult, so exit normally.
|
|
||||||
// Other terminal states are treated as cancelled when backing out.
|
|
||||||
if (state == APPLY_COMPLETE) {
|
if (state == APPLY_COMPLETE) {
|
||||||
finish();
|
resumeReader(KOReaderSyncOutcomeState::APPLIED_REMOTE);
|
||||||
|
} else if (state == UPLOAD_COMPLETE) {
|
||||||
|
resumeReader(KOReaderSyncOutcomeState::UPLOAD_COMPLETE);
|
||||||
|
} else if (state == SYNC_FAILED || state == NO_CREDENTIALS) {
|
||||||
|
resumeReader(KOReaderSyncOutcomeState::FAILED);
|
||||||
} else {
|
} else {
|
||||||
closeCancelled();
|
resumeReader(KOReaderSyncOutcomeState::CANCELLED);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((state == UPLOAD_COMPLETE || state == APPLY_COMPLETE) && millis() - uploadCompleteTime >= 3000) {
|
if ((state == UPLOAD_COMPLETE || state == APPLY_COMPLETE) && millis() - uploadCompleteTime >= 3000) {
|
||||||
// Keep pull/apply result on auto-close; upload-complete remains cancel-style.
|
|
||||||
if (state == APPLY_COMPLETE) {
|
if (state == APPLY_COMPLETE) {
|
||||||
finish();
|
resumeReader(KOReaderSyncOutcomeState::APPLIED_REMOTE);
|
||||||
} else {
|
} else {
|
||||||
closeCancelled();
|
resumeReader(KOReaderSyncOutcomeState::UPLOAD_COMPLETE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -671,9 +702,9 @@ void KOReaderSyncActivity::loop() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Wifi will be turned off in onExit()
|
// Wifi will be turned off in onExit()
|
||||||
setResult(SyncResult{remotePosition.spineIndex, remotePosition.pageNumber, remotePosition.paragraphIndex,
|
const SyncResult result = {remotePosition.spineIndex, remotePosition.pageNumber, remotePosition.paragraphIndex,
|
||||||
remotePosition.hasParagraphIndex});
|
remotePosition.hasParagraphIndex};
|
||||||
finish();
|
resumeReader(KOReaderSyncOutcomeState::APPLIED_REMOTE, &result);
|
||||||
} else if (selectedOption == 1) {
|
} else if (selectedOption == 1) {
|
||||||
// Upload local progress
|
// Upload local progress
|
||||||
performUpload();
|
performUpload();
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <Epub.h>
|
#include <Epub.h>
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "ChapterXPathIndexer.h"
|
#include "ChapterXPathIndexer.h"
|
||||||
#include "KOReaderSyncClient.h"
|
#include "KOReaderSyncClient.h"
|
||||||
#include "ProgressMapper.h"
|
#include "ProgressMapper.h"
|
||||||
|
#include "CrossPointState.h"
|
||||||
#include "activities/Activity.h"
|
#include "activities/Activity.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -27,24 +27,11 @@
|
|||||||
*/
|
*/
|
||||||
class KOReaderSyncActivity final : public Activity {
|
class KOReaderSyncActivity final : public Activity {
|
||||||
public:
|
public:
|
||||||
// Intent controls UI/behavior split for the same sync pipeline.
|
explicit KOReaderSyncActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::string& epubPath,
|
||||||
// - COMPARE: fetch then let user choose apply/upload.
|
int currentSpineIndex, int currentPage, int totalPagesInSpine,
|
||||||
// - PULL_REMOTE: fetch and apply immediately.
|
uint16_t paragraphIndex = 0, bool hasParagraphIndex = false,
|
||||||
// - PUSH_LOCAL: upload immediately.
|
KOReaderSyncIntentState syncIntent = KOReaderSyncIntentState::COMPARE)
|
||||||
// This keeps WiFi/NTP/hash/memory handling centralized while enabling a
|
|
||||||
// simpler KOReader-like reader menu UX.
|
|
||||||
enum class SyncIntent {
|
|
||||||
COMPARE,
|
|
||||||
PULL_REMOTE,
|
|
||||||
PUSH_LOCAL,
|
|
||||||
};
|
|
||||||
|
|
||||||
explicit KOReaderSyncActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
|
||||||
const std::shared_ptr<Epub>& epub, const std::string& epubPath, int currentSpineIndex,
|
|
||||||
int currentPage, int totalPagesInSpine, uint16_t paragraphIndex = 0,
|
|
||||||
bool hasParagraphIndex = false, SyncIntent syncIntent = SyncIntent::COMPARE)
|
|
||||||
: Activity("KOReaderSync", renderer, mappedInput),
|
: Activity("KOReaderSync", renderer, mappedInput),
|
||||||
epub(epub),
|
|
||||||
epubPath(epubPath),
|
epubPath(epubPath),
|
||||||
currentSpineIndex(currentSpineIndex),
|
currentSpineIndex(currentSpineIndex),
|
||||||
currentPage(currentPage),
|
currentPage(currentPage),
|
||||||
@@ -83,7 +70,7 @@ class KOReaderSyncActivity final : public Activity {
|
|||||||
int totalPagesInSpine;
|
int totalPagesInSpine;
|
||||||
uint16_t localParagraphIndex;
|
uint16_t localParagraphIndex;
|
||||||
bool hasLocalParagraphIndex;
|
bool hasLocalParagraphIndex;
|
||||||
SyncIntent syncIntent = SyncIntent::COMPARE;
|
KOReaderSyncIntentState syncIntent = KOReaderSyncIntentState::COMPARE;
|
||||||
|
|
||||||
State state = WIFI_SELECTION;
|
State state = WIFI_SELECTION;
|
||||||
std::string statusMessage;
|
std::string statusMessage;
|
||||||
@@ -111,6 +98,7 @@ class KOReaderSyncActivity final : public Activity {
|
|||||||
void performSync();
|
void performSync();
|
||||||
void performUpload();
|
void performUpload();
|
||||||
void closeCancelled();
|
void closeCancelled();
|
||||||
|
void resumeReader(KOReaderSyncOutcomeState outcome, const SyncResult* appliedResult = nullptr);
|
||||||
bool ensureEpubLoadedForMapping();
|
bool ensureEpubLoadedForMapping();
|
||||||
void releaseEpubForMapping();
|
void releaseEpubForMapping();
|
||||||
bool computeLocalProgressAndChapter();
|
bool computeLocalProgressAndChapter();
|
||||||
|
|||||||
@@ -3,8 +3,12 @@
|
|||||||
#include <FsHelpers.h>
|
#include <FsHelpers.h>
|
||||||
#include <HalStorage.h>
|
#include <HalStorage.h>
|
||||||
#include <I18n.h>
|
#include <I18n.h>
|
||||||
|
#include <Logging.h>
|
||||||
|
#include <esp_heap_caps.h>
|
||||||
|
#include <esp_system.h>
|
||||||
|
|
||||||
#include "CrossPointSettings.h"
|
#include "CrossPointSettings.h"
|
||||||
|
#include "CrossPointState.h"
|
||||||
#include "Epub.h"
|
#include "Epub.h"
|
||||||
#include "EpubReaderActivity.h"
|
#include "EpubReaderActivity.h"
|
||||||
#include "Txt.h"
|
#include "Txt.h"
|
||||||
@@ -16,6 +20,14 @@
|
|||||||
#include "components/UITheme.h"
|
#include "components/UITheme.h"
|
||||||
#include "fontIds.h"
|
#include "fontIds.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void logReaderLaunchMemSnapshot(const char* stage) {
|
||||||
|
const uint32_t freeHeap = esp_get_free_heap_size();
|
||||||
|
const uint32_t contigHeap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | MALLOC_CAP_DEFAULT);
|
||||||
|
LOG_DBG("READER", "Reader mem[%s]: free=%lu contig=%lu", stage, freeHeap, contigHeap);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
std::string ReaderActivity::extractFolderPath(const std::string& filePath) {
|
std::string ReaderActivity::extractFolderPath(const std::string& filePath) {
|
||||||
const auto lastSlash = filePath.find_last_of('/');
|
const auto lastSlash = filePath.find_last_of('/');
|
||||||
if (lastSlash == std::string::npos || lastSlash == 0) {
|
if (lastSlash == std::string::npos || lastSlash == 0) {
|
||||||
@@ -90,6 +102,7 @@ void ReaderActivity::goToLibrary(const std::string& fromBookPath) {
|
|||||||
void ReaderActivity::onGoToEpubReader(std::unique_ptr<Epub> epub) {
|
void ReaderActivity::onGoToEpubReader(std::unique_ptr<Epub> epub) {
|
||||||
const auto epubPath = epub->getPath();
|
const auto epubPath = epub->getPath();
|
||||||
currentBookPath = epubPath;
|
currentBookPath = epubPath;
|
||||||
|
logReaderLaunchMemSnapshot("before_push_epub_reader");
|
||||||
startActivityForResult(std::make_unique<EpubReaderActivity>(renderer, mappedInput, std::move(epub)),
|
startActivityForResult(std::make_unique<EpubReaderActivity>(renderer, mappedInput, std::move(epub)),
|
||||||
[this](const ActivityResult&) { finish(); });
|
[this](const ActivityResult&) { finish(); });
|
||||||
}
|
}
|
||||||
@@ -115,12 +128,17 @@ void ReaderActivity::onGoToTxtReader(std::unique_ptr<Txt> txt) {
|
|||||||
|
|
||||||
void ReaderActivity::onEnter() {
|
void ReaderActivity::onEnter() {
|
||||||
Activity::onEnter();
|
Activity::onEnter();
|
||||||
|
logReaderLaunchMemSnapshot("onEnter_begin");
|
||||||
|
|
||||||
if (initialBookPath.empty()) {
|
if (initialBookPath.empty()) {
|
||||||
goToLibrary(); // Start from root when entering via Browse
|
goToLibrary(); // Start from root when entering via Browse
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (APP_STATE.koReaderSyncSession.active && APP_STATE.koReaderSyncSession.epubPath == initialBookPath) {
|
||||||
|
LOG_DBG("READER", "Opening EPUB with pending KOReader sync outcome=%d", static_cast<int>(APP_STATE.koReaderSyncSession.outcome));
|
||||||
|
}
|
||||||
|
|
||||||
currentBookPath = initialBookPath;
|
currentBookPath = initialBookPath;
|
||||||
if (isImageFile(initialBookPath)) {
|
if (isImageFile(initialBookPath)) {
|
||||||
onGoToBmpViewer(initialBookPath);
|
onGoToBmpViewer(initialBookPath);
|
||||||
|
|||||||
Reference in New Issue
Block a user