diff --git a/include/library.h b/include/library.h index 856a3a9e..fa8f2426 100644 --- a/include/library.h +++ b/include/library.h @@ -106,7 +106,15 @@ class Filter { Filter& rejectTags(const Tags& tags); Filter& category(std::string category); + + /** + * Set the filter to only accept books in the specified language. + * + * Multiple languages can be specified as a comma-separated list (in + * which case a book in any of those languages will match). + */ Filter& lang(std::string lang); + Filter& publisher(std::string publisher); Filter& creator(std::string creator); Filter& maxSize(size_t size); diff --git a/src/library.cpp b/src/library.cpp index 508d7c8e..fb9fc035 100644 --- a/src/library.cpp +++ b/src/library.cpp @@ -535,9 +535,20 @@ Xapian::Query categoryQuery(const std::string& category) return Xapian::Query("XC" + normalizeText(category)); } -Xapian::Query langQuery(const std::string& lang) +Xapian::Query langQuery(const std::string& commaSeparatedLanguageList) { - return Xapian::Query("L" + normalizeText(lang)); + Xapian::Query q; + bool firstIteration = true; + for ( const auto& lang : kiwix::split(commaSeparatedLanguageList, ",") ) { + const Xapian::Query singleLangQuery("L" + normalizeText(lang)); + if ( firstIteration ) { + q = singleLangQuery; + firstIteration = false; + } else { + q = Xapian::Query(Xapian::Query::OP_OR, q, singleLangQuery); + } + } + return q; } Xapian::Query publisherQuery(const std::string& publisher) diff --git a/src/server/internalServer.cpp b/src/server/internalServer.cpp index 378f516f..ceb5dc41 100644 --- a/src/server/internalServer.cpp +++ b/src/server/internalServer.cpp @@ -1036,9 +1036,6 @@ InternalServer::search_catalog(const RequestContext& request, kiwix::OPDSDumper& opdsDumper) { const auto filter = get_search_filter(request); - const std::string q = filter.hasQuery() - ? filter.getQuery() - : ""; std::vector bookIdsToDump = mp_library->filter(filter); const auto totalResults = bookIdsToDump.size(); const size_t count = request.get_optional_param("count", 10UL); diff --git a/test/library_server.cpp b/test/library_server.cpp index 4afcbc10..f61d9428 100644 --- a/test/library_server.cpp +++ b/test/library_server.cpp @@ -114,7 +114,7 @@ std::string maskVariableOPDSFeedData(std::string s) "569344" \ ) -#define CHARLES_RAY_CATALOG_ENTRY _CHARLES_RAY_CATALOG_ENTRY("zimfile%26other") +#define CHARLES_RAY_CATALOG_ENTRY _CHARLES_RAY_CATALOG_ENTRY("zimfile%26other") #define CHARLES_RAY_CATALOG_ENTRY_NO_MAPPER _CHARLES_RAY_CATALOG_ENTRY("charlesray") #define _RAY_CHARLES_CATALOG_ENTRY(CONTENT_NAME) CATALOG_ENTRY(\ @@ -133,7 +133,7 @@ std::string maskVariableOPDSFeedData(std::string s) "569344"\ ) -#define RAY_CHARLES_CATALOG_ENTRY _RAY_CHARLES_CATALOG_ENTRY("zimfile") +#define RAY_CHARLES_CATALOG_ENTRY _RAY_CHARLES_CATALOG_ENTRY("zimfile") #define RAY_CHARLES_CATALOG_ENTRY_NO_MAPPER _RAY_CHARLES_CATALOG_ENTRY("raycharles") #define UNCATEGORIZED_RAY_CHARLES_CATALOG_ENTRY CATALOG_ENTRY(\ @@ -317,6 +317,44 @@ TEST_F(LibraryServerTest, catalog_search_by_category) ); } +TEST_F(LibraryServerTest, catalog_search_by_language) +{ + { + const auto r = zfs1_->GET("/ROOT/catalog/search?lang=eng"); + EXPECT_EQ(r->status, 200); + EXPECT_EQ(maskVariableOPDSFeedData(r->body), + OPDS_FEED_TAG + " 12345678-90ab-cdef-1234-567890abcdef\n" + " Filtered zims (lang=eng)\n" + " YYYY-MM-DDThh:mm:ssZ\n" + " 1\n" + " 0\n" + " 1\n" + CATALOG_LINK_TAGS + RAY_CHARLES_CATALOG_ENTRY + "\n" + ); + } + + { + const auto r = zfs1_->GET("/ROOT/catalog/search?lang=eng,fra"); + EXPECT_EQ(r->status, 200); + EXPECT_EQ(maskVariableOPDSFeedData(r->body), + OPDS_FEED_TAG + " 12345678-90ab-cdef-1234-567890abcdef\n" + " Filtered zims (lang=eng,fra)\n" + " YYYY-MM-DDThh:mm:ssZ\n" + " 2\n" + " 0\n" + " 2\n" + CATALOG_LINK_TAGS + RAY_CHARLES_CATALOG_ENTRY + CHARLES_RAY_CATALOG_ENTRY + "\n" + ); + } +} + TEST_F(LibraryServerTest, catalog_search_results_pagination) { { @@ -667,6 +705,40 @@ TEST_F(LibraryServerTest, catalog_v2_entries_filtered_by_search_terms) ); } +TEST_F(LibraryServerTest, catalog_v2_entries_filtered_by_language) +{ + { + const auto r = zfs1_->GET("/ROOT/catalog/v2/entries?lang=eng"); + EXPECT_EQ(r->status, 200); + EXPECT_EQ(maskVariableOPDSFeedData(r->body), + CATALOG_V2_ENTRIES_PREAMBLE("?lang=eng") + " Filtered Entries (lang=eng)\n" + " YYYY-MM-DDThh:mm:ssZ\n" + " 1\n" + " 0\n" + " 1\n" + RAY_CHARLES_CATALOG_ENTRY + "\n" + ); + } + + { + const auto r = zfs1_->GET("/ROOT/catalog/v2/entries?lang=eng,fra"); + EXPECT_EQ(r->status, 200); + EXPECT_EQ(maskVariableOPDSFeedData(r->body), + CATALOG_V2_ENTRIES_PREAMBLE("?lang=eng,fra") + " Filtered Entries (lang=eng,fra)\n" + " YYYY-MM-DDThh:mm:ssZ\n" + " 2\n" + " 0\n" + " 2\n" + RAY_CHARLES_CATALOG_ENTRY + CHARLES_RAY_CATALOG_ENTRY + "\n" + ); + } +} + TEST_F(LibraryServerTest, catalog_v2_individual_entry_access) { const auto r = zfs1_->GET("/ROOT/catalog/v2/entry/raycharles");