/catalog/v2/entries is also a search endpoint

This commit is contained in:
Veloman Yunkan 2021-04-26 02:58:02 +04:00
parent b60e3ffb26
commit 07252a127a
5 changed files with 109 additions and 23 deletions

View File

@ -690,6 +690,13 @@ Filter get_search_filter(const RequestContext& request)
return filter; return filter;
} }
template<class T>
std::vector<T> subrange(const std::vector<T>& v, size_t s, size_t n)
{
const size_t e = std::min(v.size(), s+n);
return std::vector<T>(v.begin()+std::min(v.size(), s), v.begin()+e);
}
} // unnamed namespace } // unnamed namespace
std::vector<std::string> std::vector<std::string>
@ -705,11 +712,7 @@ InternalServer::search_catalog(const RequestContext& request,
const auto totalResults = bookIdsToDump.size(); const auto totalResults = bookIdsToDump.size();
const size_t count = request.get_optional_param("count", 10UL); const size_t count = request.get_optional_param("count", 10UL);
const size_t startIndex = request.get_optional_param("start", 0UL); const size_t startIndex = request.get_optional_param("start", 0UL);
const auto s = std::min(startIndex, totalResults); bookIdsToDump = subrange(bookIdsToDump, startIndex, count);
bookIdsToDump.erase(bookIdsToDump.begin(), bookIdsToDump.begin()+s);
if (count>0 && bookIdsToDump.size() > count) {
bookIdsToDump.resize(count);
}
opdsDumper.setOpenSearchInfo(totalResults, startIndex, bookIdsToDump.size()); opdsDumper.setOpenSearchInfo(totalResults, startIndex, bookIdsToDump.size());
return bookIdsToDump; return bookIdsToDump;
} }
@ -761,7 +764,11 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_entries(const Reques
const auto now = gen_date_str(); const auto now = gen_date_str();
kainjow::mustache::list bookData; 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 Book& book = mp_library->getBookById(bookId);
const MustacheData bookUrl = book.getUrl().empty() const MustacheData bookUrl = book.getUrl().empty()
? MustacheData(false) ? MustacheData(false)
@ -786,13 +793,18 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_entries(const Reques
}); });
} }
const auto query = request.get_query().empty()
? MustacheData(false)
: MustacheData(request.get_query());
return ContentResponse::build( return ContentResponse::build(
*this, *this,
RESOURCE::catalog_v2_entries_xml, RESOURCE::catalog_v2_entries_xml,
kainjow::mustache::object{ kainjow::mustache::object{
{"date", now}, {"date", now},
{"endpoint_root", root_url + "/catalog/v2"}, {"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 } {"books", bookData }
}, },
"application/atom+xml;profile=opds-catalog;kind=acquisition" "application/atom+xml;profile=opds-catalog;kind=acquisition"

View File

@ -183,4 +183,14 @@ std::string RequestContext::get_header(const std::string& name) const {
return headers.at(lcAll(name)); 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;
}
} }

View File

@ -88,6 +88,7 @@ class RequestContext {
std::string get_url() const; std::string get_url() const;
std::string get_url_part(int part) const; std::string get_url_part(int part) const;
std::string get_full_url() const; std::string get_full_url() const;
std::string get_query() const;
ByteRange get_range() const; ByteRange get_range() const;

View File

@ -13,7 +13,7 @@
href="{{endpoint_root}}/root.xml" href="{{endpoint_root}}/root.xml"
type="application/atom+xml;profile=opds-catalog;kind=navigation"/> type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
<title>All Entries</title> <title>{{^filter}}All Entries{{/filter}}{{#filter}}Filtered Entries ({{filter}}){{/filter}}</title>
<updated>{{date}}</updated> <updated>{{date}}</updated>
{{#books}} {{#books}}

View File

@ -982,26 +982,30 @@ TEST_F(LibraryServerTest, catalog_v2_categories)
EXPECT_EQ(maskVariableOPDSFeedData(r->body), expected_output); EXPECT_EQ(maskVariableOPDSFeedData(r->body), expected_output);
} }
#define CATALOG_V2_ENTRIES_PREAMBLE \
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" \
"<feed xmlns=\"http://www.w3.org/2005/Atom\"\n" \
" xmlns:opds=\"https://specs.opds.io/opds-1.2\">\n" \
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n" \
"\n" \
" <link rel=\"self\"\n" \
" href=\"/catalog/v2/entries\"\n" \
" type=\"application/atom+xml;profile=opds-catalog;kind=acquisition\"/>\n" \
" <link rel=\"start\"\n" \
" href=\"/catalog/v2/root.xml\"\n" \
" type=\"application/atom+xml;profile=opds-catalog;kind=navigation\"/>\n" \
" <link rel=\"up\"\n" \
" href=\"/catalog/v2/root.xml\"\n" \
" type=\"application/atom+xml;profile=opds-catalog;kind=navigation\"/>\n" \
"\n" \
TEST_F(LibraryServerTest, catalog_v2_entries) TEST_F(LibraryServerTest, catalog_v2_entries)
{ {
const auto r = zfs1_->GET("/catalog/v2/entries"); const auto r = zfs1_->GET("/catalog/v2/entries");
EXPECT_EQ(r->status, 200); EXPECT_EQ(r->status, 200);
EXPECT_EQ(maskVariableOPDSFeedData(r->body), EXPECT_EQ(maskVariableOPDSFeedData(r->body),
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" CATALOG_V2_ENTRIES_PREAMBLE
"<feed xmlns=\"http://www.w3.org/2005/Atom\"\n"
" xmlns:opds=\"https://specs.opds.io/opds-1.2\">\n"
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
"\n"
" <link rel=\"self\"\n"
" href=\"/catalog/v2/entries\"\n"
" type=\"application/atom+xml;profile=opds-catalog;kind=acquisition\"/>\n"
" <link rel=\"start\"\n"
" href=\"/catalog/v2/root.xml\"\n"
" type=\"application/atom+xml;profile=opds-catalog;kind=navigation\"/>\n"
" <link rel=\"up\"\n"
" href=\"/catalog/v2/root.xml\"\n"
" type=\"application/atom+xml;profile=opds-catalog;kind=navigation\"/>\n"
"\n"
" <title>All Entries</title>\n" " <title>All Entries</title>\n"
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n" " <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
"\n" "\n"
@ -1011,3 +1015,62 @@ TEST_F(LibraryServerTest, catalog_v2_entries)
"</feed>\n" "</feed>\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
" <title>Filtered Entries (start=1)</title>\n"
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
"\n"
RAY_CHARLES_CATALOG_ENTRY
UNCATEGORIZED_RAY_CHARLES_CATALOG_ENTRY
"</feed>\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
" <title>Filtered Entries (count=2)</title>\n"
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
"\n"
CHARLES_RAY_CATALOG_ENTRY
RAY_CHARLES_CATALOG_ENTRY
"</feed>\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
" <title>Filtered Entries (count=1&amp;start=1)</title>\n"
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
"\n"
RAY_CHARLES_CATALOG_ENTRY
"</feed>\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
" <title>Filtered Entries (q=&quot;ray charles&quot;)</title>\n"
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
"\n"
RAY_CHARLES_CATALOG_ENTRY
CHARLES_RAY_CATALOG_ENTRY
"</feed>\n"
);
}