Backup: Stable state with EPUB reader fixes (Freeze, OOM, Speed, State, Tooling)

This commit is contained in:
Antigravity Agent
2026-01-19 18:44:45 -05:00
parent 21277e03eb
commit 1237f01ac2
34 changed files with 1600 additions and 192 deletions

View File

@@ -319,11 +319,16 @@ bool Epub::clearCache() const {
}
void Epub::setupCacheDir() const {
if (SdMan.exists(cachePath.c_str())) {
return;
// Always try to create, just in case.
if (!SdMan.mkdir(cachePath.c_str())) {
// If mkdir failed, it might already exist. Check if it's a directory.
// SdMan doesn't allow checking type easily without opening.
// But let's log the detailed failure state.
bool exists = SdMan.exists(cachePath.c_str());
Serial.printf("[%lu] [EBP] mkdir failed for %s. Exists? %s\n", millis(), cachePath.c_str(), exists ? "YES" : "NO");
} else {
// Serial.printf("[%lu] [EBP] Created cache directory: %s\n", millis(), cachePath.c_str());
}
SdMan.mkdir(cachePath.c_str());
}
const std::string& Epub::getCachePath() const { return cachePath; }

View File

@@ -52,6 +52,11 @@ std::unique_ptr<Page> Page::deserialize(FsFile& file) {
uint16_t count;
serialization::readPod(file, count);
if (count > 1000) {
Serial.printf("[%lu] [PGE] WARNING: Suspicious element count %d\n", millis(), count);
return nullptr;
}
for (uint16_t i = 0; i < count; i++) {
uint8_t tag;
serialization::readPod(file, tag);
@@ -60,7 +65,7 @@ std::unique_ptr<Page> Page::deserialize(FsFile& file) {
auto pl = PageLine::deserialize(file);
page->elements.push_back(std::move(pl));
} else {
Serial.printf("[%lu] [PGE] Deserialization failed: Unknown tag %u\n", millis(), tag);
Serial.printf("[%lu] [PGE] Deserialization failed: Unknown tag %u at index %d\n", millis(), tag, i);
return nullptr;
}
}

View File

@@ -11,6 +11,7 @@
constexpr int MAX_COST = std::numeric_limits<int>::max();
void ParsedText::addWord(std::string word, const EpdFontFamily::Style fontStyle) {
// Serial.printf("addWord: %s\n", word.c_str());
if (word.empty()) return;
words.push_back(std::move(word));

View File

@@ -7,12 +7,13 @@
#include "parsers/ChapterHtmlSlimParser.h"
namespace {
constexpr uint8_t SECTION_FILE_VERSION = 9;
constexpr uint8_t SECTION_FILE_VERSION = 10;
constexpr uint32_t HEADER_SIZE = sizeof(uint8_t) + sizeof(int) + sizeof(float) + sizeof(bool) + sizeof(uint8_t) +
sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint32_t);
} // namespace
uint32_t Section::onPageComplete(std::unique_ptr<Page> page) {
SDLock lock;
if (!file) {
Serial.printf("[%lu] [SCT] File not open for writing page %d\n", millis(), pageCount);
return 0;
@@ -23,7 +24,6 @@ uint32_t Section::onPageComplete(std::unique_ptr<Page> page) {
Serial.printf("[%lu] [SCT] Failed to serialize page %d\n", millis(), pageCount);
return 0;
}
Serial.printf("[%lu] [SCT] Page %d processed\n", millis(), pageCount);
pageCount++;
return position;
@@ -54,6 +54,7 @@ void Section::writeSectionFileHeader(const int fontId, const float lineCompressi
bool Section::loadSectionFile(const int fontId, const float lineCompression, const bool extraParagraphSpacing,
const uint8_t paragraphAlignment, const uint16_t viewportWidth,
const uint16_t viewportHeight) {
SDLock lock;
if (!SdMan.openFileForRead("SCT", filePath, file)) {
return false;
}
@@ -93,14 +94,14 @@ bool Section::loadSectionFile(const int fontId, const float lineCompression, con
serialization::readPod(file, pageCount);
file.close();
Serial.printf("[%lu] [SCT] Deserialization succeeded: %d pages\n", millis(), pageCount);
return true;
}
// Your updated class method (assuming you are using the 'SD' object, which is a wrapper for a specific filesystem)
bool Section::clearCache() const {
SDLock lock;
if (!SdMan.exists(filePath.c_str())) {
Serial.printf("[%lu] [SCT] Cache does not exist, no action needed\n", millis());
return true;
}
@@ -109,7 +110,6 @@ bool Section::clearCache() const {
return false;
}
Serial.printf("[%lu] [SCT] Cache cleared successfully\n", millis());
return true;
}
@@ -117,6 +117,7 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c
const uint8_t paragraphAlignment, const uint16_t viewportWidth,
const uint16_t viewportHeight, const std::function<void()>& progressSetupFn,
const std::function<void(int)>& progressFn) {
SDLock lock;
constexpr uint32_t MIN_SIZE_FOR_PROGRESS = 50 * 1024; // 50KB
const auto localPath = epub->getSpineItem(spineIndex).href;
const auto tmpHtmlPath = epub->getCachePath() + "/.tmp_" + std::to_string(spineIndex) + ".html";
@@ -161,8 +162,6 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c
return false;
}
Serial.printf("[%lu] [SCT] Streamed temp HTML to %s (%d bytes)\n", millis(), tmpHtmlPath.c_str(), fileSize);
// Only show progress bar for larger chapters where rendering overhead is worth it
if (progressSetupFn && fileSize >= MIN_SIZE_FOR_PROGRESS) {
progressSetupFn();
@@ -217,6 +216,7 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c
}
std::unique_ptr<Page> Section::loadPageFromSectionFile() {
SDLock lock;
if (!SdMan.openFileForRead("SCT", filePath, file)) {
return nullptr;
}
@@ -224,9 +224,23 @@ std::unique_ptr<Page> Section::loadPageFromSectionFile() {
file.seek(HEADER_SIZE - sizeof(uint32_t));
uint32_t lutOffset;
serialization::readPod(file, lutOffset);
if (lutOffset > file.size() || lutOffset < HEADER_SIZE) {
Serial.printf("[%lu] [SCT] Invalid LUT offset %u (file size %u)\n", millis(), lutOffset, file.size());
file.close();
return nullptr;
}
file.seek(lutOffset + sizeof(uint32_t) * currentPage);
uint32_t pagePos;
serialization::readPod(file, pagePos);
if (pagePos > file.size()) {
Serial.printf("[%lu] [SCT] Invalid page pos %u for page %d\n", millis(), pagePos, currentPage);
file.close();
return nullptr;
}
file.seek(pagePos);
auto page = Page::deserialize(file);

View File

@@ -16,6 +16,7 @@ void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int
auto wordXposIt = wordXpos.begin();
for (size_t i = 0; i < words.size(); i++) {
Serial.printf("[%lu] [TXB] Rendering word: %s\n", millis(), wordIt->c_str());
renderer.drawText(fontId, *wordXposIt + x, y, wordIt->c_str(), true, *wordStylesIt);
std::advance(wordIt, 1);
@@ -52,6 +53,7 @@ std::unique_ptr<TextBlock> TextBlock::deserialize(FsFile& file) {
// Word count
serialization::readPod(file, wc);
Serial.printf("[%lu] [TXB] Deserializing TextBlock: %u words\n", millis(), wc);
// Sanity check: prevent allocation of unreasonably large lists (max 10000 words per block)
if (wc > 10000) {
@@ -63,7 +65,13 @@ std::unique_ptr<TextBlock> TextBlock::deserialize(FsFile& file) {
words.resize(wc);
wordXpos.resize(wc);
wordStyles.resize(wc);
for (auto& w : words) serialization::readString(file, w);
wordStyles.resize(wc);
int i = 0;
for (auto& w : words) {
if (i % 100 == 0 && i > 0) Serial.printf("[%lu] [TXB] Reading word %d/%d\n", millis(), i, wc);
serialization::readString(file, w);
i++;
}
for (auto& x : wordXpos) serialization::readPod(file, x);
for (auto& s : wordStyles) serialization::readPod(file, s);

View File

@@ -55,6 +55,7 @@ void ChapterHtmlSlimParser::startNewTextBlock(const TextBlock::Style style) {
}
void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* name, const XML_Char** atts) {
// Serial.printf("startElement: %s\n", name);
auto* self = static_cast<ChapterHtmlSlimParser*>(userData);
// Middle of skip
@@ -268,9 +269,17 @@ void XMLCALL ChapterHtmlSlimParser::endElement(void* userData, const XML_Char* n
}
bool ChapterHtmlSlimParser::parseAndBuildPages() {
startNewTextBlock((TextBlock::Style)this->paragraphAlignment);
SDLock lock;
Serial.printf("[%lu] [EHP] parseAndBuildPages start. Heap: %u\n", millis(), ESP.getFreeHeap());
Serial.printf("[%lu] [EHP] Calling startNewTextBlock\n", millis());
startNewTextBlock((TextBlock::Style)this->paragraphAlignment);
Serial.printf("[%lu] [EHP] startNewTextBlock returned\n", millis());
Serial.printf("[%lu] [EHP] Creating XML parser\n", millis());
const XML_Parser parser = XML_ParserCreate(nullptr);
if (parser) Serial.printf("[%lu] [EHP] Parser created\n", millis());
int done;
if (!parser) {
@@ -306,6 +315,7 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
}
const size_t len = file.read(buf, 1024);
// Serial.printf("[%lu] [EHP] Read %d bytes\n", millis(), len);
if (len == 0 && file.available() > 0) {
Serial.printf("[%lu] [EHP] File read error\n", millis());
@@ -331,6 +341,7 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
done = file.available() == 0;
if (XML_ParseBuffer(parser, static_cast<int>(len), done) == XML_STATUS_ERROR) {
Serial.printf("[%lu] [EHP] XML_ParseBuffer returned error\n", millis());
Serial.printf("[%lu] [EHP] Parse error at line %lu:\n%s\n", millis(), XML_GetCurrentLineNumber(parser),
XML_ErrorString(XML_GetErrorCode(parser)));
XML_StopParser(parser, XML_FALSE); // Stop any pending processing
@@ -340,6 +351,7 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
file.close();
return false;
}
vTaskDelay(1);
} while (!done);
XML_StopParser(parser, XML_FALSE); // Stop any pending processing