Use temp file approach
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
#include "ChapterXPathIndexer.h"
|
#include "ChapterXPathIndexer.h"
|
||||||
|
|
||||||
|
#include <HalStorage.h>
|
||||||
#include <Logging.h>
|
#include <Logging.h>
|
||||||
#include <Print.h>
|
|
||||||
#include <expat.h>
|
#include <expat.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -388,10 +388,34 @@ static std::optional<ParserState> parseSpineItem(const std::shared_ptr<Epub>& ep
|
|||||||
return std::nullopt;
|
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);
|
ParserState state(spineIndex);
|
||||||
|
|
||||||
XML_Parser parser = XML_ParserCreate(nullptr);
|
XML_Parser parser = XML_ParserCreate(nullptr);
|
||||||
if (!parser) {
|
if (!parser) {
|
||||||
|
Storage.remove(tmpPath.c_str());
|
||||||
LOG_ERR("KOX", "Failed to allocate XML parser for spine=%d", spineIndex);
|
LOG_ERR("KOX", "Failed to allocate XML parser for spine=%d", spineIndex);
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
@@ -401,30 +425,29 @@ static std::optional<ParserState> parseSpineItem(const std::shared_ptr<Epub>& ep
|
|||||||
XML_SetCharacterDataHandler(parser, onCharacterData);
|
XML_SetCharacterDataHandler(parser, onCharacterData);
|
||||||
XML_SetDefaultHandlerExpand(parser, onDefaultHandlerExpand);
|
XML_SetDefaultHandlerExpand(parser, onDefaultHandlerExpand);
|
||||||
|
|
||||||
// Feed decompressed data to Expat incrementally to avoid a large contiguous allocation.
|
FsFile tmpFile;
|
||||||
class ExpatPrint : public Print {
|
bool parseOk = Storage.openFileForRead("KOX", tmpPath, tmpFile);
|
||||||
public:
|
|
||||||
XML_Parser parser;
|
if (parseOk) {
|
||||||
bool ok = true;
|
constexpr size_t kParseBufSize = 1024;
|
||||||
size_t write(uint8_t b) override { return write(&b, 1); }
|
int done;
|
||||||
size_t write(const uint8_t* buf, size_t size) override {
|
do {
|
||||||
if (!ok) return 0;
|
void* const buf = XML_GetBuffer(parser, kParseBufSize);
|
||||||
if (XML_Parse(parser, reinterpret_cast<const char*>(buf), static_cast<int>(size), XML_FALSE) ==
|
if (!buf) {
|
||||||
XML_STATUS_ERROR) {
|
parseOk = false;
|
||||||
ok = false;
|
break;
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
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;
|
Storage.remove(tmpPath.c_str());
|
||||||
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;
|
|
||||||
|
|
||||||
if (!parseOk) {
|
if (!parseOk) {
|
||||||
LOG_ERR("KOX", "XPath parse failed for spine=%d at line %lu: %s", spineIndex, XML_GetCurrentLineNumber(parser),
|
LOG_ERR("KOX", "XPath parse failed for spine=%d at line %lu: %s", spineIndex, XML_GetCurrentLineNumber(parser),
|
||||||
|
|||||||
Reference in New Issue
Block a user