lets you navigate and download books now

This commit is contained in:
Justin Mitchell
2026-01-02 19:59:43 -05:00
parent c99b93435f
commit 231393703c
4 changed files with 205 additions and 72 deletions

View File

@@ -60,13 +60,13 @@ bool OpdsParser::parse(const char* xmlData, const size_t length) {
XML_ParserFree(parser);
parser = nullptr;
Serial.printf("[%lu] [OPDS] Parsed %zu books\n", millis(), books.size());
Serial.printf("[%lu] [OPDS] Parsed %zu entries\n", millis(), entries.size());
return true;
}
void OpdsParser::clear() {
books.clear();
currentBook = OpdsBook{};
entries.clear();
currentEntry = OpdsEntry{};
currentText.clear();
inEntry = false;
inTitle = false;
@@ -75,6 +75,16 @@ void OpdsParser::clear() {
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) {
@@ -90,7 +100,7 @@ void XMLCALL OpdsParser::startElement(void* userData, const XML_Char* name, cons
// Check for entry element (with or without namespace prefix)
if (strcmp(name, "entry") == 0 || strstr(name, ":entry") != nullptr) {
self->inEntry = true;
self->currentBook = OpdsBook{};
self->currentEntry = OpdsEntry{};
return;
}
@@ -123,16 +133,26 @@ void XMLCALL OpdsParser::startElement(void* userData, const XML_Char* name, cons
return;
}
// Check for link element with acquisition rel and epub type
// Check for link element
if (strcmp(name, "link") == 0 || strstr(name, ":link") != nullptr) {
const char* rel = findAttribute(atts, "rel");
const char* type = findAttribute(atts, "type");
const char* href = findAttribute(atts, "href");
// Look for acquisition link with epub type
if (rel && type && href) {
if (strstr(rel, "opds-spec.org/acquisition") != nullptr && strcmp(type, "application/epub+zip") == 0) {
self->currentBook.epubUrl = href;
if (href) {
// Check for acquisition link with epub type (this is a downloadable book)
if (rel && type && strstr(rel, "opds-spec.org/acquisition") != nullptr &&
strcmp(type, "application/epub+zip") == 0) {
self->currentEntry.type = OpdsEntryType::BOOK;
self->currentEntry.href = href;
}
// Check for navigation link (subsection or no rel specified with atom+xml type)
else if (type && strstr(type, "application/atom+xml") != nullptr) {
// Only set navigation link if we don't already have an epub link
if (self->currentEntry.type != OpdsEntryType::BOOK) {
self->currentEntry.type = OpdsEntryType::NAVIGATION;
self->currentEntry.href = href;
}
}
}
}
@@ -143,12 +163,12 @@ void XMLCALL OpdsParser::endElement(void* userData, const XML_Char* name) {
// Check for entry end
if (strcmp(name, "entry") == 0 || strstr(name, ":entry") != nullptr) {
// Only add book if it has required fields
if (!self->currentBook.title.empty() && !self->currentBook.epubUrl.empty()) {
self->books.push_back(self->currentBook);
// Only add entry if it has required fields (title and href)
if (!self->currentEntry.title.empty() && !self->currentEntry.href.empty()) {
self->entries.push_back(self->currentEntry);
}
self->inEntry = false;
self->currentBook = OpdsBook{};
self->currentEntry = OpdsEntry{};
return;
}
@@ -157,7 +177,7 @@ void XMLCALL OpdsParser::endElement(void* userData, const XML_Char* name) {
// Check for title end
if (strcmp(name, "title") == 0 || strstr(name, ":title") != nullptr) {
if (self->inTitle) {
self->currentBook.title = self->currentText;
self->currentEntry.title = self->currentText;
}
self->inTitle = false;
return;
@@ -172,7 +192,7 @@ void XMLCALL OpdsParser::endElement(void* userData, const XML_Char* name) {
// Check for author name end
if (self->inAuthor && (strcmp(name, "name") == 0 || strstr(name, ":name") != nullptr)) {
if (self->inAuthorName) {
self->currentBook.author = self->currentText;
self->currentEntry.author = self->currentText;
}
self->inAuthorName = false;
return;
@@ -181,7 +201,7 @@ void XMLCALL OpdsParser::endElement(void* userData, const XML_Char* name) {
// Check for id end
if (strcmp(name, "id") == 0 || strstr(name, ":id") != nullptr) {
if (self->inId) {
self->currentBook.id = self->currentText;
self->currentEntry.id = self->currentText;
}
self->inId = false;
return;

View File

@@ -5,15 +5,27 @@
#include <vector>
/**
* Represents a book entry from an OPDS feed.
* Type of OPDS entry.
*/
struct OpdsBook {
enum class OpdsEntryType {
NAVIGATION, // Link to another catalog
BOOK // Downloadable book
};
/**
* Represents an entry from an OPDS feed (either a navigation link or a book).
*/
struct OpdsEntry {
OpdsEntryType type = OpdsEntryType::NAVIGATION;
std::string title;
std::string author;
std::string epubUrl; // Relative URL like "/books/get/epub/3/Calibre_Library"
std::string author; // Only for books
std::string href; // Navigation URL or epub download URL
std::string id;
};
// Legacy alias for backward compatibility
using OpdsBook = OpdsEntry;
/**
* Parser for OPDS (Open Publication Distribution System) Atom feeds.
* Uses the Expat XML parser to parse OPDS catalog entries.
@@ -21,8 +33,12 @@ struct OpdsBook {
* Usage:
* OpdsParser parser;
* if (parser.parse(xmlData, xmlLength)) {
* for (const auto& book : parser.getBooks()) {
* // Process book entries
* for (const auto& entry : parser.getEntries()) {
* if (entry.type == OpdsEntryType::BOOK) {
* // Downloadable book
* } else {
* // Navigation link to another catalog
* }
* }
* }
*/
@@ -44,13 +60,19 @@ class OpdsParser {
bool parse(const char* xmlData, size_t length);
/**
* Get the parsed books.
* @return Vector of OpdsBook entries
* Get the parsed entries (both navigation and book entries).
* @return Vector of OpdsEntry entries
*/
const std::vector<OpdsBook>& getBooks() const { return books; }
const std::vector<OpdsEntry>& getEntries() const { return entries; }
/**
* Clear all parsed books.
* Get only book entries (legacy compatibility).
* @return Vector of book entries
*/
std::vector<OpdsEntry> getBooks() const;
/**
* Clear all parsed entries.
*/
void clear();
@@ -64,8 +86,8 @@ class OpdsParser {
static const char* findAttribute(const XML_Char** atts, const char* name);
XML_Parser parser = nullptr;
std::vector<OpdsBook> books;
OpdsBook currentBook;
std::vector<OpdsEntry> entries;
OpdsEntry currentEntry;
std::string currentText;
// Parser state