diff --git a/include/opds_dumper.h b/include/opds_dumper.h index bdea0f8ac..3c4ce4227 100644 --- a/include/opds_dumper.h +++ b/include/opds_dumper.h @@ -59,9 +59,10 @@ class OPDSDumper * * @param bookIds the ids of the books to include in the feed * @param query the query used to obtain the list of book ids + * @param partial whether the feed should include partial or complete entries * @return The OPDS feed. */ - std::string dumpOPDSFeedV2(const std::vector& bookIds, const std::string& query) const; + std::string dumpOPDSFeedV2(const std::vector& bookIds, const std::string& query, bool partial) const; /** * Dump the OPDS complete entry document. diff --git a/src/opds_dumper.cpp b/src/opds_dumper.cpp index b5f5bb078..ddf2e83de 100644 --- a/src/opds_dumper.cpp +++ b/src/opds_dumper.cpp @@ -134,20 +134,22 @@ string OPDSDumper::dumpOPDSFeed(const std::vector& bookIds, const s return render_template(RESOURCE::templates::catalog_entries_xml, template_data); } -string OPDSDumper::dumpOPDSFeedV2(const std::vector& bookIds, const std::string& query) const +string OPDSDumper::dumpOPDSFeedV2(const std::vector& bookIds, const std::string& query, bool partial) const { const auto bookData = getBookData(library, bookIds); + const char* const endpoint = partial ? "/partial_entries" : "/entries"; const kainjow::mustache::object template_data{ {"date", gen_date_str()}, {"endpoint_root", rootLocation + "/catalog/v2"}, - {"feed_id", gen_uuid(libraryId + "/entries?"+query)}, + {"feed_id", gen_uuid(libraryId + endpoint + "?" + query)}, {"filter", query.empty() ? MustacheData(false) : MustacheData(query)}, {"query", query.empty() ? "" : "?" + urlEncode(query)}, {"totalResults", to_string(m_totalResults)}, {"startIndex", to_string(m_startIndex)}, {"itemsPerPage", to_string(m_count)}, - {"books", bookData } + {"books", bookData }, + {"dump_partial_entries", MustacheData(partial)} }; return render_template(RESOURCE::templates::catalog_v2_entries_xml, template_data); diff --git a/src/server/internalServer.h b/src/server/internalServer.h index 3c9b6d89c..cfaceb9ec 100644 --- a/src/server/internalServer.h +++ b/src/server/internalServer.h @@ -75,7 +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_entries(const RequestContext& request, bool partial); std::unique_ptr handle_catalog_v2_complete_entry(const RequestContext& request, const std::string& entryId); std::unique_ptr handle_catalog_v2_categories(const RequestContext& request); std::unique_ptr handle_catalog_v2_languages(const RequestContext& request); diff --git a/src/server/internalServer_catalog_v2.cpp b/src/server/internalServer_catalog_v2.cpp index 79960b42d..88a3f64ef 100644 --- a/src/server/internalServer_catalog_v2.cpp +++ b/src/server/internalServer_catalog_v2.cpp @@ -59,7 +59,9 @@ std::unique_ptr InternalServer::handle_catalog_v2(const RequestContext const std::string entryId = request.get_url_part(3); return handle_catalog_v2_complete_entry(request, entryId); } else if (url == "entries") { - return handle_catalog_v2_entries(request); + return handle_catalog_v2_entries(request, /*partial=*/false); + } else if (url == "partial_entries") { + return handle_catalog_v2_entries(request, /*partial=*/true); } else if (url == "categories") { return handle_catalog_v2_categories(request); } else if (url == "languages") { @@ -86,13 +88,13 @@ std::unique_ptr InternalServer::handle_catalog_v2_root(const RequestCo ); } -std::unique_ptr InternalServer::handle_catalog_v2_entries(const RequestContext& request) +std::unique_ptr InternalServer::handle_catalog_v2_entries(const RequestContext& request, bool partial) { OPDSDumper opdsDumper(mp_library); opdsDumper.setRootLocation(m_root); opdsDumper.setLibraryId(m_library_id); const auto bookIds = search_catalog(request, opdsDumper); - const auto opdsFeed = opdsDumper.dumpOPDSFeedV2(bookIds, request.get_query()); + const auto opdsFeed = opdsDumper.dumpOPDSFeedV2(bookIds, request.get_query(), partial); return ContentResponse::build( *this, opdsFeed, diff --git a/static/templates/catalog_v2_entries.xml b/static/templates/catalog_v2_entries.xml index 451e3b342..36e59a850 100644 --- a/static/templates/catalog_v2_entries.xml +++ b/static/templates/catalog_v2_entries.xml @@ -5,7 +5,7 @@ {{feed_id}} urn:uuid:{{id}} {{title}} {{updated}} - {{description}} +{{#dump_partial_entries}} + +{{/dump_partial_entries}}{{^dump_partial_entries}} {{description}} {{language}} {{name}} {{flavour}} @@ -49,6 +53,7 @@ {{#url}} {{/url}} +{{/dump_partial_entries}} {{/books}} diff --git a/test/server.cpp b/test/server.cpp index eebae7338..34ff1b6b8 100644 --- a/test/server.cpp +++ b/test/server.cpp @@ -1075,7 +1075,7 @@ TEST_F(LibraryServerTest, catalog_v2_languages) EXPECT_EQ(maskVariableOPDSFeedData(r->body), expected_output); } -#define CATALOG_V2_ENTRIES_PREAMBLE(q) \ +#define CATALOG_V2_ENTRIES_PREAMBLE0(x) \ "\n" \ "12345678-90ab-cdef-1234-567890abcdef\n" \ "\n" \ " \n" \ " \n" \ "\n" \ +#define CATALOG_V2_ENTRIES_PREAMBLE(q) \ + CATALOG_V2_ENTRIES_PREAMBLE0("entries" q) + +#define CATALOG_V2_PARTIAL_ENTRIES_PREAMBLE(q) \ + CATALOG_V2_ENTRIES_PREAMBLE0("partial_entries" q) TEST_F(LibraryServerTest, catalog_v2_entries) { @@ -1253,3 +1258,40 @@ TEST_F(LibraryServerTest, catalog_v2_individual_entry_access) const auto r1 = zfs1_->GET("/catalog/v2/entry/non-existent-entry"); EXPECT_EQ(r1->status, 404); } + +TEST_F(LibraryServerTest, catalog_v2_partial_entries) +{ + const auto r = zfs1_->GET("/catalog/v2/partial_entries"); + EXPECT_EQ(r->status, 200); + EXPECT_EQ(maskVariableOPDSFeedData(r->body), + CATALOG_V2_PARTIAL_ENTRIES_PREAMBLE("") + " All Entries\n" + " YYYY-MM-DDThh:mm:ssZ\n" + "\n" + " \n" + " urn:uuid:charlesray\n" + " Charles, Ray\n" + " YYYY-MM-DDThh:mm:ssZ\n" + " \n" + " \n" + " \n" + " urn:uuid:raycharles\n" + " Ray Charles\n" + " YYYY-MM-DDThh:mm:ssZ\n" + " \n" + " \n" + " \n" + " urn:uuid:raycharles_uncategorized\n" + " Ray (uncategorized) Charles\n" + " YYYY-MM-DDThh:mm:ssZ\n" + " \n" + " \n" + "\n" + ); +}