From 19b59fd72f5d4c628d4baf93bb01cfb6e344653e Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Sun, 18 Apr 2021 15:01:43 +0400 Subject: [PATCH] Serving /catalog/v2/entries /catalog/v2/entries is intended to play the combined role of /catalog/root.xml and /catalog/search of the old OPDS API. Currently, the latter role is not yet implemented. Implementation note: instead of tweaking and reusing `OPDSDumper::dumpOPDSFeed()`, the generation of the OPDS feed is done via `mustache` and a new template `static/catalog_v2_entries.xml`. --- src/server/internalServer.cpp | 46 +++++++++++++++++++++++++++++++++++ src/server/internalServer.h | 1 + static/catalog_v2_entries.xml | 45 ++++++++++++++++++++++++++++++++++ static/resources_list.txt | 1 + test/server.cpp | 30 +++++++++++++++++++++++ 5 files changed, 123 insertions(+) create mode 100644 static/catalog_v2_entries.xml diff --git a/src/server/internalServer.cpp b/src/server/internalServer.cpp index b94f215cd..8f628942b 100644 --- a/src/server/internalServer.cpp +++ b/src/server/internalServer.cpp @@ -723,6 +723,8 @@ std::unique_ptr InternalServer::handle_catalog_v2(const RequestContext if (url == "root.xml") { return handle_catalog_v2_root(request); + } else if (url == "entries") { + return handle_catalog_v2_entries(request); } else if (url == "categories") { return handle_catalog_v2_categories(request); } else { @@ -747,6 +749,50 @@ std::unique_ptr InternalServer::handle_catalog_v2_root(const RequestCo ); } +std::unique_ptr InternalServer::handle_catalog_v2_entries(const RequestContext& request) +{ + const std::string root_url = normalizeRootUrl(m_root); + + const auto now = gen_date_str(); + kainjow::mustache::list bookData; + for ( const auto& bookId : mp_library->getBooksIds() ) { + const Book& book = mp_library->getBookById(bookId); + const MustacheData bookUrl = book.getUrl().empty() + ? MustacheData(false) + : MustacheData(book.getUrl()); + bookData.push_back(kainjow::mustache::object{ + {"id", "urn:uuid:"+book.getId()}, + {"name", book.getName()}, + {"title", book.getTitle()}, + {"description", book.getDescription()}, + {"language", book.getLanguage()}, + {"content_id", book.getHumanReadableIdFromPath()}, + {"updated", book.getDate() + "T00:00:00Z"}, + {"category", book.getCategory()}, + {"flavour", book.getFlavour()}, + {"tags", book.getTags()}, + {"article_count", to_string(book.getArticleCount())}, + {"media_count", to_string(book.getMediaCount())}, + {"author_name", book.getCreator()}, + {"publisher_name", book.getPublisher()}, + {"url", bookUrl}, + {"size", to_string(book.getSize())}, + }); + } + + return ContentResponse::build( + *this, + RESOURCE::catalog_v2_entries_xml, + kainjow::mustache::object{ + {"date", now}, + {"endpoint_root", root_url + "/catalog/v2"}, + {"feed_id", gen_uuid(m_library_id + "/entries")}, + {"books", bookData } + }, + "application/atom+xml;profile=opds-catalog;kind=acquisition" + ); +} + std::unique_ptr InternalServer::handle_catalog_v2_categories(const RequestContext& request) { const std::string root_url = normalizeRootUrl(m_root); diff --git a/src/server/internalServer.h b/src/server/internalServer.h index af28856f0..142d3fc37 100644 --- a/src/server/internalServer.h +++ b/src/server/internalServer.h @@ -75,6 +75,7 @@ class InternalServer { std::unique_ptr handle_catalog(const RequestContext& request); std::unique_ptr handle_catalog_v2(const RequestContext& request); std::unique_ptr handle_catalog_v2_root(const RequestContext& request); + std::unique_ptr handle_catalog_v2_entries(const RequestContext& request); std::unique_ptr handle_catalog_v2_categories(const RequestContext& request); std::unique_ptr handle_meta(const RequestContext& request); std::unique_ptr handle_search(const RequestContext& request); diff --git a/static/catalog_v2_entries.xml b/static/catalog_v2_entries.xml new file mode 100644 index 000000000..72a698cb7 --- /dev/null +++ b/static/catalog_v2_entries.xml @@ -0,0 +1,45 @@ + + + {{feed_id}} + + + + + + All Entries + {{date}} + + {{#books}} + + {{id}} + {{title}} + {{description}} + {{language}} + {{updated}} + {{name}} + {{flavour}} + {{category}} + {{tags}} + {{article_count}} + {{media_count}} + /meta?name=favicon&content={{{content_id}}} + + + {{author_name}} + + + {{publisher_name}} + + {{#url}} + + {{/url}} + + {{/books}} + diff --git a/static/resources_list.txt b/static/resources_list.txt index 9691e80e5..9b9923bcf 100644 --- a/static/resources_list.txt +++ b/static/resources_list.txt @@ -38,4 +38,5 @@ templates/external_blocker_part.html templates/captured_external.html opensearchdescription.xml catalog_v2_root.xml +catalog_v2_entries.xml catalog_v2_categories.xml diff --git a/test/server.cpp b/test/server.cpp index 89b9f9b10..7a9d14f54 100644 --- a/test/server.cpp +++ b/test/server.cpp @@ -981,3 +981,33 @@ TEST_F(LibraryServerTest, catalog_v2_categories) )"; EXPECT_EQ(maskVariableOPDSFeedData(r->body), expected_output); } + +TEST_F(LibraryServerTest, catalog_v2_entries) +{ + const auto r = zfs1_->GET("/catalog/v2/entries"); + EXPECT_EQ(r->status, 200); + EXPECT_EQ(maskVariableOPDSFeedData(r->body), + "\n" + "\n" + " 12345678-90ab-cdef-1234-567890abcdef\n" + "\n" + " \n" + " \n" + " \n" + "\n" + " All Entries\n" + " YYYY-MM-DDThh:mm:ssZ\n" + "\n" + CHARLES_RAY_CATALOG_ENTRY + RAY_CHARLES_CATALOG_ENTRY + UNCATEGORIZED_RAY_CHARLES_CATALOG_ENTRY + "\n" + ); +}