From 07252a127af39d09edb0f57086c957b27e99cb73 Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Mon, 26 Apr 2021 02:58:02 +0400 Subject: [PATCH] /catalog/v2/entries is also a search endpoint --- src/server/internalServer.cpp | 26 +++++++--- src/server/request_context.cpp | 10 ++++ src/server/request_context.h | 1 + static/catalog_v2_entries.xml | 2 +- test/server.cpp | 93 ++++++++++++++++++++++++++++------ 5 files changed, 109 insertions(+), 23 deletions(-) diff --git a/src/server/internalServer.cpp b/src/server/internalServer.cpp index 3634ef79a..ae77e0871 100644 --- a/src/server/internalServer.cpp +++ b/src/server/internalServer.cpp @@ -690,6 +690,13 @@ Filter get_search_filter(const RequestContext& request) return filter; } +template +std::vector subrange(const std::vector& v, size_t s, size_t n) +{ + const size_t e = std::min(v.size(), s+n); + return std::vector(v.begin()+std::min(v.size(), s), v.begin()+e); +} + } // unnamed namespace std::vector @@ -705,11 +712,7 @@ InternalServer::search_catalog(const RequestContext& request, const auto totalResults = bookIdsToDump.size(); const size_t count = request.get_optional_param("count", 10UL); const size_t startIndex = request.get_optional_param("start", 0UL); - const auto s = std::min(startIndex, totalResults); - bookIdsToDump.erase(bookIdsToDump.begin(), bookIdsToDump.begin()+s); - if (count>0 && bookIdsToDump.size() > count) { - bookIdsToDump.resize(count); - } + bookIdsToDump = subrange(bookIdsToDump, startIndex, count); opdsDumper.setOpenSearchInfo(totalResults, startIndex, bookIdsToDump.size()); return bookIdsToDump; } @@ -761,7 +764,11 @@ std::unique_ptr InternalServer::handle_catalog_v2_entries(const Reques const auto now = gen_date_str(); kainjow::mustache::list bookData; - for ( const auto& bookId : mp_library->getBooksIds() ) { + const auto filter = get_search_filter(request); + const auto allMatchingEntries = mp_library->filter(filter); + const size_t count = request.get_optional_param("count", 10UL); + const size_t start = request.get_optional_param("start", 0UL); + for ( const auto& bookId : subrange(allMatchingEntries, start, count) ) { const Book& book = mp_library->getBookById(bookId); const MustacheData bookUrl = book.getUrl().empty() ? MustacheData(false) @@ -786,13 +793,18 @@ std::unique_ptr InternalServer::handle_catalog_v2_entries(const Reques }); } + const auto query = request.get_query().empty() + ? MustacheData(false) + : MustacheData(request.get_query()); + 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")}, + {"feed_id", gen_uuid(m_library_id + "/entries?"+request.get_query())}, + {"filter", query}, {"books", bookData } }, "application/atom+xml;profile=opds-catalog;kind=acquisition" diff --git a/src/server/request_context.cpp b/src/server/request_context.cpp index cad39eefc..765d01adf 100644 --- a/src/server/request_context.cpp +++ b/src/server/request_context.cpp @@ -183,4 +183,14 @@ std::string RequestContext::get_header(const std::string& name) const { return headers.at(lcAll(name)); } +std::string RequestContext::get_query() const { + std::string q; + const char* sep = ""; + for ( const auto& a : arguments ) { + q += sep + a.first + '=' + a.second; + sep = "&"; + } + return q; +} + } diff --git a/src/server/request_context.h b/src/server/request_context.h index 3dd446af2..5457ae4bf 100644 --- a/src/server/request_context.h +++ b/src/server/request_context.h @@ -88,6 +88,7 @@ class RequestContext { std::string get_url() const; std::string get_url_part(int part) const; std::string get_full_url() const; + std::string get_query() const; ByteRange get_range() const; diff --git a/static/catalog_v2_entries.xml b/static/catalog_v2_entries.xml index 72a698cb7..a829b53b6 100644 --- a/static/catalog_v2_entries.xml +++ b/static/catalog_v2_entries.xml @@ -13,7 +13,7 @@ href="{{endpoint_root}}/root.xml" type="application/atom+xml;profile=opds-catalog;kind=navigation"/> - All Entries + {{^filter}}All Entries{{/filter}}{{#filter}}Filtered Entries ({{filter}}){{/filter}} {{date}} {{#books}} diff --git a/test/server.cpp b/test/server.cpp index 7a9d14f54..6f1382fef 100644 --- a/test/server.cpp +++ b/test/server.cpp @@ -982,26 +982,30 @@ TEST_F(LibraryServerTest, catalog_v2_categories) EXPECT_EQ(maskVariableOPDSFeedData(r->body), expected_output); } +#define CATALOG_V2_ENTRIES_PREAMBLE \ + "\n" \ + "\n" \ + " 12345678-90ab-cdef-1234-567890abcdef\n" \ + "\n" \ + " \n" \ + " \n" \ + " \n" \ + "\n" \ + + 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" + CATALOG_V2_ENTRIES_PREAMBLE " All Entries\n" " YYYY-MM-DDThh:mm:ssZ\n" "\n" @@ -1011,3 +1015,62 @@ TEST_F(LibraryServerTest, catalog_v2_entries) "\n" ); } + +TEST_F(LibraryServerTest, catalog_v2_entries_filtered_by_range) +{ + { + const auto r = zfs1_->GET("/catalog/v2/entries?start=1"); + EXPECT_EQ(r->status, 200); + EXPECT_EQ(maskVariableOPDSFeedData(r->body), + CATALOG_V2_ENTRIES_PREAMBLE + " Filtered Entries (start=1)\n" + " YYYY-MM-DDThh:mm:ssZ\n" + "\n" + RAY_CHARLES_CATALOG_ENTRY + UNCATEGORIZED_RAY_CHARLES_CATALOG_ENTRY + "\n" + ); + } + + { + const auto r = zfs1_->GET("/catalog/v2/entries?count=2"); + EXPECT_EQ(r->status, 200); + EXPECT_EQ(maskVariableOPDSFeedData(r->body), + CATALOG_V2_ENTRIES_PREAMBLE + " Filtered Entries (count=2)\n" + " YYYY-MM-DDThh:mm:ssZ\n" + "\n" + CHARLES_RAY_CATALOG_ENTRY + RAY_CHARLES_CATALOG_ENTRY + "\n" + ); + } + + { + const auto r = zfs1_->GET("/catalog/v2/entries?start=1&count=1"); + EXPECT_EQ(r->status, 200); + EXPECT_EQ(maskVariableOPDSFeedData(r->body), + CATALOG_V2_ENTRIES_PREAMBLE + " Filtered Entries (count=1&start=1)\n" + " YYYY-MM-DDThh:mm:ssZ\n" + "\n" + RAY_CHARLES_CATALOG_ENTRY + "\n" + ); + } +} + +TEST_F(LibraryServerTest, catalog_v2_entries_filtered_by_search_terms) +{ + const auto r = zfs1_->GET("/catalog/v2/entries?q=\"ray%20charles\""); + EXPECT_EQ(r->status, 200); + EXPECT_EQ(maskVariableOPDSFeedData(r->body), + CATALOG_V2_ENTRIES_PREAMBLE + " Filtered Entries (q="ray charles")\n" + " YYYY-MM-DDThh:mm:ssZ\n" + "\n" + RAY_CHARLES_CATALOG_ENTRY + CHARLES_RAY_CATALOG_ENTRY + "\n" + ); +}