## Summary * **What is the goal of this PR?** Fixes #2275. A book could get stuck reopening on an old page, with progress no longer saving and neither "Delete Book Cache" nor "Clear Reading Cache" able to fix it. Root cause: `progress.bin` was written truncate-in-place, so an interrupted write (power loss, or a crash mid-SPI during sleep) left it with a broken FAT cluster chain that the firmware could neither rewrite nor delete — recovery required `fsck`/manual deletion on a host PC. Confirmed in the SDK: `SDCardManager::openFileForWrite` opens with `O_RDWR | O_CREAT | O_TRUNC`, so the canonical file is zeroed before the few progress bytes are rewritten — exactly the window that corrupts the FAT chain. * **What changes are included?** * New shared helper `ProgressFile::writeAtomic()` (`src/activities/reader/ProgressFile.h`): writes progress to `progress.bin.tmp`, flushes and closes it, then `remove`s the old `progress.bin` and `rename`s the temp into place. An interrupted write now only ever damages the throwaway temp; the canonical file is never torn. * All three readers route their progress saves through the helper: EPUB (`EpubReaderUtils.h`), `TxtReaderActivity`, `XtcReaderActivity` — they all shared the identical vulnerable pattern. * Minor: `EpubReaderUtils::saveProgress` now takes `const Epub&` (clears a cppcheck `constParameterReference` finding). ## Additional Context * **Crash-safe, not metadata-atomic.** On FAT the replace is `remove` + `rename` (two directory ops; SdFat's `rename` won't overwrite, hence remove-first). A crash between them leaves *neither* file, which reads as "no saved progress" on next launch — a harmless reset to an old page, never a corrupt/unclearable file. The guarantee is that `progress.bin` is never half-written. * **Prevents, does not repair.** This stops new corruption on healthy cards. It cannot fix an already-corrupted `progress.bin` (removing it may itself fail at the FAT level) — those still need `fsck`/manual deletion, as in the issue's workaround. * **Known follow-up (out of scope here):** a crash *while writing the temp* can leave an orphan `progress.bin.tmp`. It's harmless and self-healing (the next save overwrites it, and it never blocks reading progress), but a boot-time orphan-`.tmp` cleanup would be a tidy follow-up. * **Focus areas for review:** the close-before-rename ordering in `ProgressFile.h` and the remove-before-rename rationale. ## Verification * `./bin/clang-format-fix` — clean * `pio check --fail-on-defect low --fail-on-defect medium --fail-on-defect high` — no defects * `pio run` — SUCCESS (RAM 30.9%, Flash 78.8%; footprint essentially unchanged) * Tested on a **Xteink X4** device: open book, turn pages, sleep/exit, reopen — progress now restores to the navigated page across all three readers (EPUB / TXT / XTC). --- ### AI Usage Did you use AI tools to help write this code? _**YES**_ --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
32 lines
1011 B
C++
32 lines
1011 B
C++
#pragma once
|
|
|
|
#include <Epub.h>
|
|
#include <Logging.h>
|
|
|
|
#include "ProgressFile.h"
|
|
|
|
namespace EpubReaderUtils {
|
|
|
|
// Persists reader progress for an EPUB to its cache directory. Returns true on success.
|
|
inline bool saveProgress(const Epub& epub, int spineIndex, int pageNumber, int pageCount) {
|
|
if (spineIndex < 0 || spineIndex > 0xFFFF || pageNumber < 0 || pageNumber > 0xFFFF || pageCount < 0 ||
|
|
pageCount > 0xFFFF) {
|
|
LOG_ERR("ERS", "Progress values out of range: spine=%d page=%d count=%d", spineIndex, pageNumber, pageCount);
|
|
return false;
|
|
}
|
|
uint8_t data[6];
|
|
data[0] = spineIndex & 0xFF;
|
|
data[1] = (spineIndex >> 8) & 0xFF;
|
|
data[2] = pageNumber & 0xFF;
|
|
data[3] = (pageNumber >> 8) & 0xFF;
|
|
data[4] = pageCount & 0xFF;
|
|
data[5] = (pageCount >> 8) & 0xFF;
|
|
if (!ProgressFile::writeAtomic(epub.getCachePath(), data, sizeof(data))) {
|
|
return false;
|
|
}
|
|
LOG_DBG("ERS", "Progress saved: spine=%d page=%d", spineIndex, pageNumber);
|
|
return true;
|
|
}
|
|
|
|
} // namespace EpubReaderUtils
|