Merge pull request #194 from jpirnay/fix-opds-oom

feat: Support OPDS image sidecar download and fix OOM for large lists
This commit is contained in:
jpirnay
2026-05-10 21:41:15 +02:00
committed by GitHub
11 changed files with 249 additions and 93 deletions
+9 -10
View File
@@ -4,6 +4,7 @@
#include <Logging.h>
#include <cstring>
#include <utility>
namespace {
// Returns the length of href after trimming trailing slashes.
@@ -146,7 +147,6 @@ void OpdsParser::flush() {
bool OpdsParser::error() const { return errorOccured; }
void OpdsParser::clear() {
entries.clear();
searchTemplate.clear();
osdUrl.clear();
nextPageUrl.clear();
@@ -156,14 +156,6 @@ void OpdsParser::clear() {
inEntry = inTitle = inAuthor = inAuthorName = inId = false;
}
std::vector<OpdsEntry> OpdsParser::getBooks() const {
std::vector<OpdsEntry> books;
for (const auto& entry : entries) {
if (entry.type == OpdsEntryType::BOOK) books.push_back(entry);
}
return books;
}
const char* OpdsParser::findAttribute(const XML_Char** atts, const char* name) {
for (int i = 0; atts[i]; i += 2) {
if (strcmp(atts[i], name) == 0) return atts[i + 1];
@@ -206,6 +198,10 @@ void XMLCALL OpdsParser::startElement(void* userData, const XML_Char* name, cons
}
self->currentEntry.acquisitionLinks.push_back(acquisition);
}
} else if (rel && type && strstr(rel, "opds-spec.org/image") != nullptr &&
strstr(rel, "thumbnail") == nullptr && strncmp(type, "image/", 6) == 0 &&
self->currentEntry.imageHref.empty()) {
self->currentEntry.imageHref = href;
} else if (type && strstr(type, "application/atom+xml") != nullptr) {
if (self->currentEntry.type != OpdsEntryType::BOOK) {
self->currentEntry.type = OpdsEntryType::NAVIGATION;
@@ -243,7 +239,10 @@ void XMLCALL OpdsParser::endElement(void* userData, const XML_Char* name) {
if (strcmp(name, "entry") == 0 || strstr(name, ":entry") != nullptr) {
if (!self->currentEntry.title.empty() && !self->currentEntry.href.empty()) {
self->entries.push_back(self->currentEntry);
if (self->onEntryParsed) {
self->onEntryParsed(std::move(self->currentEntry));
self->currentEntry = OpdsEntry{};
}
}
self->inEntry = false;
} else if (self->inEntry) {
+14 -21
View File
@@ -2,6 +2,7 @@
#include <Print.h>
#include <expat.h>
#include <functional>
#include <string>
#include <vector>
@@ -30,6 +31,7 @@ struct OpdsEntry {
std::string href; // Navigation URL or epub download URL
std::string id;
std::vector<OpdsAcquisitionLink> acquisitionLinks;
std::string imageHref; // Cover image URL (rel="http://opds-spec.org/image"), books only
};
// Legacy alias for backward compatibility
@@ -41,14 +43,17 @@ using OpdsBook = OpdsEntry;
*
* Usage:
* OpdsParser parser;
* if (parser.parse(xmlData, xmlLength)) {
* for (const auto& entry : parser.getEntries()) {
* if (entry.type == OpdsEntryType::BOOK) {
* // Downloadable book
* } else {
* // Navigation link to another catalog
* }
* parser.onEntryParsed = [](OpdsEntry entry) {
* if (entry.type == OpdsEntryType::BOOK) {
* // Process downloadable book
* } else {
* // Process navigation link
* }
* };
*
* // Entries are emitted immediately as they are parsed from the stream.
* if (parser.parse(xmlData, xmlLength)) {
* // Parsing completed successfully
* }
*/
class OpdsParser final : public Print {
@@ -73,24 +78,13 @@ class OpdsParser final : public Print {
operator bool() { return !error(); }
/**
* Get the parsed entries (both navigation and book entries).
* @return Vector of OpdsEntry entries
*/
const std::vector<OpdsEntry>& getEntries() const& { return entries; }
std::vector<OpdsEntry> getEntries() && { return std::move(entries); }
/**
* Get only book entries (legacy compatibility).
* @return Vector of book entries
*/
std::vector<OpdsEntry> getBooks() const;
/**
* Clear all parsed entries.
*/
void clear();
std::function<void(OpdsEntry)> onEntryParsed;
private:
// Expat callbacks
static void XMLCALL startElement(void* userData, const XML_Char* name, const XML_Char** atts);
@@ -105,7 +99,6 @@ class OpdsParser final : public Print {
static const char* findAttribute(const XML_Char** atts, const char* name);
XML_Parser parser = nullptr;
std::vector<OpdsEntry> entries;
OpdsEntry currentEntry;
std::string currentText;