mirror of https://github.com/kiwix/libkiwix.git
Better API to filter books in a library.
Instead of having a single method `listBooksIds` that tries to be exhaustive about all the filter and sort option, split the method in two separated methods `filter` and `sort`. The `filter` method takes a `Filter` object that represent on what we are filtering. This object has to be construct before calling `filter`. ```cpp Filter filter; filter.query("Astring"); filter.acceptTags({"nopic"}); // return all book in eng and with "Astring" in the tile or description". library.filter(filter); //equivalent to library.listBooksIds(ALL, UNSORTED, "Astring", "", "", "", {"nopic"}); // or better library.filter(Filter().query("Astring").acceptTags({"nopic"})); ``` The method `listBooksIds` has been marked as deprecated. Add a small test on the library.
This commit is contained in:
parent
21592af8b2
commit
31c9375a3a
|
@ -26,6 +26,7 @@
|
||||||
|
|
||||||
#include "book.h"
|
#include "book.h"
|
||||||
#include "bookmark.h"
|
#include "bookmark.h"
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
#define KIWIX_LIBRARY_VERSION "20110515"
|
#define KIWIX_LIBRARY_VERSION "20110515"
|
||||||
|
|
||||||
|
@ -44,6 +45,65 @@ enum supportedListMode {
|
||||||
VALID = 1 << 4,
|
VALID = 1 << 4,
|
||||||
NOVALID = 1 << 5
|
NOVALID = 1 << 5
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class Filter {
|
||||||
|
private:
|
||||||
|
uint64_t activeFilters;
|
||||||
|
std::vector<std::string> _acceptTags;
|
||||||
|
std::vector<std::string> _rejectTags;
|
||||||
|
std::string _lang;
|
||||||
|
std::string _publisher;
|
||||||
|
std::string _creator;
|
||||||
|
size_t _maxSize;
|
||||||
|
std::string _query;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Filter();
|
||||||
|
~Filter() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the filter to check local.
|
||||||
|
*
|
||||||
|
* A local book is a book with a path.
|
||||||
|
* If accept is true, only local book are accepted.
|
||||||
|
* If accept is false, only non local book are accepted.
|
||||||
|
*/
|
||||||
|
Filter& local(bool accept);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the filter to check remote.
|
||||||
|
*
|
||||||
|
* A remote book is a book with a url.
|
||||||
|
* If accept is true, only remote book are accepted.
|
||||||
|
* If accept is false, only non remote book are accepted.
|
||||||
|
*/
|
||||||
|
Filter& remote(bool accept);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the filter to check validity.
|
||||||
|
*
|
||||||
|
* A valid book is a book with a path pointing to a existing zim file.
|
||||||
|
* If accept is true, only valid book are accepted.
|
||||||
|
* If accept is false, only non valid book are accepted.
|
||||||
|
*/
|
||||||
|
Filter& valid(bool accept);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the filter to only accept book with corresponding tag.
|
||||||
|
*/
|
||||||
|
Filter& acceptTags(std::vector<std::string> tags);
|
||||||
|
Filter& rejectTags(std::vector<std::string> tags);
|
||||||
|
|
||||||
|
Filter& lang(std::string lang);
|
||||||
|
Filter& publisher(std::string publisher);
|
||||||
|
Filter& creator(std::string creator);
|
||||||
|
Filter& maxSize(size_t size);
|
||||||
|
Filter& query(std::string query);
|
||||||
|
|
||||||
|
bool accept(const Book& book) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Library store several books.
|
* A Library store several books.
|
||||||
*/
|
*/
|
||||||
|
@ -162,9 +222,27 @@ class Library
|
||||||
* @param search List only books with search in the title or description.
|
* @param search List only books with search in the title or description.
|
||||||
* @return The list of bookIds corresponding to the query.
|
* @return The list of bookIds corresponding to the query.
|
||||||
*/
|
*/
|
||||||
std::vector<std::string> filter(const std::string& search);
|
DEPRECATED std::vector<std::string> filter(const std::string& search);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter the library and return the id of the keep elements.
|
||||||
|
*
|
||||||
|
* @param filter The filter to use.
|
||||||
|
* @return The list of bookIds corresponding to the filter.
|
||||||
|
*/
|
||||||
|
std::vector<std::string> filter(const Filter& filter);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort (in place) bookIds using the given comparator.
|
||||||
|
*
|
||||||
|
* @param bookIds the list of book Ids to sort
|
||||||
|
* @param comparator how to sort the books
|
||||||
|
* @return The sorted list of books
|
||||||
|
*/
|
||||||
|
void sort(std::vector<std::string>& bookIds, supportedListSortBy sortBy, bool ascending);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List books in the library.
|
* List books in the library.
|
||||||
*
|
*
|
||||||
|
@ -187,7 +265,7 @@ class Library
|
||||||
* Set to 0 to cancel this filter.
|
* Set to 0 to cancel this filter.
|
||||||
* @return The list of bookIds corresponding to the query.
|
* @return The list of bookIds corresponding to the query.
|
||||||
*/
|
*/
|
||||||
std::vector<std::string> listBooksIds(
|
DEPRECATED std::vector<std::string> listBooksIds(
|
||||||
int supportedListMode = ALL,
|
int supportedListMode = ALL,
|
||||||
supportedListSortBy sortBy = UNSORTED,
|
supportedListSortBy sortBy = UNSORTED,
|
||||||
const std::string& search = "",
|
const std::string& search = "",
|
||||||
|
|
360
src/library.cpp
360
src/library.cpp
|
@ -96,14 +96,16 @@ unsigned int Library::getBookCount(const bool localBooks,
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Library::writeToFile(const std::string& path) {
|
bool Library::writeToFile(const std::string& path)
|
||||||
|
{
|
||||||
auto baseDir = removeLastPathElement(path, true, false);
|
auto baseDir = removeLastPathElement(path, true, false);
|
||||||
LibXMLDumper dumper(this);
|
LibXMLDumper dumper(this);
|
||||||
dumper.setBaseDir(baseDir);
|
dumper.setBaseDir(baseDir);
|
||||||
return writeTextFile(path, dumper.dumpLibXMLContent(getBooksIds()));
|
return writeTextFile(path, dumper.dumpLibXMLContent(getBooksIds()));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Library::writeBookmarksToFile(const std::string& path) {
|
bool Library::writeBookmarksToFile(const std::string& path)
|
||||||
|
{
|
||||||
LibXMLDumper dumper(this);
|
LibXMLDumper dumper(this);
|
||||||
return writeTextFile(path, dumper.dumpLibXMLBookmark());
|
return writeTextFile(path, dumper.dumpLibXMLBookmark());
|
||||||
}
|
}
|
||||||
|
@ -182,67 +184,104 @@ std::vector<std::string> Library::filter(const std::string& search)
|
||||||
return getBooksIds();
|
return getBooksIds();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return filter(Filter().query(search));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<std::string> Library::filter(const Filter& filter)
|
||||||
|
{
|
||||||
std::vector<std::string> bookIds;
|
std::vector<std::string> bookIds;
|
||||||
for(auto& pair:m_books) {
|
for(auto& pair:m_books) {
|
||||||
auto& book = pair.second;
|
auto book = pair.second;
|
||||||
if (matchRegex(book.getTitle(), "\\Q" + search + "\\E")
|
if(filter.accept(book)) {
|
||||||
|| matchRegex(book.getDescription(), "\\Q" + search + "\\E")) {
|
|
||||||
bookIds.push_back(pair.first);
|
bookIds.push_back(pair.first);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return bookIds;
|
return bookIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<supportedListSortBy sort>
|
template<supportedListSortBy SORT>
|
||||||
struct Comparator {
|
struct KEY_TYPE {
|
||||||
Library* lib;
|
typedef std::string TYPE;
|
||||||
Comparator(Library* lib) : lib(lib) {}
|
|
||||||
|
|
||||||
bool operator() (const std::string& id1, const std::string& id2) {
|
|
||||||
return get_keys(id1) < get_keys(id2);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string get_keys(const std::string& id);
|
|
||||||
unsigned int get_keyi(const std::string& id);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
std::string Comparator<TITLE>::get_keys(const std::string& id)
|
struct KEY_TYPE<SIZE> {
|
||||||
|
typedef size_t TYPE;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<supportedListSortBy sort>
|
||||||
|
class Comparator {
|
||||||
|
private:
|
||||||
|
Library* lib;
|
||||||
|
bool ascending;
|
||||||
|
|
||||||
|
inline typename KEY_TYPE<sort>::TYPE get_key(const std::string& id);
|
||||||
|
|
||||||
|
public:
|
||||||
|
Comparator(Library* lib, bool ascending) : lib(lib), ascending(ascending) {}
|
||||||
|
inline bool operator() (const std::string& id1, const std::string& id2) {
|
||||||
|
if (ascending) {
|
||||||
|
return get_key(id1) < get_key(id2);
|
||||||
|
} else {
|
||||||
|
return get_key(id2) < get_key(id1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
std::string Comparator<TITLE>::get_key(const std::string& id)
|
||||||
{
|
{
|
||||||
return lib->getBookById(id).getTitle();
|
return lib->getBookById(id).getTitle();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
unsigned int Comparator<SIZE>::get_keyi(const std::string& id)
|
size_t Comparator<SIZE>::get_key(const std::string& id)
|
||||||
{
|
{
|
||||||
return lib->getBookById(id).getSize();
|
return lib->getBookById(id).getSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
bool Comparator<SIZE>::operator() (const std::string& id1, const std::string& id2)
|
std::string Comparator<DATE>::get_key(const std::string& id)
|
||||||
{
|
|
||||||
return get_keyi(id1) < get_keyi(id2);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<>
|
|
||||||
std::string Comparator<DATE>::get_keys(const std::string& id)
|
|
||||||
{
|
{
|
||||||
return lib->getBookById(id).getDate();
|
return lib->getBookById(id).getDate();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
std::string Comparator<CREATOR>::get_keys(const std::string& id)
|
std::string Comparator<CREATOR>::get_key(const std::string& id)
|
||||||
{
|
{
|
||||||
return lib->getBookById(id).getCreator();
|
return lib->getBookById(id).getCreator();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
std::string Comparator<PUBLISHER>::get_keys(const std::string& id)
|
std::string Comparator<PUBLISHER>::get_key(const std::string& id)
|
||||||
{
|
{
|
||||||
return lib->getBookById(id).getPublisher();
|
return lib->getBookById(id).getPublisher();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Library::sort(std::vector<std::string>& bookIds, supportedListSortBy sort, bool ascending)
|
||||||
|
{
|
||||||
|
switch(sort) {
|
||||||
|
case TITLE:
|
||||||
|
std::sort(bookIds.begin(), bookIds.end(), Comparator<TITLE>(this, ascending));
|
||||||
|
break;
|
||||||
|
case SIZE:
|
||||||
|
std::sort(bookIds.begin(), bookIds.end(), Comparator<SIZE>(this, ascending));
|
||||||
|
break;
|
||||||
|
case DATE:
|
||||||
|
std::sort(bookIds.begin(), bookIds.end(), Comparator<DATE>(this, ascending));
|
||||||
|
break;
|
||||||
|
case CREATOR:
|
||||||
|
std::sort(bookIds.begin(), bookIds.end(), Comparator<CREATOR>(this, ascending));
|
||||||
|
break;
|
||||||
|
case PUBLISHER:
|
||||||
|
std::sort(bookIds.begin(), bookIds.end(), Comparator<PUBLISHER>(this, ascending));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
std::vector<std::string> Library::listBooksIds(
|
std::vector<std::string> Library::listBooksIds(
|
||||||
int mode,
|
int mode,
|
||||||
|
@ -254,74 +293,205 @@ std::vector<std::string> Library::listBooksIds(
|
||||||
const std::vector<std::string>& tags,
|
const std::vector<std::string>& tags,
|
||||||
size_t maxSize) {
|
size_t maxSize) {
|
||||||
|
|
||||||
std::vector<std::string> bookIds;
|
Filter _filter;
|
||||||
for(auto& pair:m_books) {
|
if (mode & LOCAL)
|
||||||
auto& book = pair.second;
|
_filter.local(true);
|
||||||
auto local = !book.getPath().empty();
|
if (mode & NOLOCAL)
|
||||||
if (mode & LOCAL && !local)
|
_filter.local(false);
|
||||||
continue;
|
if (mode & VALID)
|
||||||
if (mode & NOLOCAL && local)
|
_filter.valid(true);
|
||||||
continue;
|
if (mode & NOVALID)
|
||||||
auto valid = book.isPathValid();
|
_filter.valid(false);
|
||||||
if (mode & VALID && !valid)
|
if (mode & REMOTE)
|
||||||
continue;
|
_filter.remote(true);
|
||||||
if (mode & NOVALID && valid)
|
if (mode & NOREMOTE)
|
||||||
continue;
|
_filter.remote(false);
|
||||||
auto remote = !book.getUrl().empty();
|
if (!tags.empty())
|
||||||
if (mode & REMOTE && !remote)
|
_filter.acceptTags(tags);
|
||||||
continue;
|
if (maxSize != 0)
|
||||||
if (mode & NOREMOTE && remote)
|
_filter.maxSize(maxSize);
|
||||||
continue;
|
if (!language.empty())
|
||||||
if (!tags.empty()) {
|
_filter.lang(language);
|
||||||
auto vBookTags = split(book.getTags(), ";");
|
if (!publisher.empty())
|
||||||
std::set<std::string> sBookTags(vBookTags.begin(), vBookTags.end());
|
_filter.publisher(publisher);
|
||||||
bool ok = true;
|
if (!creator.empty())
|
||||||
for (auto& t: tags) {
|
_filter.creator(creator);
|
||||||
if (sBookTags.find(t) == sBookTags.end()) {
|
if (!search.empty())
|
||||||
// A "filter" tag is not in the book tag.
|
_filter.query(search);
|
||||||
// No need to loop for all "filter" tags.
|
|
||||||
ok = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (! ok ) {
|
|
||||||
// Skip the book
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (maxSize != 0 && book.getSize() > maxSize)
|
|
||||||
continue;
|
|
||||||
if (!language.empty() && book.getLanguage() != language)
|
|
||||||
continue;
|
|
||||||
if (!publisher.empty() && book.getPublisher() != publisher)
|
|
||||||
continue;
|
|
||||||
if (!creator.empty() && book.getCreator() != creator)
|
|
||||||
continue;
|
|
||||||
if (!search.empty() && !(matchRegex(book.getTitle(), "\\Q" + search + "\\E")
|
|
||||||
|| matchRegex(book.getDescription(), "\\Q" + search + "\\E")))
|
|
||||||
continue;
|
|
||||||
bookIds.push_back(pair.first);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch(sortBy) {
|
auto bookIds = filter(_filter);
|
||||||
case TITLE:
|
|
||||||
std::sort(bookIds.begin(), bookIds.end(), Comparator<TITLE>(this));
|
sort(bookIds, sortBy, true);
|
||||||
break;
|
|
||||||
case SIZE:
|
|
||||||
std::sort(bookIds.begin(), bookIds.end(), Comparator<SIZE>(this));
|
|
||||||
break;
|
|
||||||
case DATE:
|
|
||||||
std::sort(bookIds.begin(), bookIds.end(), Comparator<DATE>(this));
|
|
||||||
break;
|
|
||||||
case CREATOR:
|
|
||||||
std::sort(bookIds.begin(), bookIds.end(), Comparator<CREATOR>(this));
|
|
||||||
break;
|
|
||||||
case PUBLISHER:
|
|
||||||
std::sort(bookIds.begin(), bookIds.end(), Comparator<PUBLISHER>(this));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return bookIds;
|
return bookIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Filter::Filter()
|
||||||
|
: activeFilters(0),
|
||||||
|
_maxSize(0)
|
||||||
|
{};
|
||||||
|
|
||||||
|
#define FLAG(x) (1 << x)
|
||||||
|
enum filterTypes {
|
||||||
|
NONE = 0,
|
||||||
|
_LOCAL = FLAG(0),
|
||||||
|
_REMOTE = FLAG(1),
|
||||||
|
_NOLOCAL = FLAG(2),
|
||||||
|
_NOREMOTE = FLAG(3),
|
||||||
|
_VALID = FLAG(4),
|
||||||
|
_NOVALID = FLAG(5),
|
||||||
|
ACCEPTTAGS = FLAG(6),
|
||||||
|
REJECTTAGS = FLAG(7),
|
||||||
|
LANG = FLAG(8),
|
||||||
|
_PUBLISHER = FLAG(9),
|
||||||
|
_CREATOR = FLAG(10),
|
||||||
|
MAXSIZE = FLAG(11),
|
||||||
|
QUERY = FLAG(12),
|
||||||
|
};
|
||||||
|
|
||||||
|
Filter& Filter::local(bool accept)
|
||||||
|
{
|
||||||
|
if (accept) {
|
||||||
|
activeFilters |= _LOCAL;
|
||||||
|
activeFilters &= ~_NOLOCAL;
|
||||||
|
} else {
|
||||||
|
activeFilters |= _NOLOCAL;
|
||||||
|
activeFilters &= ~_LOCAL;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Filter& Filter::remote(bool accept)
|
||||||
|
{
|
||||||
|
if (accept) {
|
||||||
|
activeFilters |= _REMOTE;
|
||||||
|
activeFilters &= ~_NOREMOTE;
|
||||||
|
} else {
|
||||||
|
activeFilters |= _NOREMOTE;
|
||||||
|
activeFilters &= ~_REMOTE;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Filter& Filter::valid(bool accept)
|
||||||
|
{
|
||||||
|
if (accept) {
|
||||||
|
activeFilters |= _VALID;
|
||||||
|
activeFilters &= ~_NOVALID;
|
||||||
|
} else {
|
||||||
|
activeFilters |= _NOVALID;
|
||||||
|
activeFilters &= ~_VALID;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Filter& Filter::acceptTags(std::vector<std::string> tags)
|
||||||
|
{
|
||||||
|
_acceptTags = tags;
|
||||||
|
activeFilters |= ACCEPTTAGS;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Filter& Filter::rejectTags(std::vector<std::string> tags)
|
||||||
|
{
|
||||||
|
_rejectTags = tags;
|
||||||
|
activeFilters |= REJECTTAGS;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Filter& Filter::lang(std::string lang)
|
||||||
|
{
|
||||||
|
_lang = lang;
|
||||||
|
activeFilters |= LANG;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Filter& Filter::publisher(std::string publisher)
|
||||||
|
{
|
||||||
|
_publisher = publisher;
|
||||||
|
activeFilters |= _PUBLISHER;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Filter& Filter::creator(std::string creator)
|
||||||
|
{
|
||||||
|
_creator = creator;
|
||||||
|
activeFilters |= _CREATOR;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Filter& Filter::maxSize(size_t maxSize)
|
||||||
|
{
|
||||||
|
_maxSize = maxSize;
|
||||||
|
activeFilters |= MAXSIZE;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Filter& Filter::query(std::string query)
|
||||||
|
{
|
||||||
|
_query = query;
|
||||||
|
activeFilters |= QUERY;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ACTIVE(X) (activeFilters & (X))
|
||||||
|
bool Filter::accept(const Book& book) const
|
||||||
|
{
|
||||||
|
auto local = !book.getPath().empty();
|
||||||
|
if (ACTIVE(_LOCAL) && !local)
|
||||||
|
return false;
|
||||||
|
if (ACTIVE(_NOLOCAL) && local)
|
||||||
|
return false;
|
||||||
|
auto valid = book.isPathValid();
|
||||||
|
if (ACTIVE(_VALID) && !valid)
|
||||||
|
return false;
|
||||||
|
if (ACTIVE(_NOVALID) && valid)
|
||||||
|
return false;
|
||||||
|
auto remote = !book.getUrl().empty();
|
||||||
|
if (ACTIVE(_REMOTE) && !remote)
|
||||||
|
return false;
|
||||||
|
if (ACTIVE(_NOREMOTE) && remote)
|
||||||
|
return false;
|
||||||
|
if (ACTIVE(ACCEPTTAGS)) {
|
||||||
|
if (!_acceptTags.empty()) {
|
||||||
|
auto vBookTags = split(book.getTags(), ";");
|
||||||
|
std::set<std::string> sBookTags(vBookTags.begin(), vBookTags.end());
|
||||||
|
for (auto& t: _acceptTags) {
|
||||||
|
if (sBookTags.find(t) == sBookTags.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ACTIVE(REJECTTAGS)) {
|
||||||
|
if (!_rejectTags.empty()) {
|
||||||
|
auto vBookTags = split(book.getTags(), ";");
|
||||||
|
std::set<std::string> sBookTags(vBookTags.begin(), vBookTags.end());
|
||||||
|
for (auto& t: _rejectTags) {
|
||||||
|
if (sBookTags.find(t) != sBookTags.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ACTIVE(MAXSIZE) && book.getSize() > _maxSize)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (ACTIVE(LANG) && book.getLanguage() != _lang)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (ACTIVE(_PUBLISHER) && book.getPublisher() != _publisher)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (ACTIVE(_CREATOR) && book.getCreator() != _creator)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if ( ACTIVE(QUERY)
|
||||||
|
&& !(matchRegex(book.getTitle(), "\\Q" + _query + "\\E")
|
||||||
|
|| matchRegex(book.getDescription(), "\\Q" + _query + "\\E")))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,241 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2013 Tommi Maekitalo
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License as
|
||||||
|
* published by the Free Software Foundation; either version 2 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but
|
||||||
|
* is provided AS IS, WITHOUT ANY WARRANTY; without even the implied
|
||||||
|
* warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and
|
||||||
|
* NON-INFRINGEMENT. See the GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
|
||||||
|
const char * sampleOpdsStream = R"(
|
||||||
|
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:opds="http://opds-spec.org/2010/catalog">
|
||||||
|
<id>00000000-0000-0000-0000-000000000000</id>
|
||||||
|
<entry>
|
||||||
|
<title>Encyclopédie de la Tunisie</title>
|
||||||
|
<id>urn:uuid:0c45160e-f917-760a-9159-dfe3c53cdcdd</id>
|
||||||
|
<icon>/meta?name=favicon&content=wikipedia_fr_tunisie_novid_2018-10</icon>
|
||||||
|
<updated>2018-10-08T00:00::00:Z</updated>
|
||||||
|
<language>fra</language>
|
||||||
|
<summary>Le meilleur de Wikipédia sur la Tunisie</summary>
|
||||||
|
<tags>wikipedia;novid;_ftindex</tags>
|
||||||
|
<link type="text/html" href="/wikipedia_fr_tunisie_novid_2018-10" />
|
||||||
|
<author>
|
||||||
|
<name>Wikipedia</name>
|
||||||
|
</author>
|
||||||
|
<link rel="http://opds-spec.org/acquisition/open-access" type="application/x-zim" href="http://download.kiwix.org/zim/wikipedia/wikipedia_fr_tunisie_novid_2018-10.zim.meta4" length="90030080" />
|
||||||
|
<link rel="http://opds-spec.org/image/thumbnail" type="image/png" href="/meta?name=favicon&content=wikipedia_fr_tunisie_novid_2018-10" />
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<title>Tania Louis</title>
|
||||||
|
<id>urn:uuid:0d0bcd57-d3f6-cb22-44cc-a723ccb4e1b2</id>
|
||||||
|
<icon>/meta?name=favicon&content=biologie-tout-compris_fr_all_2018-06</icon>
|
||||||
|
<updated>2018-06-23T00:00::00:Z</updated>
|
||||||
|
<language>fra</language>
|
||||||
|
<summary>Tania Louis videos</summary>
|
||||||
|
<tags>youtube</tags>
|
||||||
|
<link type="text/html" href="/biologie-tout-compris_fr_all_2018-06" />
|
||||||
|
<author>
|
||||||
|
<name>Tania Louis</name>
|
||||||
|
</author>
|
||||||
|
<link rel="http://opds-spec.org/acquisition/open-access" type="application/x-zim" href="http://download.kiwix.org/zim/other/biologie-tout-compris_fr_all_2018-06.zim.meta4" length="2172639232" />
|
||||||
|
<link rel="http://opds-spec.org/image/thumbnail" type="image/png" href="/meta?name=favicon&content=biologie-tout-compris_fr_all_2018-06" />
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<title>Wikiquote</title>
|
||||||
|
<id>urn:uuid:0ea1cde6-441d-6c58-f2c7-21c2838e659f</id>
|
||||||
|
<icon>/meta?name=favicon&content=wikiquote_fr_all_nopic_2019-06</icon>
|
||||||
|
<updated>2019-06-05T00:00::00:Z</updated>
|
||||||
|
<language>fra</language>
|
||||||
|
<summary>Une page de Wikiquote, le recueil des citations libres.</summary>
|
||||||
|
<tags>wikiquote;nopic</tags>
|
||||||
|
<link type="text/html" href="/wikiquote_fr_all_nopic_2019-06" />
|
||||||
|
<author>
|
||||||
|
<name>Wikiquote</name>
|
||||||
|
</author>
|
||||||
|
<link rel="http://opds-spec.org/acquisition/open-access" type="application/x-zim" href="http://download.kiwix.org/zim/wikiquote/wikiquote_fr_all_nopic_2019-06.zim.meta4" length="21368832" />
|
||||||
|
<link rel="http://opds-spec.org/image/thumbnail" type="image/png" href="/meta?name=favicon&content=wikiquote_fr_all_nopic_2019-06" />
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<title>Géographie par Wikipédia</title>
|
||||||
|
<id>urn:uuid:1123e574-6eef-6d54-28fc-13e4caeae474</id>
|
||||||
|
<icon>/meta?name=favicon&content=wikipedia_fr_geography_nopic_2019-06</icon>
|
||||||
|
<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>
|
||||||
|
<link type="text/html" href="/wikipedia_fr_geography_nopic_2019-06" />
|
||||||
|
<author>
|
||||||
|
<name>Wikipedia</name>
|
||||||
|
</author>
|
||||||
|
<link rel="http://opds-spec.org/acquisition/open-access" type="application/x-zim" href="http://download.kiwix.org/zim/wikipedia/wikipedia_fr_geography_nopic_2019-06.zim.meta4" length="157586432" />
|
||||||
|
<link rel="http://opds-spec.org/image/thumbnail" type="image/png" href="/meta?name=favicon&content=wikipedia_fr_geography_nopic_2019-06" />
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<title>Mathématiques</title>
|
||||||
|
<id>urn:uuid:14829621-c490-c376-0792-9de558b57efa</id>
|
||||||
|
<icon>/meta?name=favicon&content=wikipedia_fr_mathematics_nopic_2019-05</icon>
|
||||||
|
<updated>2019-05-13T00:00::00:Z</updated>
|
||||||
|
<language>fra</language>
|
||||||
|
<summary>Une</summary>
|
||||||
|
<tags>wikipedia;nopic</tags>
|
||||||
|
<link type="text/html" href="/wikipedia_fr_mathematics_nopic_2019-05" />
|
||||||
|
<author>
|
||||||
|
<name>Wikipedia</name>
|
||||||
|
</author>
|
||||||
|
<link rel="http://opds-spec.org/acquisition/open-access" type="application/x-zim" href="http://download.kiwix.org/zim/wikipedia/wikipedia_fr_mathematics_nopic_2019-05.zim.meta4" length="223368192" />
|
||||||
|
<link rel="http://opds-spec.org/image/thumbnail" type="image/png" href="/meta?name=favicon&content=wikipedia_fr_mathematics_nopic_2019-05" />
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<title>Granblue Fantasy Wiki</title>
|
||||||
|
<id>urn:uuid:006cbd1b-16d8-b00d-a584-c1ae110a94ed</id>
|
||||||
|
<icon>/meta?name=favicon&content=granbluefantasy_en_all_all_nopic_2018-10</icon>
|
||||||
|
<updated>2018-10-14T00:00::00:Z</updated>
|
||||||
|
<language>eng</language>
|
||||||
|
<summary>Granblue Fantasy Wiki</summary>
|
||||||
|
<tags>gbf;nopic;_ftindex</tags>
|
||||||
|
<link type="text/html" href="/granbluefantasy_en_all_all_nopic_2018-10" />
|
||||||
|
<author>
|
||||||
|
<name>Wiki</name>
|
||||||
|
</author>
|
||||||
|
<link rel="http://opds-spec.org/acquisition/open-access" type="application/x-zim" href="http://download.kiwix.org/zim/other/granbluefantasy_en_all_all_nopic_2018-10.zim.meta4" length="23197696" />
|
||||||
|
<link rel="http://opds-spec.org/image/thumbnail" type="image/png" href="/meta?name=favicon&content=granbluefantasy_en_all_all_nopic_2018-10" />
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<title>Movies & TV Stack Exchange</title>
|
||||||
|
<id>urn:uuid:00f37b00-f4da-0675-995a-770f9c72903e</id>
|
||||||
|
<icon>/meta?name=favicon&content=movies.stackexchange.com_en_all_2019-02</icon>
|
||||||
|
<updated>2019-02-03T00:00::00:Z</updated>
|
||||||
|
<language>eng</language>
|
||||||
|
<summary>Q&A for movie and tv enthusiasts</summary>
|
||||||
|
<tags>stackexchange;_ftindex</tags>
|
||||||
|
<link type="text/html" href="/movies.stackexchange.com_en_all_2019-02" />
|
||||||
|
<author>
|
||||||
|
<name>Movies & TV Stack Exchange</name>
|
||||||
|
</author>
|
||||||
|
<link rel="http://opds-spec.org/acquisition/open-access" type="application/x-zim" href="http://download.kiwix.org/zim/stack_exchange/movies.stackexchange.com_en_all_2019-02.zim.meta4" length="859463680" />
|
||||||
|
<link rel="http://opds-spec.org/image/thumbnail" type="image/png" href="/meta?name=favicon&content=movies.stackexchange.com_en_all_2019-02" />
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<title>TED talks - Business</title>
|
||||||
|
<id>urn:uuid:0189d9be-2fd0-b4b6-7300-20fab0b5cdc8</id>
|
||||||
|
<icon>/meta?name=favicon&content=ted_en_business_2018-07</icon>
|
||||||
|
<updated>2018-07-23T00:00::00:Z</updated>
|
||||||
|
<language>eng</language>
|
||||||
|
<summary>Ideas worth spreading</summary>
|
||||||
|
<tags></tags>
|
||||||
|
<link type="text/html" href="/ted_en_business_2018-07" />
|
||||||
|
<author>
|
||||||
|
<name>TED</name>
|
||||||
|
</author>
|
||||||
|
<link rel="http://opds-spec.org/acquisition/open-access" type="application/x-zim" href="http://download.kiwix.org/zim/ted/ted_en_business_2018-07.zim.meta4" length="8855827456" />
|
||||||
|
<link rel="http://opds-spec.org/image/thumbnail" type="image/png" href="/meta?name=favicon&content=ted_en_business_2018-07" />
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<title>Mythology & Folklore Stack Exchange</title>
|
||||||
|
<id>urn:uuid:028055ac-4acc-1d54-65e0-a96de45e1b22</id>
|
||||||
|
<icon>/meta?name=favicon&content=mythology.stackexchange.com_en_all_2019-02</icon>
|
||||||
|
<updated>2019-02-03T00:00::00:Z</updated>
|
||||||
|
<language>eng</language>
|
||||||
|
<summary>Q&A for enthusiasts and scholars of mythology and folklore</summary>
|
||||||
|
<tags>stackexchange;_ftindex</tags>
|
||||||
|
<link type="text/html" href="/mythology.stackexchange.com_en_all_2019-02" />
|
||||||
|
<author>
|
||||||
|
<name>Mythology & Folklore Stack Exchange</name>
|
||||||
|
</author>
|
||||||
|
<link rel="http://opds-spec.org/acquisition/open-access" type="application/x-zim" href="http://download.kiwix.org/zim/stack_exchange/mythology.stackexchange.com_en_all_2019-02.zim.meta4" length="47005696" />
|
||||||
|
<link rel="http://opds-spec.org/image/thumbnail" type="image/png" href="/meta?name=favicon&content=mythology.stackexchange.com_en_all_2019-02" />
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<title>Islam Stack Exchange</title>
|
||||||
|
<id>urn:uuid:02e9c7ff-36fc-9c6e-6ac7-cd7085989029</id>
|
||||||
|
<icon>/meta?name=favicon&content=islam.stackexchange.com_en_all_2019-01</icon>
|
||||||
|
<updated>2019-01-31T00:00::00:Z</updated>
|
||||||
|
<language>eng</language>
|
||||||
|
<summary>Q&A for Muslims, experts in Islam, and those interested in learning more about Islam</summary>
|
||||||
|
<tags>stackexchange;_ftindex</tags>
|
||||||
|
<link type="text/html" href="/islam.stackexchange.com_en_all_2019-01" />
|
||||||
|
<author>
|
||||||
|
<name>Islam Stack Exchange</name>
|
||||||
|
</author>
|
||||||
|
<link rel="http://opds-spec.org/acquisition/open-access" type="application/x-zim" href="http://download.kiwix.org/zim/stack_exchange/islam.stackexchange.com_en_all_2019-01.zim.meta4" length="135346176" />
|
||||||
|
<link rel="http://opds-spec.org/image/thumbnail" type="image/png" href="/meta?name=favicon&content=islam.stackexchange.com_en_all_2019-01" />
|
||||||
|
</entry>
|
||||||
|
</feed>
|
||||||
|
|
||||||
|
)";
|
||||||
|
|
||||||
|
#include "../include/library.h"
|
||||||
|
#include "../include/manager.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
class LibraryTest : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
void SetUp() override {
|
||||||
|
kiwix::Manager manager(&lib);
|
||||||
|
manager.readOpds(sampleOpdsStream, "foo.urlHost");
|
||||||
|
}
|
||||||
|
|
||||||
|
kiwix::Library lib;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(LibraryTest, sanityCheck)
|
||||||
|
{
|
||||||
|
EXPECT_EQ(lib.getBookCount(true, true), 10U);
|
||||||
|
EXPECT_EQ(lib.getBooksLanguages().size(), 2U);
|
||||||
|
EXPECT_EQ(lib.getBooksCreators().size(), 8U);
|
||||||
|
EXPECT_EQ(lib.getBooksPublishers().size(), 1U);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LibraryTest, filterCheck)
|
||||||
|
{
|
||||||
|
auto bookIds = lib.filter(kiwix::Filter());
|
||||||
|
EXPECT_EQ(bookIds, lib.getBooksIds());
|
||||||
|
|
||||||
|
bookIds = lib.filter(kiwix::Filter().lang("eng"));
|
||||||
|
EXPECT_EQ(bookIds.size(), 5U);
|
||||||
|
|
||||||
|
bookIds = lib.filter(kiwix::Filter().acceptTags({"stackexchange"}));
|
||||||
|
EXPECT_EQ(bookIds.size(), 3U);
|
||||||
|
|
||||||
|
bookIds = lib.filter(kiwix::Filter().acceptTags({"wikipedia"}));
|
||||||
|
EXPECT_EQ(bookIds.size(), 3U);
|
||||||
|
|
||||||
|
bookIds = lib.filter(kiwix::Filter().acceptTags({"wikipedia", "nopic"}));
|
||||||
|
EXPECT_EQ(bookIds.size(), 2U);
|
||||||
|
|
||||||
|
bookIds = lib.filter(kiwix::Filter().acceptTags({"wikipedia"}).rejectTags({"nopic"}));
|
||||||
|
EXPECT_EQ(bookIds.size(), 1U);
|
||||||
|
|
||||||
|
bookIds = lib.filter(kiwix::Filter().query("folklore"));
|
||||||
|
EXPECT_EQ(bookIds.size(), 1U);
|
||||||
|
|
||||||
|
bookIds = lib.filter(kiwix::Filter().query("Wiki"));
|
||||||
|
EXPECT_EQ(bookIds.size(), 3U);
|
||||||
|
|
||||||
|
bookIds = lib.filter(kiwix::Filter().query("Wiki").creator("Wiki"));
|
||||||
|
EXPECT_EQ(bookIds.size(), 1U);
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
::testing::InitGoogleTest(&argc, argv);
|
||||||
|
return RUN_ALL_TESTS();
|
||||||
|
}
|
|
@ -1,7 +1,8 @@
|
||||||
|
|
||||||
|
|
||||||
tests = [
|
tests = [
|
||||||
'parseUrl'
|
'parseUrl',
|
||||||
|
'library'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue