Use temp file approach
This commit is contained in:
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user