mirror of https://github.com/kiwix/libkiwix.git
Merge pull request #459 from kiwix/opds_category_support
Support for book categories in OPDS feed
This commit is contained in:
commit
f7c867f8a7
|
@ -59,6 +59,7 @@ class Book
|
|||
const std::string& getDate() const { return m_date; }
|
||||
const std::string& getUrl() const { return m_url; }
|
||||
const std::string& getName() const { return m_name; }
|
||||
std::string getCategory() const;
|
||||
const std::string& getTags() const { return m_tags; }
|
||||
std::string getTagStr(const std::string& tagName) const;
|
||||
bool getTagBool(const std::string& tagName) const;
|
||||
|
@ -94,6 +95,9 @@ class Book
|
|||
void setFaviconMimeType(const std::string& faviconMimeType) { m_faviconMimeType = faviconMimeType; }
|
||||
void setDownloadId(const std::string& downloadId) { m_downloadId = downloadId; }
|
||||
|
||||
private:
|
||||
std::string getCategoryFromTags() const;
|
||||
|
||||
protected:
|
||||
std::string m_id;
|
||||
std::string m_downloadId;
|
||||
|
@ -101,6 +105,7 @@ class Book
|
|||
bool m_pathValid = false;
|
||||
std::string m_title;
|
||||
std::string m_description;
|
||||
std::string m_category;
|
||||
std::string m_language;
|
||||
std::string m_creator;
|
||||
std::string m_publisher;
|
||||
|
|
|
@ -52,6 +52,7 @@ class Filter {
|
|||
uint64_t activeFilters;
|
||||
std::vector<std::string> _acceptTags;
|
||||
std::vector<std::string> _rejectTags;
|
||||
std::string _category;
|
||||
std::string _lang;
|
||||
std::string _publisher;
|
||||
std::string _creator;
|
||||
|
@ -96,6 +97,7 @@ class Filter {
|
|||
Filter& acceptTags(std::vector<std::string> tags);
|
||||
Filter& rejectTags(std::vector<std::string> tags);
|
||||
|
||||
Filter& category(std::string category);
|
||||
Filter& lang(std::string lang);
|
||||
Filter& publisher(std::string publisher);
|
||||
Filter& creator(std::string creator);
|
||||
|
|
23
src/book.cpp
23
src/book.cpp
|
@ -61,6 +61,7 @@ bool Book::update(const kiwix::Book& other)
|
|||
m_name = other.m_name;
|
||||
m_flavour = other.m_flavour;
|
||||
m_tags = other.m_tags;
|
||||
m_category = other.m_category;
|
||||
m_origId = other.m_origId;
|
||||
m_articleCount = other.m_articleCount;
|
||||
m_mediaCount = other.m_mediaCount;
|
||||
|
@ -88,6 +89,7 @@ void Book::update(const kiwix::Reader& reader)
|
|||
m_name = reader.getName();
|
||||
m_flavour = reader.getFlavour();
|
||||
m_tags = reader.getTags();
|
||||
m_category = getCategoryFromTags();
|
||||
m_origId = reader.getOrigId();
|
||||
m_articleCount = reader.getArticleCount();
|
||||
m_mediaCount = reader.getMediaCount();
|
||||
|
@ -127,6 +129,8 @@ void Book::updateFromXml(const pugi::xml_node& node, const std::string& baseDir)
|
|||
try {
|
||||
m_downloadId = ATTR("downloadId");
|
||||
} catch(...) {}
|
||||
const auto catattr = node.attribute("category");
|
||||
m_category = catattr.empty() ? getCategoryFromTags() : catattr.value();
|
||||
}
|
||||
#undef ATTR
|
||||
|
||||
|
@ -156,6 +160,8 @@ void Book::updateFromOpds(const pugi::xml_node& node, const std::string& urlHost
|
|||
m_name = VALUE("name");
|
||||
m_flavour = VALUE("flavour");
|
||||
m_tags = VALUE("tags");
|
||||
const auto catnode = node.child("category");
|
||||
m_category = catnode.empty() ? getCategoryFromTags() : catnode.child_value();
|
||||
m_articleCount = strtoull(VALUE("articleCount"), 0, 0);
|
||||
m_mediaCount = strtoull(VALUE("mediaCount"), 0, 0);
|
||||
for(auto linkNode = node.child("link"); linkNode;
|
||||
|
@ -220,4 +226,21 @@ bool Book::getTagBool(const std::string& tagName) const {
|
|||
return convertStrToBool(getTagStr(tagName));
|
||||
}
|
||||
|
||||
std::string Book::getCategory() const
|
||||
{
|
||||
return m_category;
|
||||
}
|
||||
|
||||
std::string Book::getCategoryFromTags() const
|
||||
{
|
||||
try
|
||||
{
|
||||
return getTagStr("category");
|
||||
}
|
||||
catch ( const std::out_of_range& )
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -391,6 +391,7 @@ enum filterTypes {
|
|||
MAXSIZE = FLAG(11),
|
||||
QUERY = FLAG(12),
|
||||
NAME = FLAG(13),
|
||||
CATEGORY = FLAG(14),
|
||||
};
|
||||
|
||||
Filter& Filter::local(bool accept)
|
||||
|
@ -443,6 +444,13 @@ Filter& Filter::rejectTags(std::vector<std::string> tags)
|
|||
return *this;
|
||||
}
|
||||
|
||||
Filter& Filter::category(std::string category)
|
||||
{
|
||||
_category = category;
|
||||
activeFilters |= CATEGORY;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Filter& Filter::lang(std::string lang)
|
||||
{
|
||||
_lang = lang;
|
||||
|
@ -502,6 +510,7 @@ bool Filter::accept(const Book& book) const
|
|||
FILTER(_NOREMOTE, !remote)
|
||||
|
||||
FILTER(MAXSIZE, book.getSize() <= _maxSize)
|
||||
FILTER(CATEGORY, book.getCategory() == _category)
|
||||
FILTER(LANG, book.getLanguage() == _lang)
|
||||
FILTER(_PUBLISHER, book.getPublisher() == _publisher)
|
||||
FILTER(_CREATOR, book.getCreator() == _creator)
|
||||
|
|
|
@ -43,7 +43,7 @@ std::string gen_date_str()
|
|||
std::stringstream is;
|
||||
is << std::setw(2) << std::setfill('0')
|
||||
<< 1900+tm->tm_year << "-"
|
||||
<< std::setw(2) << std::setfill('0') << tm->tm_mon << "-"
|
||||
<< std::setw(2) << std::setfill('0') << tm->tm_mon+1 << "-"
|
||||
<< std::setw(2) << std::setfill('0') << tm->tm_mday << "T"
|
||||
<< std::setw(2) << std::setfill('0') << tm->tm_hour << ":"
|
||||
<< std::setw(2) << std::setfill('0') << tm->tm_min << ":"
|
||||
|
@ -54,7 +54,7 @@ std::string gen_date_str()
|
|||
static std::string gen_date_from_yyyy_mm_dd(const std::string& date)
|
||||
{
|
||||
std::stringstream is;
|
||||
is << date << "T00:00::00:Z";
|
||||
is << date << "T00:00::00Z";
|
||||
return is.str();
|
||||
}
|
||||
|
||||
|
@ -77,6 +77,7 @@ pugi::xml_node OPDSDumper::handleBook(Book book, pugi::xml_node root_node) {
|
|||
ADD_TEXT_ENTRY(entry_node, "updated", gen_date_from_yyyy_mm_dd(book.getDate()));
|
||||
ADD_TEXT_ENTRY(entry_node, "name", book.getName());
|
||||
ADD_TEXT_ENTRY(entry_node, "flavour", book.getFlavour());
|
||||
ADD_TEXT_ENTRY(entry_node, "category", book.getCategory());
|
||||
ADD_TEXT_ENTRY(entry_node, "tags", book.getTags());
|
||||
ADD_TEXT_ENTRY(entry_node, "articleCount", to_string(book.getArticleCount()));
|
||||
ADD_TEXT_ENTRY(entry_node, "mediaCount", to_string(book.getMediaCount()));
|
||||
|
|
|
@ -668,6 +668,22 @@ std::unique_ptr<Response> InternalServer::handle_catalog(const RequestContext& r
|
|||
uuid = zim::Uuid::generate(host);
|
||||
bookIdsToDump = mp_library->filter(kiwix::Filter().valid(true).local(true).remote(true));
|
||||
} else if (url == "search") {
|
||||
bookIdsToDump = search_catalog(request, opdsDumper);
|
||||
uuid = zim::Uuid::generate();
|
||||
}
|
||||
|
||||
opdsDumper.setId(kiwix::to_string(uuid));
|
||||
auto response = ContentResponse::build(
|
||||
*this,
|
||||
opdsDumper.dumpOPDSFeed(bookIdsToDump),
|
||||
"application/atom+xml; profile=opds-catalog; kind=acquisition; charset=utf-8");
|
||||
return std::move(response);
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
InternalServer::search_catalog(const RequestContext& request,
|
||||
kiwix::OPDSDumper& opdsDumper)
|
||||
{
|
||||
auto filter = kiwix::Filter().valid(true).local(true).remote(true);
|
||||
string query("<Empty query>");
|
||||
size_t count(10);
|
||||
|
@ -682,6 +698,9 @@ std::unique_ptr<Response> InternalServer::handle_catalog(const RequestContext& r
|
|||
try {
|
||||
filter.name(request.get_argument("name"));
|
||||
} catch (const std::out_of_range&) {}
|
||||
try {
|
||||
filter.category(request.get_argument("category"));
|
||||
} catch (const std::out_of_range&) {}
|
||||
try {
|
||||
filter.lang(request.get_argument("lang"));
|
||||
} catch (const std::out_of_range&) {}
|
||||
|
@ -698,22 +717,14 @@ std::unique_ptr<Response> InternalServer::handle_catalog(const RequestContext& r
|
|||
filter.rejectTags(kiwix::split(request.get_argument("notag"), ";"));
|
||||
} catch (...) {}
|
||||
opdsDumper.setTitle("Search result for " + query);
|
||||
uuid = zim::Uuid::generate();
|
||||
bookIdsToDump = mp_library->filter(filter);
|
||||
std::vector<std::string> bookIdsToDump = mp_library->filter(filter);
|
||||
auto totalResults = bookIdsToDump.size();
|
||||
bookIdsToDump.erase(bookIdsToDump.begin(), bookIdsToDump.begin()+startIndex);
|
||||
if (count>0 && bookIdsToDump.size() > count) {
|
||||
bookIdsToDump.resize(count);
|
||||
}
|
||||
opdsDumper.setOpenSearchInfo(totalResults, startIndex, bookIdsToDump.size());
|
||||
}
|
||||
|
||||
opdsDumper.setId(kiwix::to_string(uuid));
|
||||
auto response = ContentResponse::build(
|
||||
*this,
|
||||
opdsDumper.dumpOPDSFeed(bookIdsToDump),
|
||||
"application/atom+xml; profile=opds-catalog; kind=acquisition; charset=utf-8");
|
||||
return std::move(response);
|
||||
return bookIdsToDump;
|
||||
}
|
||||
|
||||
namespace
|
||||
|
|
|
@ -41,6 +41,7 @@ namespace kiwix {
|
|||
typedef kainjow::mustache::data MustacheData;
|
||||
|
||||
class Entry;
|
||||
class OPDSDumper;
|
||||
|
||||
class InternalServer {
|
||||
public:
|
||||
|
@ -79,6 +80,9 @@ class InternalServer {
|
|||
std::unique_ptr<Response> handle_captured_external(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_content(const RequestContext& request);
|
||||
|
||||
std::vector<std::string> search_catalog(const RequestContext& request,
|
||||
kiwix::OPDSDumper& opdsDumper);
|
||||
|
||||
MustacheData get_default_data() const;
|
||||
MustacheData homepage_data() const;
|
||||
|
||||
|
|
|
@ -69,6 +69,7 @@ GETTER(jstring, getDate)
|
|||
GETTER(jstring, getUrl)
|
||||
GETTER(jstring, getName)
|
||||
GETTER(jstring, getFlavour)
|
||||
GETTER(jstring, getCategory)
|
||||
GETTER(jstring, getTags)
|
||||
GETTER(jlong, getArticleCount)
|
||||
GETTER(jlong, getMediaCount)
|
||||
|
|
|
@ -24,6 +24,7 @@ public class Book
|
|||
public native String getUrl();
|
||||
public native String getName();
|
||||
public native String getFlavour();
|
||||
public native String getCategory();
|
||||
public native String getTags();
|
||||
/**
|
||||
* Return the value associated to the tag tagName
|
||||
|
|
135
test/book.cpp
135
test/book.cpp
|
@ -1,10 +1,12 @@
|
|||
#include "gtest/gtest.h"
|
||||
#include "../include/book.h"
|
||||
#include <pugixml.hpp>
|
||||
|
||||
TEST(BookTest, updateTest)
|
||||
{
|
||||
kiwix::Book book;
|
||||
|
||||
book.setId("xyz");
|
||||
book.setReadOnly(false);
|
||||
book.setPath("/home/user/Downloads/skin-of-color-society_en_all_2019-11.zim");
|
||||
book.setPathValid(true);
|
||||
|
@ -20,6 +22,9 @@ TEST(BookTest, updateTest)
|
|||
EXPECT_FALSE(newBook.update(book));
|
||||
|
||||
newBook.setReadOnly(false);
|
||||
EXPECT_FALSE(newBook.update(book));
|
||||
|
||||
newBook.setId("xyz");
|
||||
EXPECT_TRUE(newBook.update(book));
|
||||
|
||||
EXPECT_EQ(newBook.readOnly(), book.readOnly());
|
||||
|
@ -27,7 +32,137 @@ TEST(BookTest, updateTest)
|
|||
EXPECT_EQ(newBook.isPathValid(), book.isPathValid());
|
||||
EXPECT_EQ(newBook.getUrl(), book.getUrl());
|
||||
EXPECT_EQ(newBook.getTags(), book.getTags());
|
||||
EXPECT_EQ(newBook.getCategory(), book.getCategory());
|
||||
EXPECT_EQ(newBook.getName(), book.getName());
|
||||
EXPECT_EQ(newBook.getFavicon(), book.getFavicon());
|
||||
EXPECT_EQ(newBook.getFaviconMimeType(), book.getFaviconMimeType());
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct XMLDoc : pugi::xml_document
|
||||
{
|
||||
explicit XMLDoc(const std::string& xml)
|
||||
{
|
||||
load_buffer(xml.c_str(), xml.size());
|
||||
}
|
||||
};
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
TEST(BookTest, updateFromXMLTest)
|
||||
{
|
||||
const XMLDoc xml(R"(
|
||||
<book id="zara"
|
||||
path="./zara.zim"
|
||||
url="https://who.org/zara.zim"
|
||||
title="Catch an infection in 24 hours"
|
||||
description="Complete guide to contagious diseases"
|
||||
language="eng"
|
||||
creator="World Health Organization"
|
||||
publisher="WHO"
|
||||
date="2020-03-31"
|
||||
name="who_contagious_diseases_en"
|
||||
tags="unittest;_category:medicine;_pictures:yes"
|
||||
articleCount="123456"
|
||||
mediaCount="234567"
|
||||
size="345678"
|
||||
>
|
||||
</book>
|
||||
)");
|
||||
|
||||
kiwix::Book book;
|
||||
book.updateFromXml(xml.child("book"), "/data/zim");
|
||||
|
||||
EXPECT_EQ(book.getPath(), "/data/zim/zara.zim");
|
||||
EXPECT_EQ(book.getUrl(), "https://who.org/zara.zim");
|
||||
EXPECT_EQ(book.getTitle(), "Catch an infection in 24 hours");
|
||||
EXPECT_EQ(book.getDescription(), "Complete guide to contagious diseases");
|
||||
EXPECT_EQ(book.getTags(), "unittest;_category:medicine;_pictures:yes");
|
||||
EXPECT_EQ(book.getName(), "who_contagious_diseases_en");
|
||||
EXPECT_EQ(book.getCategory(), "medicine");
|
||||
EXPECT_EQ(book.getArticleCount(), 123456U);
|
||||
EXPECT_EQ(book.getMediaCount(), 234567U);
|
||||
EXPECT_EQ(book.getSize(), 345678U*1024U);
|
||||
}
|
||||
|
||||
TEST(BookTest, updateFromXMLCategoryHandlingTest)
|
||||
{
|
||||
{
|
||||
const XMLDoc xml(R"(
|
||||
<book id="abcd"
|
||||
tags="_category:category_defined_via_tags_only"
|
||||
>
|
||||
</book>
|
||||
)");
|
||||
|
||||
kiwix::Book book;
|
||||
book.updateFromXml(xml.child("book"), "");
|
||||
|
||||
EXPECT_EQ(book.getCategory(), "category_defined_via_tags_only");
|
||||
}
|
||||
{
|
||||
const XMLDoc xml(R"(
|
||||
<book id="abcd"
|
||||
category="category_defined_via_attribute_only"
|
||||
>
|
||||
</book>
|
||||
)");
|
||||
|
||||
kiwix::Book book;
|
||||
book.updateFromXml(xml.child("book"), "");
|
||||
|
||||
EXPECT_EQ(book.getCategory(), "category_defined_via_attribute_only");
|
||||
}
|
||||
{
|
||||
const XMLDoc xml(R"(
|
||||
<book id="abcd"
|
||||
category="category_attribute_overrides_tags"
|
||||
tags="_category:tags_override_category_attribute"
|
||||
>
|
||||
</book>
|
||||
)");
|
||||
|
||||
kiwix::Book book;
|
||||
book.updateFromXml(xml.child("book"), "");
|
||||
|
||||
EXPECT_EQ(book.getCategory(), "category_attribute_overrides_tags");
|
||||
}
|
||||
{
|
||||
const XMLDoc xml(R"(
|
||||
<book id="abcd"
|
||||
tags="_category:tags_override_category_attribute"
|
||||
category="category_attribute_overrides_tags"
|
||||
>
|
||||
</book>
|
||||
)");
|
||||
|
||||
kiwix::Book book;
|
||||
book.updateFromXml(xml.child("book"), "");
|
||||
|
||||
EXPECT_EQ(book.getCategory(), "category_attribute_overrides_tags");
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BookTest, setTagsDoesntAffectCategory)
|
||||
{
|
||||
kiwix::Book book;
|
||||
|
||||
book.setTags("_category:youtube");
|
||||
ASSERT_EQ("", book.getCategory());
|
||||
}
|
||||
|
||||
TEST(BookTest, updateCopiesCategory)
|
||||
{
|
||||
const XMLDoc xml(R"(<book id="abcd" category="ted"></book>)");
|
||||
|
||||
kiwix::Book book;
|
||||
book.updateFromXml(xml.child("book"), "");
|
||||
|
||||
kiwix::Book newBook;
|
||||
newBook.setId("abcd");
|
||||
EXPECT_EQ(newBook.getCategory(), "");
|
||||
newBook.update(book);
|
||||
EXPECT_EQ(newBook.getCategory(), "ted");
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
<library version="1.0">
|
||||
<book
|
||||
id="raycharles"
|
||||
path="./zimfile.zim"
|
||||
url="https://github.com/kiwix/kiwix-lib/raw/master/test/data/zimfile.zim"
|
||||
title="Ray Charles"
|
||||
description="Wikipedia articles about Ray Charles"
|
||||
language="eng"
|
||||
creator="Wikipedia"
|
||||
publisher="Kiwix"
|
||||
date="2020-03-31"
|
||||
name="wikipedia_en_ray_charles"
|
||||
tags="unittest;wikipedia;_category:wikipedia;_pictures:no;_videos:no;_details:no;_ftindex:yes"
|
||||
articleCount="284"
|
||||
mediaCount="2"
|
||||
size="556"
|
||||
></book>
|
||||
<book
|
||||
id="raycharles_uncategorized"
|
||||
path="./zimfile.zim"
|
||||
url="https://github.com/kiwix/kiwix-lib/raw/master/test/data/zimfile.zim"
|
||||
title="Ray Charles"
|
||||
description="Wikipedia articles about Ray Charles"
|
||||
language="eng"
|
||||
creator="Wikipedia"
|
||||
publisher="Kiwix"
|
||||
date="2020-03-31"
|
||||
name="wikipedia_en_ray_charles"
|
||||
tags="unittest;wikipedia;_pictures:no;_videos:no;_details:no;_ftindex:yes"
|
||||
articleCount="284"
|
||||
mediaCount="2"
|
||||
size="556"
|
||||
></book>
|
||||
<book
|
||||
id="charlesray"
|
||||
path="./zimfile.zim"
|
||||
url="https://github.com/kiwix/kiwix-lib/raw/master/test/data/zimfile.zim"
|
||||
title="Charles, Ray"
|
||||
description="Wikipedia articles about Charles, Ray"
|
||||
language="eng"
|
||||
creator="Wikipedia"
|
||||
publisher="Kiwix"
|
||||
date="2020-03-31"
|
||||
name="wikipedia_en_ray_charles"
|
||||
tags="unittest;wikipedia;_category:jazz;_pictures:no;_videos:no;_details:no;_ftindex:yes"
|
||||
articleCount="284"
|
||||
mediaCount="2"
|
||||
size="556"
|
||||
></book>
|
||||
</library>
|
|
@ -46,7 +46,7 @@ const char * sampleOpdsStream = R"(
|
|||
<updated>2018-06-23T00:00::00:Z</updated>
|
||||
<language>fra</language>
|
||||
<summary>Tania Louis videos</summary>
|
||||
<tags>youtube</tags>
|
||||
<tags>youtube;_category:category_defined_via_tags_only</tags>
|
||||
<link type="text/html" href="/biologie-tout-compris_fr_all_2018-06" />
|
||||
<author>
|
||||
<name>Tania Louis</name>
|
||||
|
@ -61,6 +61,7 @@ const char * sampleOpdsStream = R"(
|
|||
<updated>2019-06-05T00:00::00:Z</updated>
|
||||
<language>fra</language>
|
||||
<summary>Une page de Wikiquote, le recueil des citations libres.</summary>
|
||||
<category>category_defined_via_category_element_only</category>
|
||||
<tags>wikiquote;nopic</tags>
|
||||
<link type="text/html" href="/wikiquote_fr_all_nopic_2019-06" />
|
||||
<author>
|
||||
|
@ -76,7 +77,8 @@ const char * sampleOpdsStream = R"(
|
|||
<updated>2019-06-02T00:00::00:Z</updated>
|
||||
<summary>Une sélection d'articles de Wikipédia sur la géographie</summary>
|
||||
<language>fra</language>
|
||||
<tags>wikipedia;nopic</tags>
|
||||
<category>category_element_overrides_tags</category>
|
||||
<tags>wikipedia;nopic;_category:tags_override_category_element</tags>
|
||||
<link type="text/html" href="/wikipedia_fr_geography_nopic_2019-06" />
|
||||
<author>
|
||||
<name>Wikipedia</name>
|
||||
|
@ -91,7 +93,8 @@ const char * sampleOpdsStream = R"(
|
|||
<updated>2019-05-13T00:00::00:Z</updated>
|
||||
<language>fra</language>
|
||||
<summary>Une</summary>
|
||||
<tags>wikipedia;nopic</tags>
|
||||
<tags>wikipedia;nopic;_category:tags_override_category_element</tags>
|
||||
<category>category_element_overrides_tags</category>
|
||||
<link type="text/html" href="/wikipedia_fr_mathematics_nopic_2019-05" />
|
||||
<author>
|
||||
<name>Wikipedia</name>
|
||||
|
@ -228,6 +231,15 @@ TEST_F(LibraryTest, sanityCheck)
|
|||
EXPECT_EQ(lib.getBooksPublishers().size(), 1U);
|
||||
}
|
||||
|
||||
TEST_F(LibraryTest, categoryHandling)
|
||||
{
|
||||
EXPECT_EQ("", lib.getBookById("0c45160e-f917-760a-9159-dfe3c53cdcdd").getCategory());
|
||||
EXPECT_EQ("category_defined_via_tags_only", lib.getBookById("0d0bcd57-d3f6-cb22-44cc-a723ccb4e1b2").getCategory());
|
||||
EXPECT_EQ("category_defined_via_category_element_only", lib.getBookById("0ea1cde6-441d-6c58-f2c7-21c2838e659f").getCategory());
|
||||
EXPECT_EQ("category_element_overrides_tags", lib.getBookById("1123e574-6eef-6d54-28fc-13e4caeae474").getCategory());
|
||||
EXPECT_EQ("category_element_overrides_tags", lib.getBookById("14829621-c490-c376-0792-9de558b57efa").getCategory());
|
||||
}
|
||||
|
||||
TEST_F(LibraryTest, filterCheck)
|
||||
{
|
||||
auto bookIds = lib.filter(kiwix::Filter());
|
||||
|
|
|
@ -25,7 +25,8 @@ if gtest_dep.found() and not meson.is_cross_build()
|
|||
data_files = [
|
||||
'example.zim',
|
||||
'zimfile.zim',
|
||||
'corner_cases.zim'
|
||||
'corner_cases.zim',
|
||||
'library.xml'
|
||||
]
|
||||
foreach file : data_files
|
||||
# configure_file(input : 'data/' + file,
|
||||
|
|
241
test/server.cpp
241
test/server.cpp
|
@ -53,6 +53,7 @@ public: // types
|
|||
typedef std::vector<std::string> FilePathCollection;
|
||||
|
||||
public: // functions
|
||||
ZimFileServer(int serverPort, std::string libraryFilePath);
|
||||
ZimFileServer(int serverPort, const FilePathCollection& zimpaths);
|
||||
~ZimFileServer();
|
||||
|
||||
|
@ -66,6 +67,9 @@ public: // functions
|
|||
return client->Head(path, headers);
|
||||
}
|
||||
|
||||
private:
|
||||
void run(int serverPort);
|
||||
|
||||
private: // data
|
||||
kiwix::Library library;
|
||||
kiwix::Manager manager;
|
||||
|
@ -74,6 +78,16 @@ private: // data
|
|||
std::unique_ptr<httplib::Client> client;
|
||||
};
|
||||
|
||||
ZimFileServer::ZimFileServer(int serverPort, std::string libraryFilePath)
|
||||
: manager(&this->library)
|
||||
{
|
||||
if ( isRelativePath(libraryFilePath) )
|
||||
libraryFilePath = computeAbsolutePath(getCurrentDirectory(), libraryFilePath);
|
||||
manager.readFile(libraryFilePath, true, true);
|
||||
|
||||
run(serverPort);
|
||||
}
|
||||
|
||||
ZimFileServer::ZimFileServer(int serverPort, const FilePathCollection& zimpaths)
|
||||
: manager(&this->library)
|
||||
{
|
||||
|
@ -82,6 +96,11 @@ ZimFileServer::ZimFileServer(int serverPort, const FilePathCollection& zimpaths)
|
|||
throw std::runtime_error("Unable to add the ZIM file '" + zimpath + "'");
|
||||
}
|
||||
|
||||
run(serverPort);
|
||||
}
|
||||
|
||||
void ZimFileServer::run(int serverPort)
|
||||
{
|
||||
const std::string address = "127.0.0.1";
|
||||
nameMapper.reset(new kiwix::HumanReadableNameMapper(library, false));
|
||||
server.reset(new kiwix::Server(&library, nameMapper.get()));
|
||||
|
@ -546,3 +565,225 @@ TEST_F(ServerTest, RangeHeaderIsCaseInsensitive)
|
|||
EXPECT_EQ(r0->body, r->body);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Testing of the library-related functionality of the server
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class LibraryServerTest : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
std::unique_ptr<ZimFileServer> zfs1_;
|
||||
|
||||
const int PORT = 8002;
|
||||
|
||||
protected:
|
||||
void SetUp() override {
|
||||
zfs1_.reset(new ZimFileServer(PORT, "./test/library.xml"));
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
zfs1_.reset();
|
||||
}
|
||||
};
|
||||
|
||||
// Returns a copy of 'text' with every line that fully matches 'pattern'
|
||||
// replaced with the fixed string 'replacement'
|
||||
std::string replaceLines(const std::string& text,
|
||||
const std::string& pattern,
|
||||
const std::string& replacement)
|
||||
{
|
||||
std::regex regex("^" + pattern + "$");
|
||||
std::ostringstream oss;
|
||||
std::istringstream iss(text);
|
||||
std::string line;
|
||||
while ( std::getline(iss, line) ) {
|
||||
if ( std::regex_match(line, regex) ) {
|
||||
oss << replacement << "\n";
|
||||
} else {
|
||||
oss << line << "\n";
|
||||
}
|
||||
}
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string maskVariableOPDSFeedData(std::string s)
|
||||
{
|
||||
s = replaceLines(s, " <updated>.+</updated>",
|
||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>");
|
||||
s = replaceLines(s, " <id>.+</id>",
|
||||
" <id>12345678-90ab-cdef-1234-567890abcdef</id>");
|
||||
return s;
|
||||
}
|
||||
|
||||
#define OPDS_FEED_TAG \
|
||||
"<feed xmlns=\"http://www.w3.org/2005/Atom\"" \
|
||||
" xmlns:opds=\"http://opds-spec.org/2010/catalog\">\n"
|
||||
|
||||
#define CATALOG_LINK_TAGS \
|
||||
" <link rel=\"self\" href=\"\" type=\"application/atom+xml\" />\n" \
|
||||
" <link rel=\"search\"" \
|
||||
" type=\"application/opensearchdescription+xml\"" \
|
||||
" href=\"catalog/searchdescription.xml\" />\n"
|
||||
|
||||
#define CHARLES_RAY_CATALOG_ENTRY \
|
||||
" <entry>\n" \
|
||||
" <id>urn:uuid:charlesray</id>\n" \
|
||||
" <title>Charles, Ray</title>\n" \
|
||||
" <summary>Wikipedia articles about Charles, Ray</summary>\n" \
|
||||
" <language>eng</language>\n" \
|
||||
" <updated>2020-03-31T00:00::00Z</updated>\n" \
|
||||
" <name>wikipedia_en_ray_charles</name>\n" \
|
||||
" <flavour></flavour>\n" \
|
||||
" <category>jazz</category>\n" \
|
||||
" <tags>unittest;wikipedia;_category:jazz;_pictures:no;_videos:no;_details:no;_ftindex:yes</tags>\n" \
|
||||
" <articleCount>284</articleCount>\n" \
|
||||
" <mediaCount>2</mediaCount>\n" \
|
||||
" <icon>/meta?name=favicon&content=zimfile</icon>\n" \
|
||||
" <link type=\"text/html\" href=\"/zimfile\" />\n" \
|
||||
" <author>\n" \
|
||||
" <name>Wikipedia</name>\n" \
|
||||
" </author>\n" \
|
||||
" <publisher>\n" \
|
||||
" <name>Kiwix</name>\n" \
|
||||
" </publisher>\n" \
|
||||
" <link rel=\"http://opds-spec.org/acquisition/open-access\" type=\"application/x-zim\" href=\"https://github.com/kiwix/kiwix-lib/raw/master/test/data/zimfile.zim\" length=\"569344\" />\n" \
|
||||
" </entry>\n"
|
||||
|
||||
#define RAY_CHARLES_CATALOG_ENTRY \
|
||||
" <entry>\n" \
|
||||
" <id>urn:uuid:raycharles</id>\n" \
|
||||
" <title>Ray Charles</title>\n" \
|
||||
" <summary>Wikipedia articles about Ray Charles</summary>\n" \
|
||||
" <language>eng</language>\n" \
|
||||
" <updated>2020-03-31T00:00::00Z</updated>\n" \
|
||||
" <name>wikipedia_en_ray_charles</name>\n" \
|
||||
" <flavour></flavour>\n" \
|
||||
" <category>wikipedia</category>\n" \
|
||||
" <tags>unittest;wikipedia;_category:wikipedia;_pictures:no;_videos:no;_details:no;_ftindex:yes</tags>\n" \
|
||||
" <articleCount>284</articleCount>\n" \
|
||||
" <mediaCount>2</mediaCount>\n" \
|
||||
" <icon>/meta?name=favicon&content=zimfile</icon>\n" \
|
||||
" <link type=\"text/html\" href=\"/zimfile\" />\n" \
|
||||
" <author>\n" \
|
||||
" <name>Wikipedia</name>\n" \
|
||||
" </author>\n" \
|
||||
" <publisher>\n" \
|
||||
" <name>Kiwix</name>\n" \
|
||||
" </publisher>\n" \
|
||||
" <link rel=\"http://opds-spec.org/acquisition/open-access\" type=\"application/x-zim\" href=\"https://github.com/kiwix/kiwix-lib/raw/master/test/data/zimfile.zim\" length=\"569344\" />\n" \
|
||||
" </entry>\n"
|
||||
|
||||
#define UNCATEGORIZED_RAY_CHARLES_CATALOG_ENTRY \
|
||||
" <entry>\n" \
|
||||
" <id>urn:uuid:raycharles_uncategorized</id>\n" \
|
||||
" <title>Ray Charles</title>\n" \
|
||||
" <summary>Wikipedia articles about Ray Charles</summary>\n" \
|
||||
" <language>eng</language>\n" \
|
||||
" <updated>2020-03-31T00:00::00Z</updated>\n" \
|
||||
" <name>wikipedia_en_ray_charles</name>\n" \
|
||||
" <flavour></flavour>\n" \
|
||||
" <category></category>\n" \
|
||||
" <tags>unittest;wikipedia;_pictures:no;_videos:no;_details:no;_ftindex:yes</tags>\n" \
|
||||
" <articleCount>284</articleCount>\n" \
|
||||
" <mediaCount>2</mediaCount>\n" \
|
||||
" <icon>/meta?name=favicon&content=zimfile</icon>\n" \
|
||||
" <link type=\"text/html\" href=\"/zimfile\" />\n" \
|
||||
" <author>\n" \
|
||||
" <name>Wikipedia</name>\n" \
|
||||
" </author>\n" \
|
||||
" <publisher>\n" \
|
||||
" <name>Kiwix</name>\n" \
|
||||
" </publisher>\n" \
|
||||
" <link rel=\"http://opds-spec.org/acquisition/open-access\" type=\"application/x-zim\" href=\"https://github.com/kiwix/kiwix-lib/raw/master/test/data/zimfile.zim\" length=\"569344\" />\n" \
|
||||
" </entry>\n"
|
||||
|
||||
TEST_F(LibraryServerTest, catalog_root_xml)
|
||||
{
|
||||
const auto r = zfs1_->GET("/catalog/root.xml");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||
OPDS_FEED_TAG
|
||||
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
|
||||
" <title>All zims</title>\n"
|
||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||
CATALOG_LINK_TAGS
|
||||
CHARLES_RAY_CATALOG_ENTRY
|
||||
RAY_CHARLES_CATALOG_ENTRY
|
||||
UNCATEGORIZED_RAY_CHARLES_CATALOG_ENTRY
|
||||
"</feed>\n"
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(LibraryServerTest, catalog_searchdescription_xml)
|
||||
{
|
||||
const auto r = zfs1_->GET("/catalog/searchdescription.xml");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
EXPECT_EQ(r->body,
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||
"<OpenSearchDescription xmlns=\"http://a9.com/-/spec/opensearch/1.1/\">\n"
|
||||
" <ShortName>Zim catalog search</ShortName>\n"
|
||||
" <Description>Search zim files in the catalog.</Description>\n"
|
||||
" <Url type=\"application/atom+xml;profile=opds-catalog\"\n"
|
||||
" xmlns:atom=\"http://www.w3.org/2005/Atom\"\n"
|
||||
" xmlns:k=\"http://kiwix.org/opensearchextension/1.0\"\n"
|
||||
" indexOffset=\"0\"\n"
|
||||
" template=\"//catalog/search?q={searchTerms?}&lang={language?}&name={k:name?}&tag={k:tag?}¬ag={k:notag?}&maxsize={k:maxsize?}&count={count?}&start={startIndex?}\"/>\n"
|
||||
"</OpenSearchDescription>\n"
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(LibraryServerTest, catalog_search_by_text)
|
||||
{
|
||||
const auto r = zfs1_->GET("/catalog/search?q=ray%20charles");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||
OPDS_FEED_TAG
|
||||
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
|
||||
" <title>Search result for ray charles</title>\n"
|
||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||
" <totalResults>2</totalResults>\n"
|
||||
" <startIndex>0</startIndex>\n"
|
||||
" <itemsPerPage>2</itemsPerPage>\n"
|
||||
CATALOG_LINK_TAGS
|
||||
RAY_CHARLES_CATALOG_ENTRY
|
||||
UNCATEGORIZED_RAY_CHARLES_CATALOG_ENTRY
|
||||
"</feed>\n"
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(LibraryServerTest, catalog_search_by_tag)
|
||||
{
|
||||
const auto r = zfs1_->GET("/catalog/search?tag=_category:jazz");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||
OPDS_FEED_TAG
|
||||
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
|
||||
" <title>Search result for <Empty query></title>\n"
|
||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||
" <totalResults>1</totalResults>\n"
|
||||
" <startIndex>0</startIndex>\n"
|
||||
" <itemsPerPage>1</itemsPerPage>\n"
|
||||
CATALOG_LINK_TAGS
|
||||
CHARLES_RAY_CATALOG_ENTRY
|
||||
"</feed>\n"
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(LibraryServerTest, catalog_search_by_category)
|
||||
{
|
||||
const auto r = zfs1_->GET("/catalog/search?category=jazz");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||
OPDS_FEED_TAG
|
||||
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
|
||||
" <title>Search result for <Empty query></title>\n"
|
||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||
" <totalResults>1</totalResults>\n"
|
||||
" <startIndex>0</startIndex>\n"
|
||||
" <itemsPerPage>1</itemsPerPage>\n"
|
||||
CATALOG_LINK_TAGS
|
||||
CHARLES_RAY_CATALOG_ENTRY
|
||||
"</feed>\n"
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue