Use temp file approach

This commit is contained in:
jpirnay
2026-03-19 16:28:29 +01:00
parent 11e4c4b67f
commit 6deca2b78f
+46 -23
View File
@@ -1,7 +1,7 @@
#include "ChapterXPathIndexer.h"
#include <HalStorage.h>
#include <Logging.h>
#include <Print.h>
#include <expat.h>
#include <algorithm>
@@ -388,10 +388,34 @@ static std::optional<ParserState> parseSpineItem(const std::shared_ptr<Epub>& ep
return std::nullopt;
}
// Phase 1: decompress EPUB entry to a temp file on SD.
// This keeps the ~1.3 KB uzlib_uncomp struct off the stack before Expat runs.
const std::string tmpPath = epub->getCachePath() + "/.tmp_kox.html";
{
if (Storage.exists(tmpPath.c_str())) {
Storage.remove(tmpPath.c_str());
}
FsFile tmpFile;
if (!Storage.openFileForWrite("KOX", tmpPath, tmpFile)) {
LOG_ERR("KOX", "Failed to create temp file for spine=%d", spineIndex);
return std::nullopt;
}
constexpr size_t kChunkSize = 1024;
const bool ok = epub->readItemContentsToStream(spineItem.href, tmpFile, kChunkSize);
tmpFile.close();
if (!ok) {
Storage.remove(tmpPath.c_str());
LOG_ERR("KOX", "Failed to decompress spine=%d to temp file", spineIndex);
return std::nullopt;
}
}
// Phase 2: parse the temp file with Expat in chunks.
ParserState state(spineIndex);
XML_Parser parser = XML_ParserCreate(nullptr);
if (!parser) {
Storage.remove(tmpPath.c_str());
LOG_ERR("KOX", "Failed to allocate XML parser for spine=%d", spineIndex);
return std::nullopt;
}
@@ -401,30 +425,29 @@ static std::optional<ParserState> parseSpineItem(const std::shared_ptr<Epub>& ep
XML_SetCharacterDataHandler(parser, onCharacterData);
XML_SetDefaultHandlerExpand(parser, onDefaultHandlerExpand);
// Feed decompressed data to Expat incrementally to avoid a large contiguous allocation.
class ExpatPrint : public Print {
public:
XML_Parser parser;
bool ok = true;
size_t write(uint8_t b) override { return write(&b, 1); }
size_t write(const uint8_t* buf, size_t size) override {
if (!ok) return 0;
if (XML_Parse(parser, reinterpret_cast<const char*>(buf), static_cast<int>(size), XML_FALSE) ==
XML_STATUS_ERROR) {
ok = false;
return 0;
FsFile tmpFile;
bool parseOk = Storage.openFileForRead("KOX", tmpPath, tmpFile);
if (parseOk) {
constexpr size_t kParseBufSize = 1024;
int done;
do {
void* const buf = XML_GetBuffer(parser, kParseBufSize);
if (!buf) {
parseOk = false;
break;
}
return size;
}
};
const size_t len = tmpFile.read(buf, kParseBufSize);
done = tmpFile.available() == 0;
if (XML_ParseBuffer(parser, static_cast<int>(len), done) == XML_STATUS_ERROR) {
parseOk = false;
break;
}
} while (!done);
tmpFile.close();
}
ExpatPrint ep;
ep.parser = parser;
constexpr size_t kChunkSize = 512;
epub->readItemContentsToStream(spineItem.href, ep, kChunkSize);
// Finalise the parse regardless of streaming result
const bool parseOk = ep.ok && XML_Parse(parser, "", 0, XML_TRUE) != XML_STATUS_ERROR;
Storage.remove(tmpPath.c_str());
if (!parseOk) {
LOG_ERR("KOX", "XPath parse failed for spine=%d at line %lu: %s", spineIndex, XML_GetCurrentLineNumber(parser),