From e3eca4600acb27d12d2385af229eeac9ae1234cb Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 27 Sep 2026 21:16:09 +0000 Subject: [PATCH 1/3] fix: quiet expected-miss log errors and fix log newlines - Probe image/section/dictionary caches with Storage.open so a miss on first open is not logged as an error. - Stale section cache version logs at INF as a rebuild. - No RTC / no IMU log at INF; ternary format strings go through "%s" so both branches get the newline. - Bump freeink-sdk to ad146a5 (EpdBus, SecureClient, SDCardManager and Frontlight prints routed through LOG_*). Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01XgLRarbAmBVepTPKCK7mgd --- CHANGELOG.md | 1 + freeink-sdk | 2 +- lib/Epub/Epub.cpp | 6 +++--- lib/Epub/Epub/Section.cpp | 7 +++++-- lib/Epub/Epub/blocks/ImageBlock.cpp | 9 +++++---- lib/hal/HalClock.cpp | 2 +- lib/hal/HalTiltSensor.cpp | 2 +- src/CrossPointSettings.cpp | 10 ++++++---- src/util/Dictionary.cpp | 9 +++++---- 9 files changed, 28 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2a392201d3..403eb70036c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ ### Fixed +- The serial log no longer reports errors for normal events: a missing image, section or dictionary cache file on first open, an outdated section cache being rebuilt, and a device without a clock chip or motion sensor. Log lines that were missing a line break now end cleanly, and SD, display, TLS and frontlight messages use the standard log format. - X4 Pro light-sleep firmware shows its real version (for example `1.6.0-x4-pro`) instead of "dev" in Settings, on the boot screen, and in the web and OTA version checks. - Background Library indexing and the reader's next-page draw-ahead run at full CPU speed again instead of the lowest idle clock. - USB Drive no longer reads ahead into the sectors a computer is about to write, so copying files to the card is not slowed by background reads. diff --git a/freeink-sdk b/freeink-sdk index 44d42099601..ad146a56210 160000 --- a/freeink-sdk +++ b/freeink-sdk @@ -1 +1 @@ -Subproject commit 44d42099601a9aed5c9115100e12fc30d561b035 +Subproject commit ad146a5621001a320c35f8f24dfd294a07959c3a diff --git a/lib/Epub/Epub.cpp b/lib/Epub/Epub.cpp index 49e1a2c2a64..bf49cc02236 100644 --- a/lib/Epub/Epub.cpp +++ b/lib/Epub/Epub.cpp @@ -1578,10 +1578,10 @@ bool Epub::seedOptimizerImageCache(const std::string& itemHref, const int expect return false; } // Existing exact-layout output always wins, even if the optional transport is corrupt. - FsFile existing; + FsFile existing = Storage.open(destPxcPath.c_str()); // quiet: a miss is expected uint16_t w = 0, h = 0; - bool cached = Storage.openFileForRead("EBP", destPxcPath, existing) && readPxcHeader(existing, w, h) && - w == expectedWidth && h == expectedHeight && existing.size() == pxcByteCount(w, h); + bool cached = existing && readPxcHeader(existing, w, h) && w == expectedWidth && h == expectedHeight && + existing.size() == pxcByteCount(w, h); existing.close(); if (cached) { Storage.remove(backup.c_str()); diff --git a/lib/Epub/Epub/Section.cpp b/lib/Epub/Epub/Section.cpp index 01ea65ee083..8858257f19f 100644 --- a/lib/Epub/Epub/Section.cpp +++ b/lib/Epub/Epub/Section.cpp @@ -255,7 +255,9 @@ bool Section::writeSectionFileHeader(const ReaderRenderSpec& spec) { } bool Section::loadSectionFile(const ReaderRenderSpec& spec) { - if (!Storage.openFileForRead("SCT", filePath, file)) { + // Quiet open: no section file yet is the normal first-open case. + file = Storage.open(filePath.c_str()); + if (!file) { return false; } @@ -286,7 +288,8 @@ bool Section::loadSectionFile(const ReaderRenderSpec& spec) { if (version != SECTION_FILE_VERSION && version != SECTION_FILE_PARTIAL_VERSION) { // Explicit close() required: member variable persists beyond function scope file.close(); - LOG_ERR("SCT", "Deserialization failed: Unknown version %u", version); + LOG_INF("SCT", "Stale section cache v%u (want v%u), rebuilding", static_cast(version), + static_cast(SECTION_FILE_VERSION)); clearCache(); return false; } diff --git a/lib/Epub/Epub/blocks/ImageBlock.cpp b/lib/Epub/Epub/blocks/ImageBlock.cpp index d326dd65f77..6f31495dd37 100644 --- a/lib/Epub/Epub/blocks/ImageBlock.cpp +++ b/lib/Epub/Epub/blocks/ImageBlock.cpp @@ -291,8 +291,9 @@ bool renderFromCache(GfxRenderer& renderer, const std::string& cachePath, int x, return renderCachedPixels(renderer, retained->pixels.get(), retained->width, retained->height, x, y); } - FsFile cacheFile; - if (!Storage.openFileForRead("IMG", cachePath, cacheFile)) { + // Quiet open: a missing cache is the normal first-render case. + FsFile cacheFile = Storage.open(cachePath.c_str()); + if (!cacheFile) { invalidateRetainedPxcPath(cachePath); return false; } @@ -412,8 +413,8 @@ bool renderFromCache(GfxRenderer& renderer, const std::string& cachePath, int x, bool ImageBlock::hasValidCache() const { const auto cachePath = getCachePath(imagePath); - FsFile cacheFile; - if (!Storage.openFileForRead("IMG", cachePath, cacheFile)) { + FsFile cacheFile = Storage.open(cachePath.c_str()); // quiet: a miss is expected + if (!cacheFile) { return false; } diff --git a/lib/hal/HalClock.cpp b/lib/hal/HalClock.cpp index 64003e18ecb..29a5089c07a 100644 --- a/lib/hal/HalClock.cpp +++ b/lib/hal/HalClock.cpp @@ -72,7 +72,7 @@ void adjustDateByDays(uint16_t& year, uint8_t& month, uint8_t& day, const int da void HalClock::begin() { _available = _sdkRtc.begin(); - LOG_INF("CLK", _available ? "SDK RTC found" : "RTC not found"); + LOG_INF("CLK", "%s", _available ? "SDK RTC found" : "RTC not found"); } bool HalClock::refresh(const bool needDate) const { diff --git a/lib/hal/HalTiltSensor.cpp b/lib/hal/HalTiltSensor.cpp index 7744733d4d0..ded1e28bcbe 100644 --- a/lib/hal/HalTiltSensor.cpp +++ b/lib/hal/HalTiltSensor.cpp @@ -35,7 +35,7 @@ void HalTiltSensor::begin() { LOG_INF("GYR", "SDK IMU initialized"); return; } - LOG_ERR("GYR", "SDK IMU not found"); + LOG_INF("GYR", "SDK IMU not found"); } bool HalTiltSensor::wake() { diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp index 5affe08a75b..080d86c4ffa 100644 --- a/src/CrossPointSettings.cpp +++ b/src/CrossPointSettings.cpp @@ -840,11 +840,13 @@ bool CrossPointSettings::loadFromFile() { } if (result && (resave || migrateToCurrentPath)) { if (saveToFile()) { - LOG_DBG("CPS", migrateToCurrentPath ? "Migrated legacy settings.json to crossink-settings.json" - : "Resaved settings to update format"); + LOG_DBG("CPS", "%s", + migrateToCurrentPath ? "Migrated legacy settings.json to crossink-settings.json" + : "Resaved settings to update format"); } else { - LOG_ERR("CPS", migrateToCurrentPath ? "Failed to save migrated settings to crossink-settings.json" - : "Failed to resave settings after format update"); + LOG_ERR("CPS", "%s", + migrateToCurrentPath ? "Failed to save migrated settings to crossink-settings.json" + : "Failed to resave settings after format update"); } } migrateLanguageBinaryFile(); diff --git a/src/util/Dictionary.cpp b/src/util/Dictionary.cpp index dcf013e07a9..3e54027eeae 100644 --- a/src/util/Dictionary.cpp +++ b/src/util/Dictionary.cpp @@ -135,8 +135,9 @@ std::string Dictionary::readConfiguredDictPath(const char* cachePath) { // Try per-book dictionary.bin first when cachePath is provided. if (cachePath && cachePath[0] != '\0') { snprintf(binPath, sizeof(binPath), "%s/%s", cachePath, DICT_BIN); - HalFile f; - if (Storage.openFileForRead("DICT", binPath, f)) { + // Quiet probe: a missing per-book file just means "Use Global". + HalFile f = Storage.open(binPath); + if (f) { const int sz = static_cast(f.fileSize()); if (sz > 0) { std::string result(sz, '\0'); @@ -155,8 +156,8 @@ std::string Dictionary::readConfiguredDictPath(const char* cachePath) { // Read global dictionary.bin. snprintf(binPath, sizeof(binPath), "%s/%s", GLOBAL_DICT_DIR, DICT_BIN); - HalFile f; - if (!Storage.openFileForRead("DICT", binPath, f)) return ""; + HalFile f = Storage.open(binPath); // quiet probe: no global dictionary configured + if (!f) return ""; const int sz = static_cast(f.fileSize()); if (sz <= 0) { f.close(); From 0be0cc4f9f2b30fd677b24196b213e9ff1441e65 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 27 Sep 2026 21:16:09 +0000 Subject: [PATCH 2/3] fix: cache short upscaled images by clamping block rows to image height The decoder clamps each block to the image height, so the band never needs more than h rows. A DC-only progressive JPEG could report 121 rows for a 67-row image, fail the band check, and re-decode on every pass. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01XgLRarbAmBVepTPKCK7mgd --- CHANGELOG.md | 1 + lib/Epub/Epub/converters/PixelCache.h | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 403eb70036c..b40efe757f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ ### Fixed +- Small images that are scaled up, such as a short progressive JPEG, are now cached after the first draw instead of being decoded again on every screen refresh. - The serial log no longer reports errors for normal events: a missing image, section or dictionary cache file on first open, an outdated section cache being rebuilt, and a device without a clock chip or motion sensor. Log lines that were missing a line break now end cleanly, and SD, display, TLS and frontlight messages use the standard log format. - X4 Pro light-sleep firmware shows its real version (for example `1.6.0-x4-pro`) instead of "dev" in Settings, on the boot screen, and in the web and OTA version checks. - Background Library indexing and the reader's next-page draw-ahead run at full CPU speed again instead of the lowest idle clock. diff --git a/lib/Epub/Epub/converters/PixelCache.h b/lib/Epub/Epub/converters/PixelCache.h index e1311b2709b..c6fb373cd60 100644 --- a/lib/Epub/Epub/converters/PixelCache.h +++ b/lib/Epub/Epub/converters/PixelCache.h @@ -70,6 +70,12 @@ struct PixelCache { flushedRows = 0; ok = false; + // The decoder clamps every block to the image height, so a block never + // writes more than h rows. Without this, a short upscaled image (a DC-only + // progressive JPEG block can span 121 rows for a 67-row image) never gets + // a cache and is re-decoded on every render pass. + if (maxBlockDstRows > h) maxBlockDstRows = h; + int wantRows = maxBlockDstRows + 2; if (wantRows < MIN_BAND_ROWS) wantRows = MIN_BAND_ROWS; if (wantRows > h) wantRows = h; From bdaaefcd33427dd3523d80c12d9672cc5e2789fd Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 27 Sep 2026 21:38:07 +0000 Subject: [PATCH 3/3] test: default oflag in HalStorage::open stubs to match the HAL Section, ImageBlock and Epub now call Storage.open(path) with the default read-only flag. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01XgLRarbAmBVepTPKCK7mgd --- test/memory_policy/stubs/HalStorage.h | 2 +- test/pxc_v2/stubs/HalStorage.h | 2 +- test/section_persistence/stubs/HalStorage.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/memory_policy/stubs/HalStorage.h b/test/memory_policy/stubs/HalStorage.h index c8af7f59bf9..abd9dcde905 100644 --- a/test/memory_policy/stubs/HalStorage.h +++ b/test/memory_policy/stubs/HalStorage.h @@ -107,7 +107,7 @@ class HalStorage { return true; } - HalFile open(const char* path, oflag_t) { + HalFile open(const char* path, oflag_t = O_RDONLY) { const auto found = files_.find(path); return found == files_.end() ? HalFile{} : HalFile(found->second); } diff --git a/test/pxc_v2/stubs/HalStorage.h b/test/pxc_v2/stubs/HalStorage.h index 83ee02587f5..8b6e498d121 100644 --- a/test/pxc_v2/stubs/HalStorage.h +++ b/test/pxc_v2/stubs/HalStorage.h @@ -94,7 +94,7 @@ class HalStorage { return true; } - HalFile open(const char* path, oflag_t) { + HalFile open(const char* path, oflag_t = O_RDONLY) { const auto found = files_.find(path); return found == files_.end() ? HalFile{} : HalFile(found->second); } diff --git a/test/section_persistence/stubs/HalStorage.h b/test/section_persistence/stubs/HalStorage.h index a6482c37da2..918af99b115 100644 --- a/test/section_persistence/stubs/HalStorage.h +++ b/test/section_persistence/stubs/HalStorage.h @@ -93,7 +93,7 @@ class HalStorage { return true; } - HalFile open(const char* path, oflag_t) { + HalFile open(const char* path, oflag_t = O_RDONLY) { const auto found = files_.find(path); return found == files_.end() ? HalFile{} : HalFile(found->second); }