Compare commits

..

4 Commits

Author SHA1 Message Date
Matthieu Gautier
45b3cfca16 Merge pull request #999 from kiwix/const-abi 2023-09-14 13:42:43 +02:00
Kunal Mehta
65b3c8442f Revert ABI breakage in kiwix::Downloader::getDownloadIds()
Changing it to be `const` is an ABI change since the symbol changes from
`_ZN5...` to `_ZNK5...` (addition of "K").

The other changes made in 18b7b5f27 are probably fine to keep since they
don't appear to be used from what I can tell.

Fixes https://github.com/kiwix/libkiwix/issues/998.
2023-09-14 13:20:41 +02:00
Matthieu Gautier
e461f2b437 New version 12.1.1 2023-07-31 16:50:30 +02:00
Matthieu Gautier
313ecc3f19 Revert "Make Downloader return shared_ptr instead of raw pointer."
This reverts commit 0e612de4d1.
2023-07-27 15:31:07 +02:00
76 changed files with 799 additions and 2442 deletions

View File

@@ -8,7 +8,7 @@ on:
jobs:
macOS:
runs-on: macos-13
runs-on: macos-11
env:
HOME: /Users/runner
steps:
@@ -83,7 +83,7 @@ jobs:
HOME: /home/runner
runs-on: ubuntu-20.04
container:
image: "ghcr.io/kiwix/kiwix-build_ci_${{matrix.image_variant}}:38"
image: "ghcr.io/kiwix/kiwix-build_ci_${{matrix.image_variant}}:37"
steps:
- name: Checkout code
uses: actions/checkout@v3

View File

@@ -5,17 +5,15 @@ on:
push:
branches:
- main
release:
types: [published]
jobs:
build-deb:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
distro:
- debian-unstable
- ubuntu-kinetic
- ubuntu-jammy
- ubuntu-focal
steps:
@@ -40,12 +38,13 @@ jobs:
email: release+launchpad@kiwix.org
distro: ${{ matrix.distro }}
- uses: legoktm/gh-action-build-deb@debian-unstable
if: matrix.distro == 'debian-unstable'
name: Build package for debian-unstable
id: build-debian-unstable
- uses: legoktm/gh-action-build-deb@ubuntu-kinetic
if: matrix.distro == 'ubuntu-kinetic'
name: Build package for ubuntu-kinetic
id: build-ubuntu-kinetic
with:
args: --no-sign
ppa: ${{ steps.ppa.outputs.ppa }}
- uses: legoktm/gh-action-build-deb@ubuntu-jammy
if: matrix.distro == 'ubuntu-jammy'
@@ -70,7 +69,7 @@ jobs:
- uses: legoktm/gh-action-dput@master
name: Upload dev package
# Only upload on pushes to main
# Only upload on pushes to git default branch
if: github.event_name == 'push' && github.event.ref == 'refs/heads/main' && startswith(matrix.distro, 'ubuntu-')
with:
gpg_key: ${{ secrets.LAUNCHPAD_GPG }}
@@ -79,8 +78,10 @@ jobs:
- uses: legoktm/gh-action-dput@master
name: Upload release package
if: github.event_name == 'release' && startswith(matrix.distro, 'ubuntu-')
# Only upload on pushes to master or tag
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') && startswith(matrix.distro, 'ubuntu-')
with:
gpg_key: ${{ secrets.LAUNCHPAD_GPG }}
repository: ppa:kiwixteam/release
packages: output/*_source.changes

View File

@@ -1,22 +1,7 @@
libkiwix 13.0.0
libkiwix 12.1.1
===============
* Server:
- Improved look & feel of kiwix-serve UI (@veloman-yunkan #917 #1021)
- Increase tolerance to malformed (control characters) ZIM entry titles (@veloman-yunkan #1023)
- API allowing to filter many categories at once (@juuz0 #974)
- Cookie-less user language control (@veloman-yumkan #997)
- Hack to fix Mirrorbrain based broken magnet URLs (@rgaudin #1001)
* Fix handling of books with 'Name' metadata with dots (@mgautier #1016)
* New method beautifyFileSize() to provide nice-looking book sizes (@vuuz0 #971)
* Fix a few missing includes (@mgautierfr #978)
* New functions to read - kiwix-serve - languages and categories streams (@juuz0 #967)
* Add support of Fon language (@kelson42 #1013)
* C++17 code base compliancy (@mgautierfr #996)
* Use everywhere std::shared_ptr in place of raw pointer (@mgautierfr #991)
* Do not use [[nodiscard]] attribute on compiler not supporting it (@mgautierfr #1003)
* Add a non minified version of autoComplete.js (@mgautierfr #1008)
* Multiple CI/CD improvements (@kelson42 #982)
* Revert API break introduced in libkiwix 12.1.0
libkiwix 12.1.0
===============

View File

@@ -24,9 +24,9 @@ with the Libkiwix compilation itself, we recommend to have a look to
Preamble
--------
Although the Libkiwix can be (cross-)compiled on/for many systems, the
Although the Libkiwix can be (cross-)compiled on/for many sytems, the
following documentation explains how to do it on POSIX ones. It is
primarily thought for GNU/Linux systems and has been tested on recent
primarly thought for GNU/Linux systems and has been tested on recent
releases of Ubuntu and Fedora.
Dependencies
@@ -54,7 +54,7 @@ The following dependency needs to be available at runtime:
These dependencies may or may not be packaged by your operating
system. They may also be packaged but only in an older version. The
compilation script will tell you if one of them is missing or too old.
In the worst case, you will have to download and compile bleeding edge
In the worse case, you will have to download and compile bleeding edge
version by hand.
If you want to install these dependencies locally, then use the
@@ -201,7 +201,7 @@ To use JS provided by kiwix-serve you can use the following template to start wi
If you compile manually Libmicrohttpd, you might need to compile it
without GNU TLS, a bug here will impeach further compilation
without GNU TLS, a bug here will empeach further compilation
otherwise.
If the compilation still fails, you might need to get a more recent

View File

@@ -24,6 +24,8 @@ author = 'libkiwix-team'
# -- General configuration ---------------------------------------------------
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
@@ -40,7 +42,9 @@ templates_path = ['_templates']
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
html_theme = 'sphinx_rtd_theme'
if not on_rtd:
html_theme = 'sphinx_rtd_theme'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,

View File

@@ -1,3 +1,2 @@
breathe
exhale
sphinx_rtd_theme

View File

@@ -184,7 +184,7 @@ class Downloader
* @param options: A series of pair <option_name, option_value> to pass to aria.
* @return: The newly created Download.
*/
std::shared_ptr<Download> startDownload(const std::string& uri, const std::vector<std::pair<std::string, std::string>>& options = {});
Download* startDownload(const std::string& uri, const std::vector<std::pair<std::string, std::string>>& options = {});
/**
* Get a download corrsponding to a download id (did)
@@ -194,7 +194,7 @@ class Downloader
* @return: The Download corresponding to did.
* @throw: Throw std::out_of_range if did is not found.
*/
std::shared_ptr<Download> getDownload(const std::string& did);
Download* getDownload(const std::string& did);
/**
* Get the number of downloads currently managed.
@@ -204,11 +204,11 @@ class Downloader
/**
* Get the ids of the managed downloads.
*/
std::vector<std::string> getDownloadIds() const;
std::vector<std::string> getDownloadIds();
private:
mutable std::mutex m_lock;
std::map<std::string, std::shared_ptr<Download>> m_knownDownloads;
std::map<std::string, std::unique_ptr<Download>> m_knownDownloads;
std::shared_ptr<Aria2> mp_aria;
};
}

View File

View File

@@ -34,10 +34,6 @@
#define KIWIX_LIBRARY_VERSION "20110515"
namespace Xapian {
class WritableDatabase;
};
namespace kiwix
{
@@ -109,12 +105,6 @@ class Filter {
Filter& acceptTags(const Tags& tags);
Filter& rejectTags(const Tags& tags);
/**
* Set the filter to only accept books in the specified category.
*
* Multiple categories can be specified as a comma-separated list (in
* which case a book in any of those categories will match).
*/
Filter& category(std::string category);
/**
@@ -177,53 +167,31 @@ class ZimSearcher : public zim::Searcher
std::mutex m_mutex;
};
template<typename, typename>
class ConcurrentCache;
template<typename, typename>
class MultiKeyCache;
using LibraryPtr = std::shared_ptr<Library>;
using ConstLibraryPtr = std::shared_ptr<const Library>;
// Some compiler we use don't have [[nodiscard]] attribute.
// We don't want to declare `create` with it in this case.
#define LIBKIWIX_NODISCARD
#if defined __has_cpp_attribute
#if __has_cpp_attribute (nodiscard)
#undef LIBKIWIX_NODISCARD
#define LIBKIWIX_NODISCARD [[nodiscard]]
#endif
#endif
/**
* A Library store several books.
*/
class Library: public std::enable_shared_from_this<Library>
class Library
{
// all data fields must be added in LibraryBase
mutable std::mutex m_mutex;
public:
typedef uint64_t Revision;
typedef std::vector<std::string> BookIdCollection;
typedef std::map<std::string, int> AttributeCounts;
typedef std::set<std::string> BookIdSet;
private:
Library();
public:
LIBKIWIX_NODISCARD static LibraryPtr create() {
return LibraryPtr(new Library());
}
Library();
~Library();
/**
* Library is not a copiable object. However it can be moved.
*/
Library(const Library& ) = delete;
Library(Library&& ) = delete;
Library(Library&& );
void operator=(const Library& ) = delete;
Library& operator=(Library&& ) = delete;
Library& operator=(Library&& );
/**
* Add a book to the library.
@@ -394,36 +362,19 @@ class Library: public std::enable_shared_from_this<Library>
private: // types
typedef const std::string& (Book::*BookStrPropMemFn)() const;
struct Entry : Book
{
Library::Revision lastUpdatedRevision = 0;
};
struct Impl;
private: // functions
AttributeCounts getBookAttributeCounts(BookStrPropMemFn p) const;
std::vector<std::string> getBookPropValueSet(BookStrPropMemFn p) const;
BookIdCollection filterViaBookDB(const Filter& filter) const;
unsigned int getBookCount_not_protected(const bool localBooks, const bool remoteBooks) const;
void updateBookDB(const Book& book);
void dropCache(const std::string& bookId);
private: //data
mutable std::mutex m_mutex;
Library::Revision m_revision;
std::map<std::string, Entry> m_books;
using ArchiveCache = ConcurrentCache<std::string, std::shared_ptr<zim::Archive>>;
std::unique_ptr<ArchiveCache> mp_archiveCache;
using SearcherCache = MultiKeyCache<std::string, std::shared_ptr<ZimSearcher>>;
std::unique_ptr<SearcherCache> mp_searcherCache;
std::vector<kiwix::Bookmark> m_bookmarks;
std::unique_ptr<Xapian::WritableDatabase> m_bookDB;
std::unique_ptr<Impl> mp_impl;
};
// We don't need it anymore and we don't want to polute any other potential usage
// of `LIBKIWIX_NODISCARD` token.
#undef LIBKIWIX_NODISCARD
}
#endif

View File

@@ -37,10 +37,10 @@ namespace kiwix
class LibraryManipulator
{
public: // functions
explicit LibraryManipulator(LibraryPtr library);
explicit LibraryManipulator(Library* library);
virtual ~LibraryManipulator();
LibraryPtr getLibrary() const { return library; }
Library& getLibrary() const { return library; }
bool addBookToLibrary(const Book& book);
void addBookmarkToLibrary(const Bookmark& bookmark);
@@ -52,7 +52,7 @@ class LibraryManipulator
virtual void booksWereRemovedFromLibrary();
private: // data
LibraryPtr library;
kiwix::Library& library;
};
/**
@@ -64,8 +64,8 @@ class Manager
typedef std::vector<std::string> Paths;
public: // functions
explicit Manager(LibraryManipulator manipulator);
explicit Manager(LibraryPtr library);
explicit Manager(LibraryManipulator* manipulator);
explicit Manager(Library* library);
/**
* Read a `library.xml` and add book in the file to the library.
@@ -163,7 +163,7 @@ class Manager
uint64_t m_itemsPerPage = 0;
protected:
kiwix::LibraryManipulator manipulator;
std::shared_ptr<kiwix::LibraryManipulator> manipulator;
bool readBookFromPath(const std::string& path, Book* book);
bool parseXmlDom(const pugi::xml_document& doc,

View File

@@ -4,6 +4,8 @@ headers = [
'common.h',
'library.h',
'manager.h',
'libxml_dumper.h',
'opds_dumper.h',
'downloader.h',
'search_renderer.h',
'server.h',

View File

@@ -59,7 +59,7 @@ class HumanReadableNameMapper : public NameMapper {
class UpdatableNameMapper : public NameMapper {
typedef std::shared_ptr<NameMapper> NameMapperHandle;
public:
UpdatableNameMapper(std::shared_ptr<Library> library, bool withAlias);
UpdatableNameMapper(Library& library, bool withAlias);
virtual std::string getNameForId(const std::string& id) const;
virtual std::string getIdForName(const std::string& name) const;
@@ -71,7 +71,7 @@ class UpdatableNameMapper : public NameMapper {
private:
mutable std::mutex mutex;
std::shared_ptr<Library> library;
Library& library;
NameMapperHandle nameMapper;
const bool withAlias;
};

View File

View File

@@ -37,11 +37,29 @@ class SearchRenderer
/**
* Construct a SearchRenderer from a SearchResultSet.
*
* The constructed version of the SearchRenderer will not introduce
* the book name for each result. It is better to use the other constructor
* with a Library pointer to have a better html page.
*
* @param srs The `SearchResultSet` to render.
* @param mapper The `NameMapper` to use to do the rendering.
* @param start The start offset used for the srs.
* @param estimatedResultCount The estimatedResultCount of the whole search
*/
SearchRenderer(zim::SearchResultSet srs, unsigned int start, unsigned int estimatedResultCount);
SearchRenderer(zim::SearchResultSet srs, NameMapper* mapper,
unsigned int start, unsigned int estimatedResultCount);
/**
* Construct a SearchRenderer from a SearchResultSet.
*
* @param srs The `SearchResultSet` to render.
* @param mapper The `NameMapper` to use to do the rendering.
* @param library The `Library` to use to look up book details for search results.
* @param start The start offset used for the srs.
* @param estimatedResultCount The estimatedResultCount of the whole search
*/
SearchRenderer(zim::SearchResultSet srs, NameMapper* mapper, Library* library,
unsigned int start, unsigned int estimatedResultCount);
~SearchRenderer();
@@ -72,32 +90,24 @@ class SearchRenderer
this->pageLength = pageLength;
}
std::string renderTemplate(const std::string& tmpl_str);
/**
* Generate the html page with the resutls of the search.
*
* @param mapper The `NameMapper` to use to do the rendering.
* @param library The `Library` to use to look up book details for search results.
May be nullptr. In this case, bookName is not set in the rendered string.
* @return The html string
*/
std::string getHtml(const NameMapper& mapper, const Library* library);
std::string getHtml();
/**
/**
* Generate the xml page with the resutls of the search.
*
* @param mapper The `NameMapper` to use to do the rendering.
* @param library The `Library` to use to look up book details for search results.
May be nullptr. In this case, bookName is not set in the rendered string.
* @return The xml string
*/
std::string getXml(const NameMapper& mapper, const Library* library);
std::string getXml();
protected: // function
std::string renderTemplate(const std::string& tmpl_str, const NameMapper& mapper, const Library *library);
protected:
std::string beautifyInteger(const unsigned int number);
zim::SearchResultSet m_srs;
NameMapper* mp_nameMapper;
Library* mp_library;
std::string searchBookQuery;
std::string searchPattern;
std::string protocolPrefix;

View File

@@ -36,7 +36,7 @@ namespace kiwix
*
* @param library The library to serve.
*/
Server(std::shared_ptr<Library> library, std::shared_ptr<NameMapper> nameMapper=nullptr);
Server(Library* library, NameMapper* nameMapper=nullptr);
virtual ~Server();
@@ -66,8 +66,8 @@ namespace kiwix
std::string getAddress();
protected:
std::shared_ptr<Library> mp_library;
std::shared_ptr<NameMapper> mp_nameMapper;
Library* mp_library;
NameMapper* mp_nameMapper;
std::string m_root = "";
std::string m_addr = "";
std::string m_indexTemplateString = "";

View File

@@ -23,12 +23,8 @@
#include <string>
#include <vector>
#include <map>
#include <cstdint>
namespace kiwix {
typedef std::pair<std::string, std::string> LangNameCodePair;
typedef std::vector<LangNameCodePair> FeedLanguages;
typedef std::vector<std::string> FeedCategories;
/**
* Return the current directory.
@@ -220,37 +216,5 @@ std::map<std::string, std::string> getNetworkInterfaces();
*/
std::string getBestPublicIp();
/** Converts file size to human readable format.
*
* This function will convert a number to its equivalent size using units.
*
* @param number file size in bytes.
* @return a human-readable string representation of the size, e.g., "2.3 KB", "1.8 MB", "5.2 GB".
*/
std::string beautifyFileSize(uint64_t number);
/**
* Load languages stored in an OPDS stream.
*
* @param content the OPDS stream.
* @return vector containing pairs of language code and their corresponding full language name.
*/
FeedLanguages readLanguagesFromFeed(const std::string& content);
/**
* Load categories stored in an OPDS stream .
*
* @param content the OPDS stream.
* @return vector containing category strings.
*/
FeedCategories readCategoriesFromFeed(const std::string& content);
/**
* Retrieve the full language name associated with a given ISO 639-3 language code.
*
* @param lang ISO 639-3 language code.
* @return full language name.
*/
std::string getLanguageSelfName(const std::string& lang);
}
#endif // KIWIX_TOOLS_H

View File

@@ -1,7 +1,7 @@
project('libkiwix', 'cpp',
version : '13.0.0',
version : '12.1.1',
license : 'GPLv3+',
default_options : ['c_std=c11', 'cpp_std=c++17', 'werror=true'])
default_options : ['c_std=c11', 'cpp_std=c++11', 'werror=true'])
compiler = meson.get_compiler('cpp')

View File

@@ -157,7 +157,7 @@ void Downloader::close()
mp_aria->close();
}
std::vector<std::string> Downloader::getDownloadIds() const {
std::vector<std::string> Downloader::getDownloadIds() {
std::unique_lock<std::mutex> lock(m_lock);
std::vector<std::string> ret;
for(auto& p:m_knownDownloads) {
@@ -166,37 +166,37 @@ std::vector<std::string> Downloader::getDownloadIds() const {
return ret;
}
std::shared_ptr<Download> Downloader::startDownload(const std::string& uri, const std::vector<std::pair<std::string, std::string>>& options)
Download* Downloader::startDownload(const std::string& uri, const std::vector<std::pair<std::string, std::string>>& options)
{
std::unique_lock<std::mutex> lock(m_lock);
for (auto& p: m_knownDownloads) {
auto& d = p.second;
auto& uris = d->getUris();
if (std::find(uris.begin(), uris.end(), uri) != uris.end())
return d;
return d.get();
}
std::vector<std::string> uris = {uri};
auto gid = mp_aria->addUri(uris, options);
m_knownDownloads[gid] = std::make_shared<Download>(mp_aria, gid);
return m_knownDownloads[gid];
m_knownDownloads[gid] = std::unique_ptr<Download>(new Download(mp_aria, gid));
return m_knownDownloads[gid].get();
}
std::shared_ptr<Download> Downloader::getDownload(const std::string& did)
Download* Downloader::getDownload(const std::string& did)
{
std::unique_lock<std::mutex> lock(m_lock);
try {
return m_knownDownloads.at(did);
return m_knownDownloads.at(did).get();
} catch(std::exception& e) {
for (auto gid : mp_aria->tellWaiting()) {
if (gid == did) {
m_knownDownloads[gid] = std::make_shared<Download>(mp_aria, gid);
return m_knownDownloads[gid];
m_knownDownloads[gid] = std::unique_ptr<Download>(new Download(mp_aria, gid));
return m_knownDownloads[gid].get();
}
}
for (auto gid : mp_aria->tellActive()) {
if (gid == did) {
m_knownDownloads[gid] = std::make_shared<Download>(mp_aria, gid);
return m_knownDownloads[gid];
m_knownDownloads[gid] = std::unique_ptr<Download>(new Download(mp_aria, gid));
return m_knownDownloads[gid].get();
}
}
throw e;

View File

@@ -39,8 +39,6 @@
namespace kiwix
{
namespace
{
@@ -60,8 +58,6 @@ bool booksReferToTheSameArchive(const Book& book1, const Book& book2)
&& book1.getPath() == book2.getPath();
}
} // unnamed namespace
template<typename Key, typename Value>
class MultiKeyCache: public ConcurrentCache<std::set<Key>, Value>
{
@@ -83,8 +79,49 @@ class MultiKeyCache: public ConcurrentCache<std::set<Key>, Value>
}
};
} // unnamed namespace
struct Library::Impl
{
struct Entry : Book
{
Library::Revision lastUpdatedRevision = 0;
};
Library::Revision m_revision;
std::map<std::string, Entry> m_books;
using ArchiveCache = ConcurrentCache<std::string, std::shared_ptr<zim::Archive>>;
std::unique_ptr<ArchiveCache> mp_archiveCache;
using SearcherCache = MultiKeyCache<std::string, std::shared_ptr<ZimSearcher>>;
std::unique_ptr<SearcherCache> mp_searcherCache;
std::vector<kiwix::Bookmark> m_bookmarks;
Xapian::WritableDatabase m_bookDB;
unsigned int getBookCount(const bool localBooks, const bool remoteBooks) const;
Impl();
~Impl();
Impl(Impl&& );
Impl& operator=(Impl&& );
};
Library::Impl::Impl()
: mp_archiveCache(new ArchiveCache(std::max(getEnvVar<int>("KIWIX_ARCHIVE_CACHE_SIZE", 1), 1))),
mp_searcherCache(new SearcherCache(std::max(getEnvVar<int>("KIWIX_SEARCHER_CACHE_SIZE", 1), 1))),
m_bookDB("", Xapian::DB_BACKEND_INMEMORY)
{
}
Library::Impl::~Impl()
{
}
Library::Impl::Impl(Library::Impl&& ) = default;
Library::Impl& Library::Impl::operator=(Library::Impl&& ) = default;
unsigned int
Library::getBookCount_not_protected(const bool localBooks, const bool remoteBooks) const
Library::Impl::getBookCount(const bool localBooks, const bool remoteBooks) const
{
unsigned int result = 0;
for (auto& pair: m_books) {
@@ -99,41 +136,50 @@ Library::getBookCount_not_protected(const bool localBooks, const bool remoteBook
/* Constructor */
Library::Library()
: mp_archiveCache(new ArchiveCache(std::max(getEnvVar<int>("KIWIX_ARCHIVE_CACHE_SIZE", 1), 1))),
mp_searcherCache(new SearcherCache(std::max(getEnvVar<int>("KIWIX_SEARCHER_CACHE_SIZE", 1), 1))),
m_bookDB(new Xapian::WritableDatabase("", Xapian::DB_BACKEND_INMEMORY))
: mp_impl(new Library::Impl)
{
}
Library::Library(Library&& other)
: mp_impl(std::move(other.mp_impl))
{
}
Library& Library::operator=(Library&& other)
{
mp_impl = std::move(other.mp_impl);
return *this;
}
/* Destructor */
Library::~Library() = default;
bool Library::addBook(const Book& book)
{
std::lock_guard<std::mutex> lock(m_mutex);
++m_revision;
++mp_impl->m_revision;
/* Try to find it */
updateBookDB(book);
try {
auto& oldbook = m_books.at(book.getId());
auto& oldbook = mp_impl->m_books.at(book.getId());
if ( ! booksReferToTheSameArchive(oldbook, book) ) {
dropCache(book.getId());
}
oldbook.update(book); // XXX: This may have no effect if oldbook is readonly
// XXX: Then m_bookDB will become out-of-sync with
// XXX: the real contents of the library.
oldbook.lastUpdatedRevision = m_revision;
oldbook.lastUpdatedRevision = mp_impl->m_revision;
return false;
} catch (std::out_of_range&) {
auto& newEntry = m_books[book.getId()];
auto& newEntry = mp_impl->m_books[book.getId()];
static_cast<Book&>(newEntry) = book;
newEntry.lastUpdatedRevision = m_revision;
size_t new_cache_size = static_cast<size_t>(std::ceil(getBookCount_not_protected(true, true)*0.1));
newEntry.lastUpdatedRevision = mp_impl->m_revision;
size_t new_cache_size = static_cast<size_t>(std::ceil(mp_impl->getBookCount(true, true)*0.1));
if (getEnvVar<int>("KIWIX_ARCHIVE_CACHE_SIZE", -1) <= 0) {
mp_archiveCache->setMaxSize(new_cache_size);
mp_impl->mp_archiveCache->setMaxSize(new_cache_size);
}
if (getEnvVar<int>("KIWIX_SEARCHER_CACHE_SIZE", -1) <= 0) {
mp_searcherCache->setMaxSize(new_cache_size);
mp_impl->mp_searcherCache->setMaxSize(new_cache_size);
}
return true;
}
@@ -142,15 +188,15 @@ bool Library::addBook(const Book& book)
void Library::addBookmark(const Bookmark& bookmark)
{
std::lock_guard<std::mutex> lock(m_mutex);
m_bookmarks.push_back(bookmark);
mp_impl->m_bookmarks.push_back(bookmark);
}
bool Library::removeBookmark(const std::string& zimId, const std::string& url)
{
std::lock_guard<std::mutex> lock(m_mutex);
for(auto it=m_bookmarks.begin(); it!=m_bookmarks.end(); it++) {
for(auto it=mp_impl->m_bookmarks.begin(); it!=mp_impl->m_bookmarks.end(); it++) {
if (it->getBookId() == zimId && it->getUrl() == url) {
m_bookmarks.erase(it);
mp_impl->m_bookmarks.erase(it);
return true;
}
}
@@ -160,14 +206,14 @@ bool Library::removeBookmark(const std::string& zimId, const std::string& url)
void Library::dropCache(const std::string& id)
{
mp_archiveCache->drop(id);
mp_searcherCache->drop(id);
mp_impl->mp_archiveCache->drop(id);
mp_impl->mp_searcherCache->drop(id);
}
bool Library::removeBookById(const std::string& id)
{
std::lock_guard<std::mutex> lock(m_mutex);
m_bookDB->delete_document("Q" + id);
mp_impl->m_bookDB.delete_document("Q" + id);
dropCache(id);
// We do not change the cache size here
// Most of the time, the book is remove in case of library refresh, it is
@@ -175,9 +221,9 @@ bool Library::removeBookById(const std::string& id)
// Having a too big cache is not a problem here (or it would have been before)
// (And setMaxSize doesn't actually reduce the cache size, extra cached items
// will be removed in put or getOrPut).
const bool bookWasRemoved = m_books.erase(id) == 1;
const bool bookWasRemoved = mp_impl->m_books.erase(id) == 1;
if ( bookWasRemoved ) {
++m_revision;
++mp_impl->m_revision;
}
return bookWasRemoved;
}
@@ -185,7 +231,7 @@ bool Library::removeBookById(const std::string& id)
Library::Revision Library::getRevision() const
{
std::lock_guard<std::mutex> lock(m_mutex);
return m_revision;
return mp_impl->m_revision;
}
uint32_t Library::removeBooksNotUpdatedSince(Revision libraryRevision)
@@ -193,7 +239,7 @@ uint32_t Library::removeBooksNotUpdatedSince(Revision libraryRevision)
BookIdCollection booksToRemove;
{
std::lock_guard<std::mutex> lock(m_mutex);
for ( const auto& entry : m_books) {
for ( const auto& entry : mp_impl->m_books) {
if ( entry.second.lastUpdatedRevision <= libraryRevision ) {
booksToRemove.push_back(entry.first);
}
@@ -212,7 +258,7 @@ const Book& Library::getBookById(const std::string& id) const
{
// XXX: Doesn't make sense to lock this operation since it cannot
// XXX: guarantee thread-safety because of its return type
return m_books.at(id);
return mp_impl->m_books.at(id);
}
Book Library::getBookByIdThreadSafe(const std::string& id) const
@@ -225,7 +271,7 @@ const Book& Library::getBookByPath(const std::string& path) const
{
// XXX: Doesn't make sense to lock this operation since it cannot
// XXX: guarantee thread-safety because of its return type
for(auto& it: m_books) {
for(auto& it: mp_impl->m_books) {
auto& book = it.second;
if (book.getPath() == path)
return book;
@@ -238,7 +284,7 @@ const Book& Library::getBookByPath(const std::string& path) const
std::shared_ptr<zim::Archive> Library::getArchiveById(const std::string& id)
{
try {
return mp_archiveCache->getOrPut(id,
return mp_impl->mp_archiveCache->getOrPut(id,
[&](){
auto book = getBookById(id);
if (!book.isPathValid()) {
@@ -255,7 +301,7 @@ std::shared_ptr<ZimSearcher> Library::getSearcherByIds(const BookIdSet& ids)
{
assert(!ids.empty());
try {
return mp_searcherCache->getOrPut(ids,
return mp_impl->mp_searcherCache->getOrPut(ids,
[&](){
std::vector<zim::Archive> archives;
for(auto& id:ids) {
@@ -276,7 +322,7 @@ unsigned int Library::getBookCount(const bool localBooks,
const bool remoteBooks) const
{
std::lock_guard<std::mutex> lock(m_mutex);
return getBookCount_not_protected(localBooks, remoteBooks);
return mp_impl->getBookCount(localBooks, remoteBooks);
}
bool Library::writeToFile(const std::string& path) const
@@ -307,7 +353,7 @@ Library::AttributeCounts Library::getBookAttributeCounts(BookStrPropMemFn p) con
std::lock_guard<std::mutex> lock(m_mutex);
AttributeCounts propValueCounts;
for (const auto& pair: m_books) {
for (const auto& pair: mp_impl->m_books) {
const auto& book = pair.second;
if (book.getOrigId().empty()) {
propValueCounts[(book.*p)()] += 1;
@@ -339,7 +385,7 @@ Library::AttributeCounts Library::getBooksLanguagesWithCounts() const
std::lock_guard<std::mutex> lock(m_mutex);
AttributeCounts langsWithCounts;
for (const auto& pair: m_books) {
for (const auto& pair: mp_impl->m_books) {
const auto& book = pair.second;
if (book.getOrigId().empty()) {
for ( const auto& lang : book.getLanguages() ) {
@@ -355,7 +401,7 @@ std::vector<std::string> Library::getBooksCategories() const
std::lock_guard<std::mutex> lock(m_mutex);
std::set<std::string> categories;
for (const auto& pair: m_books) {
for (const auto& pair: mp_impl->m_books) {
const auto& book = pair.second;
const auto& c = book.getCategory();
if ( !c.empty() ) {
@@ -379,12 +425,12 @@ std::vector<std::string> Library::getBooksPublishers() const
const std::vector<kiwix::Bookmark> Library::getBookmarks(bool onlyValidBookmarks) const
{
if (!onlyValidBookmarks) {
return m_bookmarks;
return mp_impl->m_bookmarks;
}
std::vector<kiwix::Bookmark> validBookmarks;
auto booksId = getBooksIds();
std::lock_guard<std::mutex> lock(m_mutex);
for(auto& bookmark:m_bookmarks) {
for(auto& bookmark:mp_impl->m_bookmarks) {
if (std::find(booksId.begin(), booksId.end(), bookmark.getBookId()) != booksId.end()) {
validBookmarks.push_back(bookmark);
}
@@ -397,7 +443,7 @@ Library::BookIdCollection Library::getBooksIds() const
std::lock_guard<std::mutex> lock(m_mutex);
BookIdCollection bookIds;
for (auto& pair: m_books) {
for (auto& pair: mp_impl->m_books) {
bookIds.push_back(pair.first);
}
@@ -436,7 +482,7 @@ void Library::updateBookDB(const Book& book)
}
indexer.index_text(normalizeText(book.getCreator()), 1, "A");
indexer.index_text(normalizeText(book.getPublisher()), 1, "XP");
doc.add_term("XN"+normalizeText(book.getName()));
indexer.index_text(normalizeText(book.getName()), 1, "XN");
indexer.index_text(normalizeText(book.getCategory()), 1, "XC");
for ( const auto& tag : split(normalizeText(book.getTags()), ";") ) {
@@ -452,7 +498,7 @@ void Library::updateBookDB(const Book& book)
doc.set_data(book.getId());
m_bookDB->replace_document(idterm, doc);
mp_impl->m_bookDB.replace_document(idterm, doc);
}
namespace
@@ -503,30 +549,25 @@ Xapian::Query nameQuery(const std::string& name)
return Xapian::Query("XN" + normalizeText(name));
}
Xapian::Query multipleParamQuery(const std::string& commaSeparatedList, const std::string& prefix)
Xapian::Query categoryQuery(const std::string& category)
{
Xapian::Query q;
bool firstIteration = true;
for ( const auto& elem : kiwix::split(commaSeparatedList, ",") ) {
const Xapian::Query singleQuery(prefix + normalizeText(elem));
if ( firstIteration ) {
q = singleQuery;
firstIteration = false;
} else {
q = Xapian::Query(Xapian::Query::OP_OR, q, singleQuery);
}
}
return q;
}
Xapian::Query categoryQuery(const std::string& commaSeparatedCategoryList)
{
return multipleParamQuery(commaSeparatedCategoryList, "XC");
return Xapian::Query("XC" + normalizeText(category));
}
Xapian::Query langQuery(const std::string& commaSeparatedLanguageList)
{
return multipleParamQuery(commaSeparatedLanguageList, "L");
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)
@@ -601,9 +642,9 @@ Library::BookIdCollection Library::filterViaBookDB(const Filter& filter) const
BookIdCollection bookIds;
std::lock_guard<std::mutex> lock(m_mutex);
Xapian::Enquire enquire(*m_bookDB);
Xapian::Enquire enquire(mp_impl->m_bookDB);
enquire.set_query(query);
const auto results = enquire.get_mset(0, m_books.size());
const auto results = enquire.get_mset(0, mp_impl->m_books.size());
for ( auto it = results.begin(); it != results.end(); ++it ) {
bookIds.push_back(it.get_document().get_data());
}
@@ -617,7 +658,7 @@ Library::BookIdCollection Library::filter(const Filter& filter) const
const auto preliminaryResult = filterViaBookDB(filter);
std::lock_guard<std::mutex> lock(m_mutex);
for(auto id : preliminaryResult) {
if(filter.accept(m_books.at(id))) {
if(filter.accept(mp_impl->m_books.at(id))) {
result.push_back(id);
}
}

View File

@@ -23,6 +23,65 @@ void LibraryDumper::setOpenSearchInfo(int totalResults, int startIndex, int coun
m_count = count;
}
namespace {
std::map<std::string, std::string> iso639_3 = {
{"atj", "atikamekw"},
{"azb", "آذربایجان دیلی"},
{"bcl", "central bikol"},
{"bgs", "tagabawa"},
{"bxr", "буряад хэлэн"},
{"cbk", "chavacano"},
{"cdo", "閩東語"},
{"dag", "Dagbani"},
{"diq", "dimli"},
{"dty", "डोटेली"},
{"eml", "emiliân-rumagnōl"},
{"fbs", "српскохрватски"},
{"guw", "Gungbe"},
{"hbs", "srpskohrvatski"},
{"ido", "ido"},
{"kbp", "kabɩ"},
{"kld", "Gamilaraay"},
{"lbe", "лакку маз"},
{"lbj", "ལ་དྭགས་སྐད་"},
{"map", "Austronesian"},
{"mhr", "марий йылме"},
{"mnw", "ဘာသာမန်"},
{"myn", "mayan"},
{"nah", "nahuatl"},
{"nai", "north American Indian"},
{"nds", "plattdütsch"},
{"nrm", "bhasa narom"},
{"olo", "livvi"},
{"pih", "Pitcairn-Norfolk"},
{"pnb", "Western Panjabi"},
{"rmr", "Caló"},
{"rmy", "romani shib"},
{"roa", "romance languages"},
{"twi", "twi"},
};
std::once_flag fillLanguagesFlag;
void fillLanguagesMap()
{
for (auto icuLangPtr = icu::Locale::getISOLanguages(); *icuLangPtr != NULL; ++icuLangPtr) {
const ICULanguageInfo lang(*icuLangPtr);
iso639_3.insert({lang.iso3Code(), lang.selfName()});
}
}
std::string getLanguageSelfName(const std::string& lang) {
const auto itr = iso639_3.find(lang);
if (itr != iso639_3.end()) {
return itr->second;
}
return lang;
};
} // unnamed namespace
kainjow::mustache::list LibraryDumper::getCategoryData() const
{
const auto now = gen_date_str();
@@ -43,6 +102,7 @@ kainjow::mustache::list LibraryDumper::getLanguageData() const
{
const auto now = gen_date_str();
kainjow::mustache::list languageData;
std::call_once(fillLanguagesFlag, fillLanguagesMap);
for ( const auto& langAndBookCount : library->getBooksLanguagesWithCounts() ) {
const std::string languageCode = langAndBookCount.first;
const int bookCount = langAndBookCount.second;

View File

@@ -27,12 +27,22 @@
namespace kiwix
{
namespace
{
struct NoDelete
{
template<class T> void operator()(T*) {}
};
} // unnamed namespace
////////////////////////////////////////////////////////////////////////////////
// LibraryManipulator
////////////////////////////////////////////////////////////////////////////////
LibraryManipulator::LibraryManipulator(LibraryPtr library)
: library(library)
LibraryManipulator::LibraryManipulator(Library* library)
: library(*library)
{}
LibraryManipulator::~LibraryManipulator()
@@ -40,7 +50,7 @@ LibraryManipulator::~LibraryManipulator()
bool LibraryManipulator::addBookToLibrary(const Book& book)
{
const auto ret = library->addBook(book);
const auto ret = library.addBook(book);
if ( ret ) {
bookWasAddedToLibrary(book);
}
@@ -49,13 +59,13 @@ bool LibraryManipulator::addBookToLibrary(const Book& book)
void LibraryManipulator::addBookmarkToLibrary(const Bookmark& bookmark)
{
library->addBookmark(bookmark);
library.addBookmark(bookmark);
bookmarkWasAddedToLibrary(bookmark);
}
uint32_t LibraryManipulator::removeBooksNotUpdatedSince(Library::Revision rev)
{
const auto n = library->removeBooksNotUpdatedSince(rev);
const auto n = library.removeBooksNotUpdatedSince(rev);
if ( n != 0 ) {
booksWereRemovedFromLibrary();
}
@@ -79,15 +89,15 @@ void LibraryManipulator::booksWereRemovedFromLibrary()
////////////////////////////////////////////////////////////////////////////////
/* Constructor */
Manager::Manager(LibraryManipulator manipulator):
Manager::Manager(LibraryManipulator* manipulator):
writableLibraryPath(""),
manipulator(manipulator)
manipulator(manipulator, NoDelete())
{
}
Manager::Manager(LibraryPtr library) :
Manager::Manager(Library* library) :
writableLibraryPath(""),
manipulator(LibraryManipulator(library))
manipulator(new LibraryManipulator(library))
{
}
@@ -111,7 +121,7 @@ bool Manager::parseXmlDom(const pugi::xml_document& doc,
if (!trustLibrary && !book.getPath().empty()) {
this->readBookFromPath(book.getPath(), &book);
}
manipulator.addBookToLibrary(book);
manipulator->addBookToLibrary(book);
}
return true;
@@ -156,7 +166,7 @@ bool Manager::parseOpdsDom(const pugi::xml_document& doc, const std::string& url
book.updateFromOpds(entryNode, urlHost);
/* Update the book properties with the new importer */
manipulator.addBookToLibrary(book);
manipulator->addBookToLibrary(book);
}
return true;
@@ -231,7 +241,7 @@ std::string Manager::addBookFromPathAndGetId(const std::string& pathToOpen,
|| (!book.getTitle().empty() && !book.getLanguages().empty()
&& !book.getDate().empty())) {
book.setUrl(url);
manipulator.addBookToLibrary(book);
manipulator->addBookToLibrary(book);
return book.getId();
}
}
@@ -286,7 +296,7 @@ bool Manager::readBookmarkFile(const std::string& path)
bookmark.updateFromXml(node);
manipulator.addBookmarkToLibrary(bookmark);
manipulator->addBookmarkToLibrary(bookmark);
}
return true;
@@ -294,7 +304,7 @@ bool Manager::readBookmarkFile(const std::string& path)
void Manager::reload(const Paths& paths)
{
const auto libRevision = manipulator.getLibrary()->getRevision();
const auto libRevision = manipulator->getLibrary().getRevision();
for (std::string path : paths) {
if (!path.empty()) {
if ( kiwix::isRelativePath(path) )
@@ -306,7 +316,7 @@ void Manager::reload(const Paths& paths)
}
}
manipulator.removeBooksNotUpdatedSince(libRevision);
manipulator->removeBooksNotUpdatedSince(libRevision);
}
}

View File

@@ -17,8 +17,6 @@ kiwix_sources = [
'tools/regexTools.cpp',
'tools/stringTools.cpp',
'tools/networkTools.cpp',
'tools/opdsParsingTools.cpp',
'tools/languageTools.cpp',
'tools/otherTools.cpp',
'tools/archiveTools.cpp',
'kiwixserve.cpp',

View File

@@ -63,7 +63,7 @@ std::string HumanReadableNameMapper::getIdForName(const std::string& name) const
// UpdatableNameMapper
////////////////////////////////////////////////////////////////////////////////
UpdatableNameMapper::UpdatableNameMapper(LibraryPtr lib, bool withAlias)
UpdatableNameMapper::UpdatableNameMapper(Library& lib, bool withAlias)
: library(lib)
, withAlias(withAlias)
{
@@ -72,7 +72,7 @@ UpdatableNameMapper::UpdatableNameMapper(LibraryPtr lib, bool withAlias)
void UpdatableNameMapper::update()
{
const auto newNameMapper = new HumanReadableNameMapper(*library, withAlias);
const auto newNameMapper = new HumanReadableNameMapper(library, withAlias);
std::lock_guard<std::mutex> lock(mutex);
nameMapper.reset(newNameMapper);
}

View File

@@ -36,9 +36,16 @@ namespace kiwix
{
/* Constructor */
SearchRenderer::SearchRenderer(zim::SearchResultSet srs,
SearchRenderer::SearchRenderer(zim::SearchResultSet srs, NameMapper* mapper,
unsigned int start, unsigned int estimatedResultCount)
: SearchRenderer(srs, mapper, nullptr, start, estimatedResultCount)
{}
SearchRenderer::SearchRenderer(zim::SearchResultSet srs, NameMapper* mapper, Library* library,
unsigned int start, unsigned int estimatedResultCount)
: m_srs(srs),
mp_nameMapper(mapper),
mp_library(library),
protocolPrefix("zim://"),
searchProtocolPrefix("search://"),
estimatedResultCount(estimatedResultCount),
@@ -157,7 +164,7 @@ kainjow::mustache::data buildPagination(
return pagination;
}
std::string SearchRenderer::renderTemplate(const std::string& tmpl_str, const NameMapper& nameMapper, const Library* library)
std::string SearchRenderer::renderTemplate(const std::string& tmpl_str)
{
const std::string absPathPrefix = protocolPrefix;
// Build the results list
@@ -165,12 +172,12 @@ std::string SearchRenderer::renderTemplate(const std::string& tmpl_str, const Na
for (auto it = m_srs.begin(); it != m_srs.end(); it++) {
kainjow::mustache::data result;
const std::string zim_id(it.getZimId());
const auto path = nameMapper.getNameForId(zim_id) + "/" + it.getPath();
const auto path = mp_nameMapper->getNameForId(zim_id) + "/" + it.getPath();
result.set("title", it.getTitle());
result.set("absolutePath", absPathPrefix + urlEncode(path));
result.set("snippet", it.getSnippet());
if (library) {
result.set("bookTitle", library->getBookById(zim_id).getTitle());
if (mp_library) {
result.set("bookTitle", mp_library->getBookById(zim_id).getTitle());
}
if (it.getWordCount() >= 0) {
result.set("wordCount", kiwix::beautifyInteger(it.getWordCount()));
@@ -215,14 +222,14 @@ std::string SearchRenderer::renderTemplate(const std::string& tmpl_str, const Na
return ss.str();
}
std::string SearchRenderer::getHtml(const NameMapper& mapper, const Library* library)
std::string SearchRenderer::getHtml()
{
return renderTemplate(RESOURCE::templates::search_result_html, mapper, library);
return renderTemplate(RESOURCE::templates::search_result_html);
}
std::string SearchRenderer::getXml(const NameMapper& mapper, const Library* library)
std::string SearchRenderer::getXml()
{
return renderTemplate(RESOURCE::templates::search_result_xml, mapper, library);
return renderTemplate(RESOURCE::templates::search_result_xml);
}

View File

@@ -29,7 +29,7 @@
namespace kiwix {
Server::Server(LibraryPtr library, std::shared_ptr<NameMapper> nameMapper) :
Server::Server(Library* library, NameMapper* nameMapper) :
mp_library(library),
mp_nameMapper(nameMapper),
mp_server(nullptr)

View File

@@ -254,11 +254,6 @@ get_matching_if_none_match_etag(const RequestContext& r, const std::string& etag
}
}
struct NoDelete
{
template<class T> void operator()(T*) {}
};
} // unnamed namespace
std::pair<std::string, Library::BookIdSet> InternalServer::selectBooks(const RequestContext& request) const
@@ -411,8 +406,8 @@ public:
};
InternalServer::InternalServer(LibraryPtr library,
std::shared_ptr<NameMapper> nameMapper,
InternalServer::InternalServer(Library* library,
NameMapper* nameMapper,
std::string addr,
int port,
std::string root,
@@ -438,7 +433,7 @@ InternalServer::InternalServer(LibraryPtr library,
m_ipConnectionLimit(ipConnectionLimit),
mp_daemon(nullptr),
mp_library(library),
mp_nameMapper(nameMapper ? nameMapper : std::shared_ptr<NameMapper>(&defaultNameMapper, NoDelete())),
mp_nameMapper(nameMapper ? nameMapper : &defaultNameMapper),
searchCache(getEnvVar<int>("KIWIX_SEARCH_CACHE_SIZE", DEFAULT_CACHE_SIZE)),
suggestionSearcherCache(getEnvVar<int>("KIWIX_SUGGESTION_SEARCHER_CACHE_SIZE", std::max((unsigned int) (mp_library->getBookCount(true, true)*0.1), 1U))),
m_customizedResources(new CustomizedResources)
@@ -759,8 +754,7 @@ std::unique_ptr<Response> InternalServer::handle_viewer_settings(const RequestCo
const kainjow::mustache::object data{
{"enable_toolbar", m_withTaskbar ? "true" : "false" },
{"enable_link_blocking", m_blockExternalLinks ? "true" : "false" },
{"enable_library_button", m_withLibraryButton ? "true" : "false" },
{"default_user_language", request.get_user_language() }
{"enable_library_button", m_withLibraryButton ? "true" : "false" }
};
return ContentResponse::build(*this, RESOURCE::templates::viewer_settings_js, data, "application/javascript; charset=utf-8");
}
@@ -792,7 +786,7 @@ std::unique_ptr<Response> InternalServer::handle_no_js(const RequestContext& req
{
const auto url = request.get_url();
const auto urlParts = kiwix::split(url, "/", true, false);
HTMLDumper htmlDumper(mp_library.get(), mp_nameMapper.get());
HTMLDumper htmlDumper(mp_library, mp_nameMapper);
htmlDumper.setRootLocation(m_root);
htmlDumper.setLibraryId(getLibraryId());
auto userLang = request.get_user_language();
@@ -963,7 +957,7 @@ std::unique_ptr<Response> InternalServer::handle_search_request(const RequestCon
const auto pageLength = getSearchPageSize(request);
/* Get the results */
SearchRenderer renderer(search->getResults(start-1, pageLength), start,
SearchRenderer renderer(search->getResults(start-1, pageLength), mp_nameMapper, mp_library, start,
search->getEstimatedMatches());
renderer.setSearchPattern(searchInfo.pattern);
renderer.setSearchBookQuery(searchInfo.bookFilterQuery);
@@ -971,17 +965,9 @@ std::unique_ptr<Response> InternalServer::handle_search_request(const RequestCon
renderer.setSearchProtocolPrefix(m_root + "/search");
renderer.setPageLength(pageLength);
if (request.get_requested_format() == "xml") {
return ContentResponse::build(
*this,
renderer.getXml(*mp_nameMapper, mp_library.get()),
"application/rss+xml; charset=utf-8"
);
return ContentResponse::build(*this, renderer.getXml(), "application/rss+xml; charset=utf-8");
}
auto response = ContentResponse::build(
*this,
renderer.getHtml(*mp_nameMapper, mp_library.get()),
"text/html; charset=utf-8"
);
auto response = ContentResponse::build(*this, renderer.getHtml(), "text/html; charset=utf-8");
// XXX: Now this has to be handled by the iframe-based viewer which
// XXX: has to resolve if the book selection resulted in a single book.
/*

View File

@@ -92,8 +92,8 @@ class OPDSDumper;
class InternalServer {
public:
InternalServer(LibraryPtr library,
std::shared_ptr<NameMapper> nameMapper,
InternalServer(Library* library,
NameMapper* nameMapper,
std::string addr,
int port,
std::string root,
@@ -178,8 +178,8 @@ class InternalServer {
int m_ipConnectionLimit;
struct MHD_Daemon* mp_daemon;
LibraryPtr mp_library;
std::shared_ptr<NameMapper> mp_nameMapper;
Library* mp_library;
NameMapper* mp_nameMapper;
SearchCache searchCache;
SuggestionSearcherCache suggestionSearcherCache;

View File

@@ -82,7 +82,7 @@ std::unique_ptr<Response> InternalServer::handle_catalog(const RequestContext& r
}
zim::Uuid uuid;
kiwix::OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get());
kiwix::OPDSDumper opdsDumper(mp_library, mp_nameMapper);
opdsDumper.setRootLocation(m_root);
opdsDumper.setLibraryId(getLibraryId());
std::vector<std::string> bookIdsToDump;
@@ -164,7 +164,7 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_root(const RequestCo
std::unique_ptr<Response> InternalServer::handle_catalog_v2_entries(const RequestContext& request, bool partial)
{
OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get());
OPDSDumper opdsDumper(mp_library, mp_nameMapper);
opdsDumper.setRootLocation(m_root);
opdsDumper.setLibraryId(getLibraryId());
const auto bookIds = search_catalog(request, opdsDumper);
@@ -185,7 +185,7 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_complete_entry(const
+ urlNotFoundMsg;
}
OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get());
OPDSDumper opdsDumper(mp_library, mp_nameMapper);
opdsDumper.setRootLocation(m_root);
opdsDumper.setLibraryId(getLibraryId());
const auto opdsFeed = opdsDumper.dumpOPDSCompleteEntry(entryId);
@@ -198,7 +198,7 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_complete_entry(const
std::unique_ptr<Response> InternalServer::handle_catalog_v2_categories(const RequestContext& request)
{
OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get());
OPDSDumper opdsDumper(mp_library, mp_nameMapper);
opdsDumper.setRootLocation(m_root);
opdsDumper.setLibraryId(getLibraryId());
return ContentResponse::build(
@@ -210,7 +210,7 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_categories(const Req
std::unique_ptr<Response> InternalServer::handle_catalog_v2_languages(const RequestContext& request)
{
OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get());
OPDSDumper opdsDumper(mp_library, mp_nameMapper);
opdsDumper.setRootLocation(m_root);
opdsDumper.setLibraryId(getLibraryId());
return ContentResponse::build(

View File

@@ -202,12 +202,21 @@ std::string RequestContext::get_user_language() const
return userlang.lang;
}
bool RequestContext::user_language_comes_from_cookie() const
{
return userlang.selectedBy == UserLanguage::SelectorKind::COOKIE;
}
RequestContext::UserLanguage RequestContext::determine_user_language() const
{
try {
return {UserLanguage::SelectorKind::QUERY_PARAM, get_argument("userlang")};
} catch(const std::out_of_range&) {}
try {
return {UserLanguage::SelectorKind::COOKIE, cookies.at("userlang")};
} catch(const std::out_of_range&) {}
try {
const std::string acceptLanguage = get_header("Accept-Language");
const auto userLangPrefs = parseUserLanguagePreferences(acceptLanguage);

View File

@@ -119,12 +119,15 @@ class RequestContext {
std::string get_user_language() const;
std::string get_requested_format() const;
bool user_language_comes_from_cookie() const;
private: // types
struct UserLanguage
{
enum SelectorKind
{
QUERY_PARAM,
COOKIE,
ACCEPT_LANGUAGE_HEADER,
DEFAULT
};

View File

@@ -387,6 +387,13 @@ MHD_Result Response::send(const RequestContext& request, MHD_Connection* connect
MHD_add_response_header(response, p.first.c_str(), p.second.c_str());
}
if ( ! request.user_language_comes_from_cookie() ) {
const std::string cookie = "userlang=" + request.get_user_language()
+ ";Path=" + request.get_root_path()
+ ";Max-Age=31536000";
MHD_add_response_header(response, MHD_HTTP_HEADER_SET_COOKIE, cookie.c_str());
}
if (m_returnCode == MHD_HTTP_OK && m_byteRange.kind() == ByteRange::RESOLVED_PARTIAL_CONTENT)
m_returnCode = MHD_HTTP_PARTIAL_CONTENT;

View File

@@ -1,75 +0,0 @@
#include "tools.h"
#include "stringTools.h"
#include <mutex>
namespace kiwix
{
namespace
{
// These mappings are not provided by the ICU library, any such mappings can be manually added here
std::map<std::string, std::string> iso639_3 = {
{"atj", "atikamekw"},
{"azb", "آذربایجان دیلی"},
{"bcl", "central bikol"},
{"bgs", "tagabawa"},
{"bxr", "буряад хэлэн"},
{"cbk", "chavacano"},
{"cdo", "閩東語"},
{"dag", "Dagbani"},
{"diq", "dimli"},
{"dty", "डोटेली"},
{"eml", "emiliân-rumagnōl"},
{"fbs", "српскохрватски"},
{"fon", "fɔ̀ngbè"},
{"guw", "Gungbe"},
{"hbs", "srpskohrvatski"},
{"ido", "ido"},
{"kbp", "kabɩ"},
{"kld", "Gamilaraay"},
{"lbe", "лакку маз"},
{"lbj", "ལ་དྭགས་སྐད་"},
{"map", "Austronesian"},
{"mhr", "марий йылме"},
{"mnw", "ဘာသာမန်"},
{"myn", "mayan"},
{"nah", "nahuatl"},
{"nai", "north American Indian"},
{"nds", "plattdütsch"},
{"nrm", "bhasa narom"},
{"olo", "livvi"},
{"pih", "Pitcairn-Norfolk"},
{"pnb", "Western Panjabi"},
{"rmr", "Caló"},
{"rmy", "romani shib"},
{"roa", "romance languages"},
{"twi", "twi"},
// ICU for Ubuntu versions <= focal (20.04) returns "" for the language code ""
// unlike the later versions - which returns "und". We map this value to "Undetermined" for a common ground.
{"", "Undetermined"},
};
std::once_flag fillLanguagesFlag;
void fillLanguagesMap()
{
for (auto icuLangPtr = icu::Locale::getISOLanguages(); *icuLangPtr != NULL; ++icuLangPtr) {
const kiwix::ICULanguageInfo lang(*icuLangPtr);
iso639_3.insert({lang.iso3Code(), lang.selfName()});
}
}
} // unnamed namespace
std::string getLanguageSelfName(const std::string& lang)
{
std::call_once(fillLanguagesFlag, fillLanguagesMap);
const auto itr = iso639_3.find(lang);
if (itr != iso639_3.end()) {
return itr->second;
}
return lang;
};
} // namespace kiwix

View File

@@ -1,70 +0,0 @@
#include "tools.h"
#include <pugixml.hpp>
namespace kiwix
{
namespace
{
#define VALUE(name) entryNode.child(name).child_value()
FeedLanguages parseLanguages(const pugi::xml_document& doc)
{
pugi::xml_node feedNode = doc.child("feed");
FeedLanguages langs;
for (pugi::xml_node entryNode = feedNode.child("entry"); entryNode;
entryNode = entryNode.next_sibling("entry")) {
auto title = VALUE("title");
auto code = VALUE("dc:language");
langs.push_back({code, title});
}
return langs;
}
FeedCategories parseCategories(const pugi::xml_document& doc)
{
pugi::xml_node feedNode = doc.child("feed");
FeedCategories categories;
for (pugi::xml_node entryNode = feedNode.child("entry"); entryNode;
entryNode = entryNode.next_sibling("entry")) {
auto title = VALUE("title");
categories.push_back(title);
}
return categories;
}
} // unnamed namespace
FeedLanguages readLanguagesFromFeed(const std::string& content)
{
pugi::xml_document doc;
pugi::xml_parse_result result
= doc.load_buffer((void*)content.data(), content.size());
if (result) {
auto langs = parseLanguages(doc);
return langs;
}
return FeedLanguages();
}
FeedCategories readCategoriesFromFeed(const std::string& content)
{
pugi::xml_document doc;
pugi::xml_parse_result result
= doc.load_buffer((void*)content.data(), content.size());
FeedCategories categories;
if (result) {
categories = parseCategories(doc);
return categories;
}
return categories;
}
} // namespace kiwix

View File

@@ -330,19 +330,17 @@ std::string kiwix::render_template(const std::string& template_str, kainjow::mus
namespace
{
std::string escapeForJSON(const std::string& s)
std::string escapeBackslashes(const std::string& s)
{
std::ostringstream oss;
std::string es;
es.reserve(s.size());
for (char c : s) {
if ( c == '\\' ) {
oss << "\\\\";
} else if ( unsigned(c) < 0x20U ) {
oss << "\\u" << std::setw(4) << std::setfill('0') << unsigned(c);
} else {
oss << c;
es.push_back('\\');
}
es.push_back(c);
}
return oss.str();
return es;
}
std::string makeFulltextSearchSuggestion(const std::string& lang,
@@ -370,10 +368,10 @@ void kiwix::Suggestions::add(const zim::SuggestionItem& suggestion)
? suggestion.getSnippet()
: suggestion.getTitle();
result.set("label", escapeForJSON(label));
result.set("value", escapeForJSON(suggestion.getTitle()));
result.set("label", escapeBackslashes(label));
result.set("value", escapeBackslashes(suggestion.getTitle()));
result.set("kind", "path");
result.set("path", escapeForJSON(suggestion.getPath()));
result.set("path", escapeBackslashes(suggestion.getPath()));
result.set("first", m_data.is_empty_list());
m_data.push_back(result);
}
@@ -383,8 +381,8 @@ void kiwix::Suggestions::addFTSearchSuggestion(const std::string& uiLang,
{
kainjow::mustache::data result;
const std::string label = makeFulltextSearchSuggestion(uiLang, queryString);
result.set("label", escapeForJSON(label));
result.set("value", escapeForJSON(queryString + " "));
result.set("label", escapeBackslashes(label));
result.set("value", escapeBackslashes(queryString + " "));
result.set("kind", "pattern");
result.set("first", m_data.is_empty_list());
m_data.push_back(result);

View File

@@ -31,6 +31,7 @@
namespace kiwix
{
std::string beautifyInteger(uint64_t number);
std::string beautifyFileSize(uint64_t number);
void printStringInHexadecimal(const char* s);
void printStringInHexadecimal(icu::UnicodeString s);
void stringReplacement(std::string& str,

View File

@@ -5,11 +5,9 @@ skin/i18n/de.json
skin/i18n/dga.json
skin/i18n/el.json
skin/i18n/en.json
skin/i18n/es.json
skin/i18n/fi.json
skin/i18n/fr.json
skin/i18n/he.json
skin/i18n/hi.json
skin/i18n/hy.json
skin/i18n/ia.json
skin/i18n/it.json
@@ -18,19 +16,14 @@ skin/i18n/ko.json
skin/i18n/ku-latn.json
skin/i18n/lb.json
skin/i18n/mk.json
skin/i18n/ms.json
skin/i18n/nl.json
skin/i18n/nqo.json
skin/i18n/or.json
skin/i18n/pl.json
skin/i18n/ru.json
skin/i18n/sc.json
skin/i18n/sk.json
skin/i18n/skr-arab.json
skin/i18n/sl.json
skin/i18n/sq.json
skin/i18n/sv.json
skin/i18n/te.json
skin/i18n/test.json
skin/i18n/tr.json
skin/i18n/zh-hans.json

View File

@@ -9,8 +9,7 @@ skin/search-icon.svg
skin/iso6391To3.js
skin/isotope.pkgd.min.js
skin/index.js
skin/autoComplete/autoComplete.min.js
skin/kiwix.css
skin/autoComplete.min.js
skin/taskbar.css
skin/index.css
skin/fonts/Poppins.ttf
@@ -43,7 +42,7 @@ templates/no_js_download.html
opensearchdescription.xml
ft_opensearchdescription.xml
catalog_v2_searchdescription.xml
skin/autoComplete/css/autoComplete.css
skin/css/autoComplete.css
skin/favicon/android-chrome-192x192.png
skin/favicon/android-chrome-512x512.png
skin/favicon/apple-touch-icon.png

View File

@@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -1,654 +0,0 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.autoComplete = factory());
}(this, (function () { 'use strict';
function ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(object);
if (enumerableOnly) {
symbols = symbols.filter(function (sym) {
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
});
}
keys.push.apply(keys, symbols);
}
return keys;
}
function _objectSpread2(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i] != null ? arguments[i] : {};
if (i % 2) {
ownKeys(Object(source), true).forEach(function (key) {
_defineProperty(target, key, source[key]);
});
} else if (Object.getOwnPropertyDescriptors) {
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
} else {
ownKeys(Object(source)).forEach(function (key) {
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
});
}
}
return target;
}
function _typeof(obj) {
"@babel/helpers - typeof";
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
_typeof = function (obj) {
return typeof obj;
};
} else {
_typeof = function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
}
return _typeof(obj);
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function _toConsumableArray(arr) {
return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
}
function _arrayWithoutHoles(arr) {
if (Array.isArray(arr)) return _arrayLikeToArray(arr);
}
function _iterableToArray(iter) {
if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _nonIterableSpread() {
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _createForOfIteratorHelper(o, allowArrayLike) {
var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"];
if (!it) {
if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
if (it) o = it;
var i = 0;
var F = function () {};
return {
s: F,
n: function () {
if (i >= o.length) return {
done: true
};
return {
done: false,
value: o[i++]
};
},
e: function (e) {
throw e;
},
f: F
};
}
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
var normalCompletion = true,
didErr = false,
err;
return {
s: function () {
it = it.call(o);
},
n: function () {
var step = it.next();
normalCompletion = step.done;
return step;
},
e: function (e) {
didErr = true;
err = e;
},
f: function () {
try {
if (!normalCompletion && it.return != null) it.return();
} finally {
if (didErr) throw err;
}
}
};
}
var select$1 = function select(element) {
return typeof element === "string" ? document.querySelector(element) : element();
};
var create = function create(tag, options) {
var el = typeof tag === "string" ? document.createElement(tag) : tag;
for (var key in options) {
var val = options[key];
if (key === "inside") {
val.append(el);
} else if (key === "dest") {
select$1(val[0]).insertAdjacentElement(val[1], el);
} else if (key === "around") {
var ref = val;
ref.parentNode.insertBefore(el, ref);
el.append(ref);
if (ref.getAttribute("autofocus") != null) ref.focus();
} else if (key in el) {
el[key] = val;
} else {
el.setAttribute(key, val);
}
}
return el;
};
var getQuery = function getQuery(field) {
return field instanceof HTMLInputElement || field instanceof HTMLTextAreaElement ? field.value : field.innerHTML;
};
var format = function format(value, diacritics) {
value = value.toString().toLowerCase();
return diacritics ? value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").normalize("NFC") : value;
};
var debounce = function debounce(callback, duration) {
var timer;
return function () {
clearTimeout(timer);
timer = setTimeout(function () {
return callback();
}, duration);
};
};
var checkTrigger = function checkTrigger(query, condition, threshold) {
return condition ? condition(query) : query.length >= threshold;
};
var mark = function mark(value, cls) {
return create("mark", _objectSpread2({
innerHTML: value
}, typeof cls === "string" && {
"class": cls
})).outerHTML;
};
var configure = (function (ctx) {
var name = ctx.name,
options = ctx.options,
resultsList = ctx.resultsList,
resultItem = ctx.resultItem;
for (var option in options) {
if (_typeof(options[option]) === "object") {
if (!ctx[option]) ctx[option] = {};
for (var subOption in options[option]) {
ctx[option][subOption] = options[option][subOption];
}
} else {
ctx[option] = options[option];
}
}
ctx.selector = ctx.selector || "#" + name;
resultsList.destination = resultsList.destination || ctx.selector;
resultsList.id = resultsList.id || name + "_list_" + ctx.id;
resultItem.id = resultItem.id || name + "_result";
ctx.input = select$1(ctx.selector);
});
var eventEmitter = (function (name, ctx) {
ctx.input.dispatchEvent(new CustomEvent(name, {
bubbles: true,
detail: ctx.feedback,
cancelable: true
}));
});
var search = (function (query, record, options) {
var _ref = options || {},
mode = _ref.mode,
diacritics = _ref.diacritics,
highlight = _ref.highlight;
var nRecord = format(record, diacritics);
record = record.toString();
query = format(query, diacritics);
if (mode === "loose") {
query = query.replace(/ /g, "");
var qLength = query.length;
var cursor = 0;
var match = Array.from(record).map(function (character, index) {
if (cursor < qLength && nRecord[index] === query[cursor]) {
character = highlight ? mark(character, highlight) : character;
cursor++;
}
return character;
}).join("");
if (cursor === qLength) return match;
} else {
var _match = nRecord.indexOf(query);
if (~_match) {
query = record.substring(_match, _match + query.length);
_match = highlight ? record.replace(query, mark(query, highlight)) : record;
return _match;
}
}
});
var getData = function getData(ctx, query) {
return new Promise(function ($return, $error) {
var data;
data = ctx.data;
if (data.cache && data.store) return $return();
return new Promise(function ($return, $error) {
if (typeof data.src === "function") {
return data.src(query).then($return, $error);
}
return $return(data.src);
}).then(function ($await_4) {
try {
ctx.feedback = data.store = $await_4;
eventEmitter("response", ctx);
return $return();
} catch ($boundEx) {
return $error($boundEx);
}
}, $error);
});
};
var findMatches = function findMatches(query, ctx) {
var data = ctx.data,
searchEngine = ctx.searchEngine;
var matches = [];
data.store.forEach(function (value, index) {
var find = function find(key) {
var record = key ? value[key] : value;
var match = typeof searchEngine === "function" ? searchEngine(query, record) : search(query, record, {
mode: searchEngine,
diacritics: ctx.diacritics,
highlight: ctx.resultItem.highlight
});
if (!match) return;
var result = {
match: match,
value: value
};
if (key) result.key = key;
matches.push(result);
};
if (data.keys) {
var _iterator = _createForOfIteratorHelper(data.keys),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var key = _step.value;
find(key);
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
} else {
find();
}
});
if (data.filter) matches = data.filter(matches);
var results = matches.slice(0, ctx.resultsList.maxResults);
ctx.feedback = {
query: query,
matches: matches,
results: results
};
eventEmitter("results", ctx);
};
var Expand = "aria-expanded";
var Active = "aria-activedescendant";
var Selected = "aria-selected";
var feedback = function feedback(ctx, index) {
ctx.feedback.selection = _objectSpread2({
index: index
}, ctx.feedback.results[index]);
};
var render = function render(ctx) {
var resultsList = ctx.resultsList,
list = ctx.list,
resultItem = ctx.resultItem,
feedback = ctx.feedback;
var matches = feedback.matches,
results = feedback.results;
ctx.cursor = -1;
list.innerHTML = "";
if (matches.length || resultsList.noResults) {
var fragment = new DocumentFragment();
results.forEach(function (result, index) {
var element = create(resultItem.tag, _objectSpread2({
id: "".concat(resultItem.id, "_").concat(index),
role: "option",
innerHTML: result.match,
inside: fragment
}, resultItem["class"] && {
"class": resultItem["class"]
}));
if (resultItem.element) resultItem.element(element, result);
});
list.append(fragment);
if (resultsList.element) resultsList.element(list, feedback);
open(ctx);
} else {
close(ctx);
}
};
var open = function open(ctx) {
if (ctx.isOpen) return;
(ctx.wrapper || ctx.input).setAttribute(Expand, true);
ctx.list.removeAttribute("hidden");
ctx.isOpen = true;
eventEmitter("open", ctx);
};
var close = function close(ctx) {
if (!ctx.isOpen) return;
(ctx.wrapper || ctx.input).setAttribute(Expand, false);
ctx.input.setAttribute(Active, "");
ctx.list.setAttribute("hidden", "");
ctx.isOpen = false;
eventEmitter("close", ctx);
};
var goTo = function goTo(index, ctx) {
var resultItem = ctx.resultItem;
var results = ctx.list.getElementsByTagName(resultItem.tag);
var cls = resultItem.selected ? resultItem.selected.split(" ") : false;
if (ctx.isOpen && results.length) {
var _results$index$classL;
var state = ctx.cursor;
if (index >= results.length) index = 0;
if (index < 0) index = results.length - 1;
ctx.cursor = index;
if (state > -1) {
var _results$state$classL;
results[state].removeAttribute(Selected);
if (cls) (_results$state$classL = results[state].classList).remove.apply(_results$state$classL, _toConsumableArray(cls));
}
results[index].setAttribute(Selected, true);
if (cls) (_results$index$classL = results[index].classList).add.apply(_results$index$classL, _toConsumableArray(cls));
ctx.input.setAttribute(Active, results[ctx.cursor].id);
ctx.list.scrollTop = results[index].offsetTop - ctx.list.clientHeight + results[index].clientHeight + 5;
ctx.feedback.cursor = ctx.cursor;
feedback(ctx, index);
eventEmitter("navigate", ctx);
}
};
var next = function next(ctx) {
goTo(ctx.cursor + 1, ctx);
};
var previous = function previous(ctx) {
goTo(ctx.cursor - 1, ctx);
};
var select = function select(ctx, event, index) {
index = index >= 0 ? index : ctx.cursor;
if (index < 0) return;
ctx.feedback.event = event;
feedback(ctx, index);
eventEmitter("selection", ctx);
close(ctx);
};
var click = function click(event, ctx) {
var itemTag = ctx.resultItem.tag.toUpperCase();
var items = Array.from(ctx.list.querySelectorAll(itemTag));
var item = event.target.closest(itemTag);
if (item && item.nodeName === itemTag) {
select(ctx, event, items.indexOf(item));
}
};
var navigate = function navigate(event, ctx) {
switch (event.keyCode) {
case 40:
case 38:
event.preventDefault();
event.keyCode === 40 ? next(ctx) : previous(ctx);
break;
case 13:
if (!ctx.submit) event.preventDefault();
if (ctx.cursor >= 0) select(ctx, event);
break;
case 9:
if (ctx.resultsList.tabSelect && ctx.cursor >= 0) select(ctx, event);
break;
case 27:
ctx.input.value = "";
close(ctx);
break;
}
};
function start (ctx, q) {
var _this = this;
return new Promise(function ($return, $error) {
var queryVal, condition;
queryVal = q || getQuery(ctx.input);
queryVal = ctx.query ? ctx.query(queryVal) : queryVal;
condition = checkTrigger(queryVal, ctx.trigger, ctx.threshold);
if (condition) {
return getData(ctx, queryVal).then(function ($await_2) {
try {
if (ctx.feedback instanceof Error) return $return();
findMatches(queryVal, ctx);
if (ctx.resultsList) render(ctx);
return $If_1.call(_this);
} catch ($boundEx) {
return $error($boundEx);
}
}, $error);
} else {
close(ctx);
return $If_1.call(_this);
}
function $If_1() {
return $return();
}
});
}
var eventsManager = function eventsManager(events, callback) {
for (var element in events) {
for (var event in events[element]) {
callback(element, event);
}
}
};
var addEvents = function addEvents(ctx) {
var events = ctx.events;
var run = debounce(function () {
return start(ctx);
}, ctx.debounce);
var publicEvents = ctx.events = _objectSpread2({
input: _objectSpread2({}, events && events.input)
}, ctx.resultsList && {
list: events ? _objectSpread2({}, events.list) : {}
});
var privateEvents = {
input: {
input: function input() {
run();
},
keydown: function keydown(event) {
navigate(event, ctx);
},
blur: function blur() {
close(ctx);
}
},
list: {
mousedown: function mousedown(event) {
event.preventDefault();
},
click: function click$1(event) {
click(event, ctx);
}
}
};
eventsManager(privateEvents, function (element, event) {
if (!ctx.resultsList && event !== "input") return;
if (publicEvents[element][event]) return;
publicEvents[element][event] = privateEvents[element][event];
});
eventsManager(publicEvents, function (element, event) {
ctx[element].addEventListener(event, publicEvents[element][event]);
});
};
var removeEvents = function removeEvents(ctx) {
eventsManager(ctx.events, function (element, event) {
ctx[element].removeEventListener(event, ctx.events[element][event]);
});
};
function init (ctx) {
var _this = this;
return new Promise(function ($return, $error) {
var placeHolder, resultsList, parentAttrs;
placeHolder = ctx.placeHolder;
resultsList = ctx.resultsList;
parentAttrs = {
role: "combobox",
"aria-owns": resultsList.id,
"aria-haspopup": true,
"aria-expanded": false
};
create(ctx.input, _objectSpread2(_objectSpread2({
"aria-controls": resultsList.id,
"aria-autocomplete": "both"
}, placeHolder && {
placeholder: placeHolder
}), !ctx.wrapper && _objectSpread2({}, parentAttrs)));
if (ctx.wrapper) ctx.wrapper = create("div", _objectSpread2({
around: ctx.input,
"class": ctx.name + "_wrapper"
}, parentAttrs));
if (resultsList) ctx.list = create(resultsList.tag, _objectSpread2({
dest: [resultsList.destination, resultsList.position],
id: resultsList.id,
role: "listbox",
hidden: "hidden"
}, resultsList["class"] && {
"class": resultsList["class"]
}));
addEvents(ctx);
if (ctx.data.cache) {
return getData(ctx).then(function ($await_2) {
try {
return $If_1.call(_this);
} catch ($boundEx) {
return $error($boundEx);
}
}, $error);
}
function $If_1() {
eventEmitter("init", ctx);
return $return();
}
return $If_1.call(_this);
});
}
function extend (autoComplete) {
var prototype = autoComplete.prototype;
prototype.init = function () {
init(this);
};
prototype.start = function (query) {
start(this, query);
};
prototype.unInit = function () {
if (this.wrapper) {
var parentNode = this.wrapper.parentNode;
parentNode.insertBefore(this.input, this.wrapper);
parentNode.removeChild(this.wrapper);
}
removeEvents(this);
};
prototype.open = function () {
open(this);
};
prototype.close = function () {
close(this);
};
prototype.goTo = function (index) {
goTo(index, this);
};
prototype.next = function () {
next(this);
};
prototype.previous = function () {
previous(this);
};
prototype.select = function (index) {
select(this, null, index);
};
prototype.search = function (query, record, options) {
return search(query, record, options);
};
}
function autoComplete(config) {
this.options = config;
this.id = autoComplete.instances = (autoComplete.instances || 0) + 1;
this.name = "autoComplete";
this.wrapper = 1;
this.threshold = 1;
this.debounce = 0;
this.resultsList = {
position: "afterend",
tag: "ul",
maxResults: 5
};
this.resultItem = {
tag: "li"
};
configure(this);
extend.call(this, autoComplete);
init(this);
}
return autoComplete;
})));

View File

@@ -1,4 +1,3 @@
/* Modified from https://github.com/TarekRaafat/autoComplete.js (version 10.2.6)*/
.autoComplete_wrapper {
display: inline-block;
position: relative;

View File

@@ -69,19 +69,30 @@ function $t(msgId, params={}) {
}
}
function getCookie(cookieName) {
const name = cookieName + "=";
let result;
decodeURIComponent(document.cookie).split('; ').forEach(val => {
if (val.indexOf(name) === 0) {
result = val.substring(name.length);
}
});
return result;
}
const DEFAULT_UI_LANGUAGE = 'en';
Translations.load(DEFAULT_UI_LANGUAGE, /*asDefault=*/true);
function getUserLanguage() {
return new URLSearchParams(window.location.search).get('userlang')
|| window.localStorage.getItem('userlang')
|| viewerSettings.defaultUserLanguage
|| getCookie('userlang')
|| DEFAULT_UI_LANGUAGE;
}
function setUserLanguage(lang, callback) {
window.localStorage.setItem('userlang', lang);
setPermanentGlobalCookie('userlang', lang);
Translations.load(lang);
Translations.whenReady(callback);
}

View File

@@ -1,57 +1,20 @@
{
"@metadata": {
"authors": [
"IMayBeABitShy",
"Lucas Werkmeister",
"ThisCarthing"
]
},
"name": "Deutsch",
"suggest-full-text-search": "enthält '{{{SEARCH_TERMS}}}'...",
"no-such-book": "Buch nicht gefunden: {{BOOK_NAME}}",
"too-many-books": "Zu viele Bücher angefragt ({{NB_BOOKS}}), die Beschränkung liegt bei {{LIMIT}}",
"no-book-found": "Keine Bücher entsprechen den Auswahlkriterien",
"url-not-found": "Die angeforderte URL \"{{url}}\" konnte auf diesem Server nicht gefunden werden.",
"suggest-search": "Führe eine Volltextsuche nach <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a> durch",
"random-article-failure": "Hoppla! Konnte keinen zufälligen Artikel auswählen :(",
"invalid-raw-data-type": "{{DATATYPE}} ist keine gültige Anfrage für unverarbeiteten Inhalt",
"no-value-for-arg": "Kein Wert für den Parameter {{ARGUMENT}} gegeben",
"no-query": "Keine Suchanfrage gegeben.",
"raw-entry-not-found": "Eintrag {{ENTRY}} des Typs {{DATATYPE}} konnte nicht gefunden werden.",
"400-page-title": "Ungültige Anfrage",
"400-page-heading": "Ungültige Anfrage",
"404-page-title": "Inhalt nicht gefunden",
"404-page-heading": "Nicht gefunden",
"500-page-title": "Interner Server-Fehler",
"500-page-heading": "Interner Server-Fehler",
"fulltext-search-unavailable": "Die Volltestsuche steht nicht zur Verfügung.",
"no-search-results": "Die Volltextsuche ist für diesen Inhalt nicht verfügbar.",
"library-button-text": "Zur Willkommensseite gehen",
"home-button-text": "Zur Hauptseite von '{{BOOK_TITLE}}' gehen",
"random-page-button-text": "Zu einer zufällig ausgewählten Seite gehen",
"searchbox-tooltip": "Nach '{{BOOK_TITLE}}' suchen",
"confusion-of-tongues": "Zwei oder mehr Bücher unterschiedlicher Sprachen werden durchsucht, was zu unübersichtlichen Ergebnissen führen kann.",
"welcome-page-overzealous-filter": "Keine Ergebnisse gefunden. Möchten Sie den <a href=\"{{URL}}\">Filter zurücksetzen</a>?",
"powered-by-kiwix-html": "Angetrieben durch &nbsp;<a href=\"https://kiwix.org\">Kiwix</a>",
"search": "Suchen",
"book-filtering-all-categories": "Alle Kategorien",
"book-filtering-all-languages": "Alle Sprachen",
"count-of-matching-books": "{{COUNT}} Bücher",
"download": "Herunterladen",
"direct-download-link-text": "Direkt",
"direct-download-alt-text": "direkt herunterladen",
"hash-download-link-text": "Sha256 Hash",
"hash-download-alt-text": "Hash herunterladen",
"magnet-link-text": "Magnet Link",
"magnet-alt-text": "Magnet Link herunterladen",
"torrent-download-link-text": "Torrent-Datei",
"torrent-download-alt-text": "Torrent herunterladen",
"library-opds-feed-all-entries": "ODPS Feed der Bibliothek - Alle Einträge",
"filter-by-tag": "Nach Tag \"{{TAG}}\" filtern",
"stop-filtering-by-tag": "Filterung nach Tag \"{{TAG}}\" aufheben",
"library-opds-feed-parameterised": "ODPS Feed der Bibliothek - Einträge mit {{#LANG}\nSprache {{LANG}} {{/LANG}}{{#CATEGORY}}\nKategorie: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nTag: {{TAG}}{{/TAG}}{{#Q}}\nQuery: {{Q}} {{/Q}}",
"welcome-to-kiwix-server": "Wilkommen beim Kiwix Server",
"download-links-heading": "Download Links für <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Buch herunterladen",
"preview-book": "Vorschau"
"searchbox-tooltip": "Nach '{{BOOK_TITLE}}' suchen"
}

View File

@@ -1,57 +0,0 @@
{
"@metadata": {
"authors": [
"Fitoschido",
"Ovruni",
"SpikeShroom",
"Vis4valentine"
]
},
"name": "español",
"suggest-full-text-search": "que contenga \"{{{SEARCH_TERMS}}}\"...",
"no-such-book": "No existe el libro: {{BOOK_NAME}}",
"too-many-books": "Demasiadas solicitudes de libros ({{NB_BOOKS}}) donde el límite es {{LIMIT}}",
"no-book-found": "Ningún libro coincide con los criterios de selección.",
"url-not-found": "La URL solicitada \"{{url}}\" no se encontró en este servidor.",
"suggest-search": "Haga una búsqueda de texto completo para <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
"random-article-failure": "¡Ups! Error al elegir un artículo aleatorio :(",
"invalid-raw-data-type": "{{DATATYPE}} no es una solicitud válida de contenido en crudo.",
"no-value-for-arg": "No se ha proporcionado ningún valor para el argumento {{ARGUMENT}}",
"no-query": "No se ha proporcionado ninguna consulta.",
"raw-entry-not-found": "No se puede encontrar la entrada {{DATATYPE}} {{ENTRY}}",
"400-page-title": "Solicitud inválida",
"400-page-heading": "Solicitud inválida",
"404-page-title": "Contenido no encontrado",
"404-page-heading": "No encontrado",
"500-page-title": "Error interno del servidor",
"500-page-heading": "Error interno del servidor",
"fulltext-search-unavailable": "Búsqueda de texto completo no disponible",
"no-search-results": "El motor de búsqueda de texto completo no está disponible para este contenido.",
"library-button-text": "Ir a la página de bienvenida",
"home-button-text": "Ir a la página principal de '{{BOOK_TITLE}}'",
"random-page-button-text": "Ir a una página seleccionada al azar",
"searchbox-tooltip": "Buscar '{{BOOK_TITLE}}'",
"confusion-of-tongues": "Dos o más libros en diferentes idiomas participarían en la búsqueda, lo que puede llevar a resultados confusos.",
"welcome-page-overzealous-filter": "Sin resultados. ¿Quieres <a href=\"{{URL}}\">restablecer el filtro</a> ?",
"powered-by-kiwix-html": "Desarrollado por&nbsp;<a href=\"https://kiwix.org\">Kiwix</a>",
"search": "Buscar",
"book-filtering-all-categories": "Todas las categorías",
"book-filtering-all-languages": "Todos los idiomas",
"count-of-matching-books": "{{COUNT}} libro(s)",
"download": "Descargar",
"direct-download-link-text": "Directamente",
"direct-download-alt-text": "descarga directa",
"hash-download-link-text": "hash sha256",
"hash-download-alt-text": "descargar hash",
"magnet-link-text": "Enlace magnético",
"magnet-alt-text": "Descargar link magnético",
"torrent-download-link-text": "Archivo de torrent",
"torrent-download-alt-text": "descargar torrent",
"filter-by-tag": "Filtrar por etiqueta \"{{TAG}}\"",
"stop-filtering-by-tag": "Dejar de filtrar por etiqueta \"{{TAG}}\"",
"library-opds-feed-parameterised": "Feed OPDS de la biblioteca: entradas que coinciden con {{#LANG}}\nLanguage: {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategory: {{CATEGORY}} {{/CATEGORY}} {{#TAG}}\nEtiqueta: {{TAG}} {{/TAG}}{{#Q}}\nConsulta: {{Q}} {{/Q}}",
"welcome-to-kiwix-server": "Bienvenido al servidor Kiwix",
"download-links-heading": "Enlaces de descarga para <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Descargar libro",
"preview-book": "Previsualizar"
}

View File

@@ -2,12 +2,11 @@
"@metadata": {
"authors": [
"Gomoko",
"Stephane",
"Thibaut120094",
"Verdy p"
]
},
"name": "Français",
"name": "français",
"suggest-full-text-search": "contenant « {{{SEARCH_TERMS}}} »...",
"no-such-book": "Aucun livre avec ce nom: {{BOOK_NAME}}",
"too-many-books": "Trop de livres demandés ({{NB_BOOKS}}) alors que la limite est de {{LIMIT}}",

View File

@@ -1,56 +0,0 @@
{
"@metadata": {
"authors": [
"Abijeet Patro",
"Juuz0"
]
},
"name": "हिन्दी",
"suggest-full-text-search": "जिसमें '{{{SEARCH_TERMS}}}' शामिल है...",
"no-such-book": "ऐसी कोई किताब नहीं: {{BOOK_NAME}}",
"too-many-books": "बहुत सारी पुस्तकों का अनुरोध किया गया है ({{NB_BOOKS}}) जहां सीमा {{LIMIT}} है",
"no-book-found": "कोई भी पुस्तक चयन मानदंड से मेल नहीं खाती",
"url-not-found": "अनुरोधित URL \"{{url}}\" इस सर्वर पर नहीं मिला।",
"suggest-search": "<a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a> के लिए पूर्ण पाठ खोज करें",
"random-article-failure": "उफ़! एक यादृच्छिक लेख चुनने में विफल :(",
"invalid-raw-data-type": "{{DATATYPE}} कच्ची सामग्री के लिए वैध अनुरोध नहीं है।",
"no-value-for-arg": "तर्क के लिए कोई मूल्य प्रदान नहीं किया गया {{ARGUMENT}}",
"no-query": "कोई प्रश्न नहीं दिया गया.",
"raw-entry-not-found": "{{DATATYPE}} प्रविष्टि {{ENTRY}} नहीं मिल सकी",
"400-page-title": "अवैध आवेदन",
"400-page-heading": "अवैध आवेदन",
"404-page-title": "सामग्री नहीं मिली",
"404-page-heading": "नहीं मिला",
"500-page-title": "आंतरिक सर्वर त्रुटि",
"500-page-heading": "आंतरिक सर्वर त्रुटि",
"fulltext-search-unavailable": "पूर्णपाठ खोज अनुपलब्ध",
"no-search-results": "इस सामग्री के लिए पूर्णपाठ खोज इंजन उपलब्ध नहीं है।",
"library-button-text": "स्वागत पृष्ठ पर जाएँ",
"home-button-text": "'{{BOOK_TITLE}}' के मुख्य पृष्ठ पर जाएँ",
"random-page-button-text": "यादृच्छिक रूप से चयनित पृष्ठ पर जाएँ",
"searchbox-tooltip": "'{{BOOK_TITLE}}' खोजें",
"confusion-of-tongues": "विभिन्न भाषाओं की दो या दो से अधिक पुस्तकें खोज में भाग लेंगी, जिससे भ्रमित करने वाले परिणाम मिल सकते हैं।",
"welcome-page-overzealous-filter": "कोई परिणाम नहीं। क्या आप <a href=\"{{URL}}\">फ़िल्टर रीसेट करना</a> चाहेंगे?",
"powered-by-kiwix-html": "<a href=\"https://kiwix.org\">किविक्स</a> द्वारा संचालित",
"search": "खोजें",
"book-filtering-all-categories": "सब वर्ग",
"book-filtering-all-languages": "सभी भाषाएँ",
"count-of-matching-books": "{{COUNT}} पुस्तक/पुस्तकें",
"download": "डाउनलोड",
"direct-download-link-text": "प्रत्यक्ष",
"direct-download-alt-text": "प्रत्यक्षत: डाउनलोड",
"hash-download-link-text": "शा256 हैश",
"hash-download-alt-text": "हैश डाउनलोड करें",
"magnet-link-text": "MAGNET लिंक",
"magnet-alt-text": "MAGNET डाउनलोड करें",
"torrent-download-link-text": "टोरेंट फ़ाइल",
"torrent-download-alt-text": "टोरेंट डाउनलोड करें",
"library-opds-feed-all-entries": "लाइब्रेरी ओपीडीएस फ़ीड - सभी प्रविष्टियाँ",
"filter-by-tag": "टैग \"{{TAG}}\" द्वारा फ़िल्टर करें",
"stop-filtering-by-tag": "\"{{TAG}}\" टैग द्वारा फ़िल्टर करना बंद करें",
"library-opds-feed-parameterised": "लाइब्रेरी ओपीडीएस फ़ीड - मिलान वाली प्रविष्टियाँ {{#LANG}}\nभाषा: {{LANG}} {/LANG}}{{#CATEGORY}}\nश्रेणी: {{CATEGORY}} {/CATEGORY}} {{#TAG}}\nटैग: {{TAG}} {{/TAG}}{{#Q}}\nक्वेरी: {{Q}} {{/Q}}",
"welcome-to-kiwix-server": "कीविक्स सर्वर में आपका स्वागत है",
"download-links-heading": "<b><i>{{BOOK_TITLE}}</i></b> के लिए डाउनलोड लिंक",
"download-links-title": "पुस्तक डाउनलोड करें",
"preview-book": "पूर्वावलोकन"
}

View File

@@ -23,6 +23,5 @@
"book-filtering-all-languages": "All Sproochen",
"count-of-matching-books": "{{COUNT}} Buch/Bicher",
"download": "Eroflueden",
"direct-download-link-text": "Direkt",
"download-links-title": "Buch eroflueden"
"direct-download-link-text": "Direkt"
}

View File

@@ -1,20 +0,0 @@
{
"@metadata": {
"authors": [
"Tofeiku"
]
},
"name": "Bahasa Melayu",
"404-page-heading": "Tidak Dijumpai",
"500-page-title": "Ralat Pelayan Dalaman",
"500-page-heading": "Ralat Pelayan Dalaman",
"library-button-text": "Pergi ke laman selamat datang",
"searchbox-tooltip": "Cari '{{BOOK_TITLE}}'",
"search": "Cari",
"book-filtering-all-categories": "Semua kategori",
"book-filtering-all-languages": "Semua bahasa",
"download": "Muat turun",
"direct-download-link-text": "Langsung",
"direct-download-alt-text": "muat turun langsung",
"download-links-title": "Muat turun buku"
}

View File

@@ -6,31 +6,10 @@
"Vistaus"
]
},
"name": "Nederlands",
"suggest-full-text-search": "bevat {{{SEARCH_TERMS}}}’…",
"no-such-book": "Boek bestaat niet: {{BOOK_NAME}}",
"too-many-books": "Er zijn teveel boeken opgevraagd ({{NB_BOOKS}}). De limiet is {{LIMIT}}.",
"too-many-books": "Er zijn teveel boeken opgevraagd ({{NB_BOOKS}}). Het limiet is {{LIMIT}}.",
"no-book-found": "Er zijn geen boeken die overeenkomen met de zoekcriteria",
"url-not-found": "De opgevraagde URL “{{url}}” is niet gevonden op deze server.",
"suggest-search": "In volledige tekst zoeen naar <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
"random-article-failure": "Oeps! Kan geen willekeurig artikel kiezen :(",
"invalid-raw-data-type": "{{DATATYPE}} is geen geldig verzoek voor onbewerkte inhoud.",
"no-value-for-arg": "Er is geen waarde opgegeven bij {{ARGUMENT}}",
"no-query": "Er is geen zoekterm opgegeven.",
"raw-entry-not-found": "Kan het {{DATATYPE}}-item {{ENTRY}} niet vinden",
"400-page-title": "Ongeldig verzoek",
"400-page-heading": "Ongeldig verzoek",
"404-page-title": "Inhoud niet gevonden",
"404-page-heading": "Niet gevonden",
"500-page-title": "Interne serverfout",
"500-page-heading": "Interne serverfout",
"fulltext-search-unavailable": "Zoeken in volledige tekst is niet beschikbaar",
"no-search-results": "De zoekmachine voor volledige tekst is niet beschikbaar voor deze inhoud.",
"library-button-text": "Naar de welkomstpagina",
"home-button-text": "Naar de hoofdpagina van {{BOOK_TITLE}}",
"random-page-button-text": "Naar een willekeurig geselecteerde pagina gaan",
"searchbox-tooltip": "Naar {{BOOK_TITLE}} zoeken",
"confusion-of-tongues": "Er zouden twee of meer boeken in verschillende talen deelnemen aan de zoekopdracht, wat tot verwarrende resultaten kan leiden.",
"welcome-page-overzealous-filter": "Geen resultaat. Wilt u <a href=\"{{URL}}\">het filter resetten</a>?",
"powered-by-kiwix-html": "Mogelijk gemaakt door <a href=\"https://kiwix.org\">Kiwix</a>",
"search": "Zoeken",
@@ -46,12 +25,6 @@
"magnet-alt-text": "magnet-link van de download",
"torrent-download-link-text": "Torrent-bestand",
"torrent-download-alt-text": "torrent downloaden",
"library-opds-feed-all-entries": "OPDS-feed bibliotheek: alle vermeldingen",
"filter-by-tag": "Filteren op label “{{TAG}}”",
"stop-filtering-by-tag": "Niet meer filteren op label “{{TAG}}”",
"library-opds-feed-parameterised": "OPDS-feed bibliotheek: vermeldingen die overeenkomen met {{#LANG}}\nTaal: {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategorie: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nLabel: {{TAG}} {{/TAG}}{{#Q}}\nZoekopdracht: {{Q}} {{/Q}}",
"welcome-to-kiwix-server": "Welkom bij de Kiwix-server",
"download-links-heading": "Downloadkoppelingen voor <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Boek downloaden",
"preview-book": "Voorvertoning"
"filter-by-tag": "Filteren op tag \"{{TAG}}\"",
"stop-filtering-by-tag": "Stoppen met filteren op tag \"{{TAG}}\""
}

View File

@@ -1,55 +0,0 @@
{
"@metadata": {
"authors": [
"Gouri"
]
},
"name": "ଓଡ଼ିଆ",
"suggest-full-text-search": "${{{SEARCH_TERMS}}} ଧାରଣ କରିଛି ...",
"no-such-book": "ଏପରି କୌଣସି ପୁସ୍ତକ ନାହିଁଃ ${{BOOK_NAME}}",
"too-many-books": "ଅତ୍ୟଧିକ ବହି ଅନୁରୋଧ (${{NB_BOOKS}}) ଯେଉଁଠାରେ ସୀମା ${{LIMIT}} |",
"no-book-found": "କୌଣସି ପୁସ୍ତକ ଚଯ଼ନ ମାନଦଣ୍ଡ ସହ ମେଳ ଖାଉନାହିଁ ।",
"url-not-found": "ଅନୁରୋଧ କରାଯାଇଥିବା URL \"{{url}}\" ଏହି ସର୍ଭରରେ ମିଳିଲା ନାହିଁ |",
"suggest-search": "<a href=\"${{{SEARCH_URL}}}\">${{PATTERN}} for</a> ପାଇଁ ଏକ ସମ୍ପୂର୍ଣ୍ଣ ପାଠ ସନ୍ଧାନ କର |",
"random-article-failure": "ଓହୋ! ଏକ ଅନିୟମିତ ପ୍ରବନ୍ଧ ବାଛିବାରେ ବିଫଳ :(",
"invalid-raw-data-type": "{{DATATYPE}} କଞ୍ଚା ବିଷୟବସ୍ତୁ ପାଇଁ ଏକ ବ valid ଧ ଅନୁରୋଧ ନୁହେଁ |",
"no-value-for-arg": "ଯୁକ୍ତି ପାଇଁ କୌଣସି ମୂଲ୍ଯ଼ ପ୍ରଦାନ କରାଯାଇନାହିଁ ${{ARGUMENT}}",
"no-query": "କୌଣସି ପ୍ରଶ୍ନ ପ୍ରଦାନ କରାଯାଇନାହିଁ ।",
"raw-entry-not-found": "${{DATATYPE}} ${{ENTRY}} କୁ ଖୋଜି ପାରୁନାହିଁ|",
"400-page-title": "ଅବୈଧ ଅନୁରୋଧ",
"400-page-heading": "ଅବୈଧ ଅନୁରୋଧ",
"404-page-title": "ବିଷଯ଼ବସ୍ତୁ ମିଳୁନାହିଁ",
"404-page-heading": "ମିଳିଲାନାହିଁ",
"500-page-title": "ଆନ୍ତରିକ ସର୍ଭର ତୃଟି",
"500-page-heading": "ଆନ୍ତରିକ ସର୍ଭର ତୃଟି",
"fulltext-search-unavailable": "ପୂର୍ଣ୍ଣ ପାଠ ସନ୍ଧାନ ଉପଲବ୍ଧ ନାହିଁ |",
"no-search-results": "ଏହି ବିଷୟବସ୍ତୁ ପାଇଁ ଫୁଲ୍ ଟେକ୍ସଟ୍ ସର୍ଚ୍ଚ ଇଞ୍ଜିନ୍ ଉପଲବ୍ଧ ନାହିଁ |",
"library-button-text": "ସ୍ୱାଗତ ପୃଷ୍ଠାକୁ ଯାଆନ୍ତୁ |",
"home-button-text": "'{{BOOK_TITLE}}' ର ମୁଖ୍ୟ ପୃଷ୍ଠାକୁ ଯାଆନ୍ତୁ |",
"random-page-button-text": "ଏକ ଅନିୟମିତ ଭାବରେ ମନୋନୀତ ପୃଷ୍ଠାକୁ ଯାଆନ୍ତୁ |",
"searchbox-tooltip": "'{{BOOK_TITLE}}' ସନ୍ଧାନ କରନ୍ତୁ |",
"confusion-of-tongues": "ବିଭିନ୍ନ ଭାଷାରେ ଦୁଇ କିମ୍ବା ଅଧିକ ପୁସ୍ତକ ସର୍ଚ୍ଚରେ ଅଂଶଗ୍ରହଣ କରିବେ ଯାହା ବିଭ୍ରାନ୍ତିକର ଫଳାଫଳ ଆଣିପାରେ |",
"welcome-page-overzealous-filter": "କୌଣସି ଫଳାଫଳ ନାହିଁ | ଆପଣ <a href=\"{{URL}}\">ଫିଲ୍ଟର ପୁନ res ସେଟ୍</a> କରିବାକୁ ଚାହୁଁଛନ୍ତି କି?",
"powered-by-kiwix-html": "<a href=\"https://kiwix.org\">କିୱିକ୍ସ</a> ଦ୍ୱାରା ଚାଳିତ |",
"search": "ଖୋଜନ୍ତୁ",
"book-filtering-all-categories": "ସମସ୍ତ ବର୍ଗ |",
"book-filtering-all-languages": "ସମସ୍ତ ଭାଷା",
"count-of-matching-books": "{{COUNT}} ପୁସ୍ତକ (ଗୁଡିକ)",
"download": "ଡାଉନଲୋଡ଼",
"direct-download-link-text": "ସିଧାସଳଖ |",
"direct-download-alt-text": "ସିଧାସଳଖ ଡାଉନଲୋଡ୍ |",
"hash-download-link-text": "Sha256 ହ୍ୟାସ୍ |",
"hash-download-alt-text": "ହ୍ୟାସ୍ ଡାଉନଲୋଡ୍ କରନ୍ତୁ |",
"magnet-link-text": "ଚୁମ୍ବକୀୟ ଲିଙ୍କ୍",
"magnet-alt-text": "ଚୁମ୍ବକ ଡାଉନଲୋଡ୍ କରନ୍ତୁ",
"torrent-download-link-text": "ଟୋରେଣ୍ଟ ଫାଇଲ୍ |",
"torrent-download-alt-text": "torrent ଡାଉନଲୋଡ୍ କରନ୍ତୁ",
"library-opds-feed-all-entries": "ଲାଇବ୍ରେରୀ OPDS ଫିଡ୍ - ସମସ୍ତ ଏଣ୍ଟ୍ରିଗୁଡିକ |",
"filter-by-tag": "ଟ୍ୟାଗ୍ ଦ୍ୱାରା ଫିଲ୍ଟର୍ \"{{TAG}}\"",
"stop-filtering-by-tag": "\"${{TAG}}\" ଟ୍ୟାଗ୍ ଦ୍ୱାରା ଫିଲ୍ଟର କରିବା ବନ୍ଦ କରନ୍ତୁ |",
"library-opds-feed-parameterised": "ଲାଇବ୍ରେରୀ OPDS ଫିଡ୍ - ଏଣ୍ଟ୍ରିଗୁଡିକ {{#LANG}}! N! ଭାଷା! ${{LANG}} ! {#TAG}} Category: ${{CATEGORY}} ଟ୍ୟାଗ୍: ${{TAG}} {{/ TAG}} {{# Q}}! ପ୍ରଶ୍ନ: {{Q}} {{/ Q}}",
"welcome-to-kiwix-server": "Kiwix ସର୍ଭରକୁ ସ୍ୱାଗତ |",
"download-links-heading": "<b><i>${{BOOK_TITLE}} for</i></b> ପାଇଁ ଲିଙ୍କ୍ ଡାଉନଲୋଡ୍ କରନ୍ତୁ |",
"download-links-title": "ବହି ଡାଉନଲୋଡ୍ କରନ୍ତୁ |",
"preview-book": "ପୂର୍ବରୂପ"
}

View File

@@ -4,9 +4,7 @@
"Fenixs-ru",
"Kareyac",
"Okras",
"Pacha Tchernof",
"Razno0",
"Smavrina"
"Pacha Tchernof"
]
},
"name": "русский",
@@ -34,23 +32,7 @@
"random-page-button-text": "Перейти на случайно выбранную страницу",
"searchbox-tooltip": "Искать '{{BOOK_TITLE}}'",
"confusion-of-tongues": "В поиске будут участвовать две или более книг на разных языках, что может привести к запутанным результатам.",
"powered-by-kiwix-html": "При поддержке&nbsp;<a href=\"https://kiwix.org\">Kiwix</a>",
"search": "Найти",
"book-filtering-all-categories": "Все категории",
"book-filtering-all-languages": "Все языки",
"count-of-matching-books": "{{COUNT}} книг(и)",
"download": "Скачать",
"direct-download-alt-text": "прямая загрузка",
"hash-download-link-text": "Хэш Sha256",
"hash-download-alt-text": "скачать хэш",
"magnet-link-text": "Магнитная ссылка",
"torrent-download-link-text": "Торрент-файл",
"torrent-download-alt-text": "скачать торрент",
"library-opds-feed-all-entries": "Канал библиотеки OPDS  все записи",
"filter-by-tag": "Фильтровать по тегу \"{{TAG}}\"",
"stop-filtering-by-tag": "Прекратить фильтрацию по тегу \"{{TAG}}\"",
"welcome-to-kiwix-server": "Добро пожаловать на сервер Kiwix",
"download-links-heading": "Ссылки для скачивания <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Скачать книгу",
"preview-book": "Предпросмотр"
"download": "Скачать"
}

View File

@@ -1,55 +0,0 @@
{
"@metadata": {
"authors": [
"Besnik b"
]
},
"name": "Shqip",
"suggest-full-text-search": "që përmban '{{{SEARCH_TERMS}}}'…",
"no-such-book": "Ska libër të tillë: {{BOOK_NAME}}",
"too-many-books": "U kërkuan shumë libra ({{NB_BOOKS}}), teksa kufiri është {{LIMIT}}",
"no-book-found": "Ska libër me përputhje me kriteret e përzgjedhjes",
"url-not-found": "URL “{{url}}” e kërkuar su gjet në këtë shërbyes.",
"suggest-search": "Bëni një kërkim të plotë teksti për <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
"random-article-failure": "Oh! Su arrit të merrej një artikull kuturu :(",
"invalid-raw-data-type": "{{DATATYPE}} sështë varg i vlefshëm kërkimi për lëndë të papërpunuar.",
"no-value-for-arg": "Su dha vlerë për argumentin {{ARGUMENT}}",
"no-query": "Su dha varg kërkimi.",
"raw-entry-not-found": "Sgjendet dot zëri {{DATATYPE}} {{ENTRY}}",
"400-page-title": "Kërkesë e pavlefshme",
"400-page-heading": "Kërkesë e pavlefshme",
"404-page-title": "Su gjet lëndë",
"404-page-heading": "Su Gjet",
"500-page-title": "Gabim i Brendshëm Shërbyesi",
"500-page-heading": "Gabim i Brendshëm Shërbyesi",
"fulltext-search-unavailable": "Kërkim teksti të plotë jo i mundshëm",
"no-search-results": "Sështë i passhëm motori i kërkimit të tekstit të plotë për këtë lëndë.",
"library-button-text": "Kalo te faqja e mirëseardhjes",
"home-button-text": "Kalo te faqja krye e '{{BOOK_TITLE}}'",
"random-page-button-text": "Kalo te një faqe e përzgjedhur kuturu",
"searchbox-tooltip": "Kërko në '{{BOOK_TITLE}}'",
"confusion-of-tongues": "Kërkimi do të merrej me dy ose më tepër libra në gjuhë të ndryshme, çka mund të sjellë përfundime të ngatërruara.",
"welcome-page-overzealous-filter": "Ska përfundime. Do të donit të <a href=\"{{URL}}\">riujdisni filtrimin</a>?",
"powered-by-kiwix-html": "Bazuar në&nbsp;<a href=\"https://kiwix.org\">Kiwix</a>",
"search": "Kërko",
"book-filtering-all-categories": "Krejt kategoritë",
"book-filtering-all-languages": "Krejt gjuhët",
"count-of-matching-books": "{{COUNT}} libër(a)",
"download": "Shkarkoje",
"direct-download-link-text": "Drejtpërsëdrejti",
"direct-download-alt-text": "shkarkim i drejtpërdrejt",
"hash-download-link-text": "Hash sha256",
"hash-download-alt-text": "shkarko hashin",
"magnet-link-text": "Lidhje Magnet",
"magnet-alt-text": "shkarko magnetin",
"torrent-download-link-text": "Kartelë Torrent",
"torrent-download-alt-text": "shkarko torrent-in",
"library-opds-feed-all-entries": "Prurje OPDS Biblioteke - Krejt zërat",
"filter-by-tag": "Filtroji sipas etiketës “{{TAG}}”",
"stop-filtering-by-tag": "Resht së filtruari sipas etiketë “{{TAG}}”",
"library-opds-feed-parameterised": "Prurje OPDS Biblioteke - zëra që kanë përputhje me {{#LANG}}\nGjuhë: {{LANG}} {{/LANG}}{{#CATEGORY}}\nKategori: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nEtiketë: {{TAG}} {{/TAG}}{{#Q}}\nVarg Kërkimi: {{Q}} {{/Q}}",
"welcome-to-kiwix-server": "Mirë se vini në Shërbyesin Kiwix",
"download-links-heading": "Lidhje shkarkimi për <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Shkarkoje librin",
"preview-book": "Paraparje"
}

View File

@@ -1,58 +0,0 @@
{
"@metadata": {
"authors": [
"Amire80",
"Chaduvari",
"Rishitha 1238",
"V Bhavya"
]
},
"name": "ఇంగ్లీషు",
"suggest-full-text-search": "'{{{SEARCH_TERMS}}}'ని కలిగి ఉంది...",
"no-such-book": "అలాంటి పుస్తకం లేదు: {{BOOK_NAME}}",
"too-many-books": "పరిమితి {{LIMIT}} ఉన్న చాలా పుస్తకాలు అభ్యర్థించబడ్డాయి ({{NB_BOOKS}})",
"no-book-found": "ఎంపిక ప్రమాణాలకు ఏ పుస్తకం సరిపోలలేదు",
"url-not-found": "అభ్యర్థించిన URL \"{{url}}\" ఈ సర్వర్‌లో కనుగొనబడలేదు.",
"suggest-search": "<a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a> కోసం పూర్తి వచన శోధన చేయండి",
"random-article-failure": "అయ్యో! యాదృచ్ఛిక కథనాన్ని ఎంచుకోవడంలో విఫలమైంది :(",
"invalid-raw-data-type": "ముడి కంటెంట్ కోసం {{DATATYPE}} చెల్లుబాటు అయ్యే అభ్యర్థన కాదు.",
"no-value-for-arg": "ఆర్గ్యుమెంట్ {{ARGUMENT}}కి విలువ అందించబడలేదు",
"no-query": "ప్రశ్న ఏదీ అందించబడలేదు.",
"raw-entry-not-found": "{{DATATYPE}} ఎంట్రీ {{ENTRY}} కనుగొనబడలేదు",
"400-page-title": "చెల్లని అభ్యర్థన",
"400-page-heading": "చెల్లని అభ్యర్థన",
"404-page-title": "కంటెంట్ కనుగొనబడలేదు",
"404-page-heading": "దొరకలేదు",
"500-page-title": "అంతర్గత సర్వర్ లోపం",
"500-page-heading": "అంతర్గత సర్వర్ లోపం",
"fulltext-search-unavailable": "పూర్తి వచన శోధన అందుబాటులో లేదు",
"no-search-results": "ఈ కంటెంట్ కోసం ఫుల్‌టెక్స్ట్ శోధన ఇంజిన్ అందుబాటులో లేదు.",
"library-button-text": "స్వాగత పేజీకి వెళ్లండి",
"home-button-text": "'{{BOOK_TITLE}}' యొక్క ప్రధాన పేజీకి వెళ్లండి",
"random-page-button-text": "యాదృచ్ఛికంగా ఎంచుకున్న పేజీకి వెళ్లండి",
"searchbox-tooltip": "'{{BOOK_TITLE}}'ని శోధించండి",
"confusion-of-tongues": "వివిధ భాషల్లోని రెండు లేదా అంతకంటే ఎక్కువ పుస్తకాలు శోధనలో పాల్గొంటాయి, ఇది గందరగోళ ఫలితాలకు దారితీయవచ్చు.",
"welcome-page-overzealous-filter": "ఫలితం లేదు. <a href=\"{{URL}}\">వడపోతను రీసెట్</a> చేస్తారా?",
"powered-by-kiwix-html": "<a href=\"https://kiwix.org\">Kiwix</a> ద్వారా ఆధారితం",
"search": "వెతుకు",
"book-filtering-all-categories": "వర్గాలన్నీ",
"book-filtering-all-languages": "అన్ని భాషలు",
"count-of-matching-books": "{{COUNT}} పుస్తకం(లు)",
"download": "డౌన్‌లోడ్",
"direct-download-link-text": "డైరెక్ట్",
"direct-download-alt-text": "నేరుగా డౌన్లోడ్",
"hash-download-link-text": "షా256 హాష్",
"hash-download-alt-text": "హాష్‌ని డౌన్‌లోడ్ చేయండి",
"magnet-link-text": "మాగ్నెట్ లింక్",
"magnet-alt-text": "అయస్కాంతాన్ని డౌన్‌లోడ్ చేయండి",
"torrent-download-link-text": "టోరెంట్ ఫైల్",
"torrent-download-alt-text": "టొరెంట్‌ని డౌన్‌లోడ్ చేయండి",
"library-opds-feed-all-entries": "లైబ్రరీ OPDS ఫీడ్ - అన్ని ఎంట్రీలు",
"filter-by-tag": "\"{{TAG}}\" ట్యాగ్ ద్వారా ఫిల్టర్ చేయండి",
"stop-filtering-by-tag": "\"{{TAG}}\" ట్యాగ్ ద్వారా ఫిల్టర్ చేయడాన్ని ఆపివేయి",
"library-opds-feed-parameterised": "లైబ్రరీ OPDS ఫీడ్ - ఎంట్రీలు సరిపోలే {{#LANG}}\nభాష: {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategory: {{CATEGORY}} {{/CATEGORY}} {{#TAG}}\nట్యాగ్: {{TAG}} {{/TAG}}{{#Q}}\nప్రశ్న: {{Q}} {{/Q}}",
"welcome-to-kiwix-server": "Kiwix సర్వర్‌కి స్వాగతం",
"download-links-heading": "<b><i>{{BOOK_TITLE}}</i></b> కోసం లింక్‌లను డౌన్‌లోడ్ చేయండి",
"download-links-title": "పుస్తకాన్ని డౌన్‌లోడ్ చేయండి",
"preview-book": "ప్రివ్యూ"
}

View File

@@ -2,9 +2,7 @@
"@metadata": {
"authors": [
"GuoPC",
"StarrySky",
"Sunai",
"XtexChooser"
"StarrySky"
]
},
"name": "英语",
@@ -14,12 +12,5 @@
"404-page-heading": "未找到",
"500-page-title": "内部服务器错误",
"500-page-heading": "内部服务器错误",
"library-button-text": "前往欢迎页面",
"search": "搜索",
"book-filtering-all-categories": "所有分类",
"book-filtering-all-languages": "所有语言",
"download": "下载",
"magnet-link-text": "磁力链接",
"torrent-download-link-text": "种子文件",
"preview-book": "预览"
"library-button-text": "前往欢迎页面"
}

View File

@@ -1,7 +1,29 @@
*,
*::after,
*::before {
margin: 0;
padding: 0;
box-sizing: inherit;
}
html {
font-size: 62.5%;
}
body {
position: relative;
box-sizing: border-box;
}
::selection {
background-color: #00b4e4;
color: white;
}
.kiwixNav {
background-color: #f4f6f8;
width: 100%;
padding: 10px 20px;
padding: 20px;
position: sticky;
top: 0;
z-index: 3;
@@ -37,10 +59,10 @@
background-image: none;
border-radius: 1px;
width: 195px;
height: 30px;
height: 35px;
flex: 1;
color: black;
padding: 0px 10px 0px 10px;
padding: 7px 10px 10px;
cursor: pointer;
}
@@ -52,7 +74,7 @@
position: relative;
display: flex;
width: 231px;
height: 30px;
height: 35px;
line-height: 3;
background: #909090;
overflow: hidden;
@@ -82,7 +104,7 @@
}
.kiwixSearch {
height: 30px;
height: 35px;
width: 231px;
border-radius: 10px;
border: solid 1px #b5b2b2;
@@ -96,7 +118,7 @@
.kiwixButton {
margin: 0 17px;
height: 30px;
height: 35px;
width: 100px;
border-radius: 10px;
color: white;
@@ -140,6 +162,29 @@
font-weight: bolder;
}
#uiLanguageSelector {
display: none;
}
#uiLanguageSelector .modal {
height: 140px;
}
#uiLanguageSelector .modal-heading {
height: 40%;
}
#uiLanguageSelector .modal-content #ui_language {
font-size: 1.6rem;
width: 100%;
}
#uiLanguageSelectorButton {
margin: 0 12px 0 0;
float: right;
cursor: pointer;
}
.book__list {
position: relative;
margin: 0 auto;
@@ -280,6 +325,58 @@
-webkit-box-orient: vertical;
}
.modal-wrapper {
position: fixed;
z-index: 100;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
align-content: center;
background-color: rgba(0, 0, 0, 30%);
}
.modal {
color: #444343;
height: 280px;
width: 250px;
margin: 15px;
background-color: #f7f7f7;
border: 1px solid #ececec;
border-radius: 3px;
}
.modal-heading {
background-color: #f0f0f0;
height: 20%;
width: 100%;
border-bottom: 1px solid #ececec;
display: grid;
grid-template-columns: 3fr 1fr;
}
.modal-title {
display: flex;
font-size: 15px;
align-items: center;
padding-left: 20px;
font-family: poppins;
}
.modal-close-button {
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
padding: 20px;
}
.modal-content div {
width: 100%;
height: 40px;
@@ -372,7 +469,7 @@
width: auto;
}
.feedLogo {
.feedLogo, #uiLanguageSelectorButton {
height: 30px;
float: right;
}

View File

@@ -46,7 +46,7 @@
window.modalUILanguageSelector.close();
const s = document.getElementById("ui_language");
const lang = s.options[s.selectedIndex].value;
localStorage.setItem('userlang', lang);
setPermanentGlobalCookie('userlang', lang);
window.location.reload();
}
@@ -197,58 +197,6 @@
}
}
function makeURLSearchString(params, keysToURIEncode) {
let output = '';
for (const [key, value] of params.entries()) {
let finalValue = (keysToURIEncode.indexOf(key) >= 0) ? encodeURIComponent(value) : value;
output += `&${key}=${finalValue}`;
}
return output;
}
/* hack for library.kiwix.org magnet links (created by MirrorBrain)
See https://github.com/kiwix/container-images/issues/242 */
async function getFixedMirrorbrainMagnet(magnetLink) {
// parse as query parameters
const params = new URLSearchParams(
magnetLink.replaceAll('&amp;', '&').replace(/^magnet:/, ''));
const zimUrl = params.get('as'); // as= is fallback URL
// download metalink to build list of mirrored URLs
let mirrorUrls = [];
const metalink = await fetch(`${zimUrl}.meta4`).then(response => {
return response.ok ? response.text() : '';
}).catch((_error) => '');
if (metalink) {
try {
const parser = new DOMParser();
const doc = parser.parseFromString(metalink, "application/xml");
doc.querySelectorAll("url").forEach((node) => {
if (node.hasAttribute("priority")) { // ensures its a mirror link
mirrorUrls.push(node.innerHTML);
}
});
} catch (err) {
// not a big deal, magnet will only contain primary URL
console.debug(`Failed to parse mirror links for ${zimUrl}`);
}
}
// set webseed (ws=) URL to primary download URL (redirects to mirror)
params.set('ws', zimUrl);
// if we got metalink mirror URLs, append them all
if (mirrorUrls) {
mirrorUrls.forEach((url) => {
params.append('ws', url);
});
}
params.set('xs', `${zimUrl}.torrent`); // adding xs= to point to torrent URL
return 'magnet:?' + makeURLSearchString(params, ['ws', 'as', 'dn', 'xs', 'tr']);
}
async function getMagnetLink(downloadLink) {
const magnetUrl = downloadLink + '.magnet';
const controller = new AbortController();
@@ -256,9 +204,6 @@
const magnetLink = await fetch(magnetUrl, { signal: controller.signal }).then(response => {
return response.ok ? response.text() : '';
}).catch((_error) => '');
if (magnetLink) {
return await getFixedMirrorbrainMagnet(magnetLink);
}
return magnetLink;
}
@@ -640,6 +585,11 @@
setInterval(updateNavVisibilityState, 250);
};
// required by i18n.js:setUserLanguage()
window.setPermanentGlobalCookie = function(name, value) {
document.cookie = `${name}=${value};path=${root};max-age=31536000`;
}
window.onload = () => { setUserLanguage(getUserLanguage(), onload); }
})();

View File

@@ -1,107 +0,0 @@
*,
*::after,
*::before {
margin: 0;
padding: 0;
box-sizing: inherit;
}
html {
font-size: 62.5%;
}
body {
position: relative;
box-sizing: border-box;
}
::selection {
background-color: #00b4e4;
color: white;
}
.modal-wrapper {
position: fixed;
z-index: 100;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
align-content: center;
background-color: rgba(0, 0, 0, 30%);
}
.modal {
color: #444343;
height: 280px;
width: 250px;
margin: 15px;
background-color: #f7f7f7;
border: 1px solid #ececec;
border-radius: 3px;
}
.modal-heading {
background-color: #f0f0f0;
height: 20%;
width: 100%;
border-bottom: 1px solid #ececec;
display: grid;
grid-template-columns: 3fr 1fr;
}
.modal-title {
display: flex;
font-size: 15px;
align-items: center;
padding-left: 20px;
font-family: poppins;
}
.modal-close-button {
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
padding: 20px;
}
#uiLanguageSelector {
display: none;
}
#uiLanguageSelector .modal {
height: 140px;
}
#uiLanguageSelector .modal-heading {
height: 40%;
}
#uiLanguageSelector .modal-content #ui_language {
font-size: 1.6rem;
width: 100%;
}
#uiLanguageSelectorButton {
margin: 0px 12px;
float: right;
cursor: pointer;
height: 30px;
}
@font-face {
font-family: "poppins";
src: url("../skin/fonts/Poppins.ttf?KIWIXCACHEID") format("truetype");
}
@font-face {
font-family: "roboto";
src: url("../skin/fonts/Roboto.ttf?KIWIXCACHEID") format("truetype");
}

View File

@@ -14,21 +14,15 @@ const uiLanguages = [
{
"English": "en"
},
{
"español": "es"
},
{
"suomi": "fi"
},
{
"Français": "fr"
"français": "fr"
},
{
"עברית": "he"
},
{
"हिन्दी": "hi"
},
{
"Հայերեն": "hy"
},
@@ -53,18 +47,9 @@ const uiLanguages = [
{
"македонски": "mk"
},
{
"Bahasa Melayu": "ms"
},
{
"Nederlands": "nl"
},
{
"ߒߞߏ": "nqo"
},
{
"ଓଡ଼ିଆ": "or"
},
{
"Polski": "pl"
},
@@ -77,21 +62,12 @@ const uiLanguages = [
{
"slovenčina": "sk"
},
{
"سرائیکی": "skr-arab"
},
{
"slovenščina": "sl"
},
{
"Shqip": "sq"
},
{
"Svenska": "sv"
},
{
"ఇంగ్లీషు": "te"
},
{
"Türkçe": "tr"
},

View File

@@ -3,9 +3,8 @@
transition: 0.3s;
width: 100%;
box-sizing: border-box;
background: #f4f6f8;
background: #e3e3e3;
border-bottom: 1px solid #aaa;
display: flex;
}
#kiwixtoolbar>a {
@@ -22,9 +21,8 @@
}
.kiwixsearch {
font-size: 1.6rem;
position: relative;
height: 30px;
height: 26px;
width: 100%;
left: 0;
margin-bottom: 0;
@@ -43,7 +41,6 @@
.kiwix .kiwix_centered {
max-width: 720px;
width: 100%;
margin: 0 auto;
}
@@ -51,8 +48,9 @@
display: none;
}
#kiwix_button_show_toggle:checked~label~.kiwix_button_cont,
#kiwix_button_show_toggle:checked~label~.kiwix_button_cont>a {
display: inline-block;
display: block;
}
#kiwix_button_show_toggle:not(:checked)~label~.kiwix_button_cont {
@@ -61,12 +59,12 @@
label[for="kiwix_button_show_toggle"] {
display: inline-block;
height: 30px;
height: 26px;
}
label[for="kiwix_button_show_toggle"] img {
transition: 0.1s;
height: 30px;
height: 26px;
}
#kiwix_button_show_toggle:checked~label img {
@@ -85,11 +83,11 @@ label[for="kiwix_button_show_toggle"],
.kiwix #kiwixtoolbar button,
.kiwix #kiwixtoolbar input[type="submit"] {
box-sizing: border-box !important;
height: 30px !important;
height: 26px !important;
line-height: 20px !important;
margin-right: 5px !important;
padding: 2px 6px !important;
border: 1px solid #b5b2b2 !important;
border: 1px solid #999 !important;
border-radius: 3px !important;
background-color: #ededed !important;
font-weight: normal !important;
@@ -101,9 +99,9 @@ label[for="kiwix_button_show_toggle"],
left: 0;
box-sizing: border-box !important;
width: 100%;
height: 30px !important;
height: 26px !important;
line-height: 20px !important;
border: 1px solid #b5b2b2 !important;
border: 1px solid #999 !important;
border-radius: 3px !important;
background-color: #fff !important;
padding: 2px 2px 2px 27px !important;
@@ -116,12 +114,11 @@ label[for=kiwixsearchbox] {
height: 100%;
left: 5px;
font-size: 90%;
line-height: 30px;
line-height: 26px;
vertical-align: middle;
}
a.suggest, a.suggest:visited, a.suggest:hover, a.suggest:active {
font-size: 1.6rem;
display: block;
text-decoration: none;
color: inherit;
@@ -132,6 +129,81 @@ a.suggest, a.suggest:visited, a.suggest:hover, a.suggest:active {
column-count: 1 !important;
}
.modal-wrapper {
position: fixed;
z-index: 100;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
align-content: center;
background-color: rgba(0, 0, 0, 30%);
}
.modal {
color: #444343;
height: 280px;
width: 250px;
margin: 15px;
background-color: #f7f7f7;
border: 1px solid #ececec;
border-radius: 3px;
}
.modal-heading {
background-color: #f0f0f0;
height: 20%;
width: 100%;
border-bottom: 1px solid #ececec;
display: grid;
grid-template-columns: 3fr 1fr;
}
.modal-title {
display: flex;
font-size: 15px;
align-items: center;
padding-left: 20px;
font-family: poppins;
}
.modal-close-button {
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
padding: 20px;
}
#uiLanguageSelector {
display: none;
}
#uiLanguageSelector .modal {
height: 140px;
}
#uiLanguageSelector .modal-heading {
height: 40%;
}
#uiLanguageSelector .modal-content #ui_language {
width: 100%;
}
#uiLanguageSelectorButton {
margin: 0px 12px 6px 12px;
float: right;
cursor: pointer;
height: 30px;
}
@media(min-width:420px) {
.kiwix_button_cont {
display: inline-block !important;
@@ -178,12 +250,8 @@ a.suggest, a.suggest:visited, a.suggest:hover, a.suggest:active {
}
}
@media(max-width:419px) {
@media(max-width:415px) {
.kiwix_searchform {
width: 80%;
}
.kiwix_button_cont {
padding-top: 5px;
}
}

View File

@@ -549,3 +549,7 @@ function finishViewerSetupOnceTranslationsAreLoaded()
viewerSetupComplete = true;
}
function setPermanentGlobalCookie(name, value) {
document.cookie = `${name}=${value};path=${root};max-age=31536000`;
}

View File

@@ -5,11 +5,6 @@
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link type="root" href="{{root}}">
<title>Welcome to Kiwix Server</title>
<link
type="text/css"
href="{{root}}/skin/kiwix.css?KIWIXCACHEID"
rel="Stylesheet"
/>
<link
type="text/css"
href="{{root}}/skin/index.css?KIWIXCACHEID"
@@ -31,7 +26,17 @@
<meta name="msapplication-TileColor" content="#da532c">
<meta name="msapplication-config" content="{{root}}/skin/favicon/browserconfig.xml?KIWIXCACHEID">
<meta name="theme-color" content="#ffffff">
<script type="text/javascript" src="./viewer_settings.js"></script>
<style>
@font-face {
font-family: "poppins";
src: url("{{root}}/skin/fonts/Poppins.ttf?KIWIXCACHEID") format("truetype");
}
@font-face {
font-family: "roboto";
src: url("{{root}}/skin/fonts/Roboto.ttf?KIWIXCACHEID") format("truetype");
}
</style>
<script type="module" src="{{root}}/skin/i18n.js?KIWIXCACHEID" defer></script>
<script type="text/javascript" src="{{root}}/skin/languages.js?KIWIXCACHEID" defer></script>
<script src="{{root}}/skin/isotope.pkgd.min.js?KIWIXCACHEID" defer></script>

View File

@@ -5,11 +5,6 @@
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link type="root" href="{{root}}">
<title>{{translations.welcome-to-kiwix-server}}</title>
<link
type="text/css"
href="{{root}}/skin/kiwix.css?KIWIXCACHEID"
rel="Stylesheet"
/>
<link
type="text/css"
href="{{root}}/skin/index.css?KIWIXCACHEID"
@@ -142,4 +137,4 @@
</div>
<div id="kiwixfooter" class="kiwixfooter">{{{translations.powered-by-kiwix-html}}}</div>
</body>
</html>
</html>

View File

@@ -1,6 +1,5 @@
const viewerSettings = {
toolbarEnabled: {{enable_toolbar}},
linkBlockingEnabled: {{enable_link_blocking}},
libraryButtonEnabled: {{enable_library_button}},
defaultUserLanguage: "{{default_user_language}}"
libraryButtonEnabled: {{enable_library_button}}
}

View File

@@ -8,14 +8,13 @@
object-src 'none';">
<title>ZIM Viewer</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link type="text/css" href="./skin/kiwix.css?KIWIXCACHEID" rel="Stylesheet" />
<link type="text/css" href="./skin/taskbar.css?KIWIXCACHEID" rel="Stylesheet" />
<link type="text/css" href="./skin/autoComplete/css/autoComplete.css?KIWIXCACHEID" rel="Stylesheet" />
<link type="text/css" href="./skin/css/autoComplete.css?KIWIXCACHEID" rel="Stylesheet" />
<script type="text/javascript" src="./viewer_settings.js"></script>
<script type="module" src="./skin/i18n.js?KIWIXCACHEID" defer></script>
<script type="text/javascript" src="./skin/languages.js?KIWIXCACHEID" defer></script>
<script type="text/javascript" src="./skin/viewer.js?KIWIXCACHEID" defer></script>
<script type="text/javascript" src="./skin/autoComplete/autoComplete.min.js?KIWIXCACHEID"></script>
<script type="text/javascript" src="./skin/autoComplete.min.js?KIWIXCACHEID"></script>
<script>
function getRootLocation() {
const p = location.pathname;
@@ -35,6 +34,13 @@
<div class="kiwix" style="display:none" id="kiwixtoolbarwrapper">
<div id="kiwixtoolbar" class="ui-widget-header">
<div class="kiwix_centered">
<a id="uiLanguageSelectorButton"
onclick="window.modalUILanguageSelector.show()"
alt="Select UI language"
aria-label="Select UI language"
title="Select UI language">
<img src="./skin/langSelector.svg?KIWIXCACHEID">
</a>
<div class="kiwix_searchform">
<form class="kiwixsearch" method="GET" action="javascript:performSearch()" id="kiwixsearchform">
<label for="kiwixsearchbox">&#x1f50d;</label>
@@ -59,13 +65,6 @@
</span>
</div>
</div>
<a onclick="window.modalUILanguageSelector.show()"
alt="Select UI language"
aria-label="Select UI language"
title="Select UI language">
<img id="uiLanguageSelectorButton"
src="./skin/langSelector.svg?KIWIXCACHEID">
</a>
</div>
</div>

View File

@@ -1,41 +0,0 @@
/*
* Copyright (C) 2023 Nikhil Tanwar (2002nikhiltanwar@gmail.com)
*
* 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 "../include/tools.h"
namespace
{
TEST(LanguageToolsTest, englishTest)
{
EXPECT_EQ(kiwix::getLanguageSelfName("eng"), "English");
}
TEST(LanguageToolsTest, manualValuesTest)
{
EXPECT_EQ(kiwix::getLanguageSelfName("dty"), "डोटेली");
}
TEST(LanguageToolsTest, emptyStringTest)
{
EXPECT_EQ(kiwix::getLanguageSelfName(""), "Undetermined");
}
}

View File

@@ -218,7 +218,7 @@ const char sampleLibraryXML[] = R"(
creator="Wikibooks"
publisher="Kiwix & Some Enthusiasts"
date="2021-04-11"
name="wikibooks.de"
name="wikibooks_de"
tags="unittest;wikibooks;_category:wikibooks"
articleCount="12"
mediaCount="0"
@@ -238,14 +238,14 @@ typedef std::vector<std::string> Langs;
TEST(LibraryOpdsImportTest, allInOne)
{
auto lib = kiwix::Library::create();
kiwix::Manager manager(lib);
kiwix::Library lib;
kiwix::Manager manager(&lib);
manager.readOpds(sampleOpdsStream, "library-opds-import.unittests.dev");
EXPECT_EQ(10U, lib->getBookCount(true, true));
EXPECT_EQ(10U, lib.getBookCount(true, true));
{
const kiwix::Book& book1 = lib->getBookById("0c45160e-f917-760a-9159-dfe3c53cdcdd");
const kiwix::Book& book1 = lib.getBookById("0c45160e-f917-760a-9159-dfe3c53cdcdd");
EXPECT_EQ(book1.getTitle(), "Encyclopédie de la Tunisie");
EXPECT_EQ(book1.getName(), "wikipedia_fr_tunisie_novid_2018-10");
@@ -271,7 +271,7 @@ TEST(LibraryOpdsImportTest, allInOne)
}
{
const kiwix::Book& book2 = lib->getBookById("0189d9be-2fd0-b4b6-7300-20fab0b5cdc8");
const kiwix::Book& book2 = lib.getBookById("0189d9be-2fd0-b4b6-7300-20fab0b5cdc8");
EXPECT_EQ(book2.getTitle(), "TED talks - Business");
EXPECT_EQ(book2.getName(), "");
EXPECT_EQ(book2.getFlavour(), "");
@@ -301,10 +301,8 @@ class LibraryTest : public ::testing::Test {
typedef kiwix::Library::BookIdCollection BookIdCollection;
typedef std::vector<std::string> TitleCollection;
LibraryTest(): lib(kiwix::Library::create()) {}
void SetUp() override {
kiwix::Manager manager(lib);
kiwix::Manager manager(&lib);
manager.readOpds(sampleOpdsStream, "foo.urlHost");
manager.readXml(sampleLibraryXML, false, "./test/library.xml", true);
}
@@ -318,25 +316,25 @@ class LibraryTest : public ::testing::Test {
TitleCollection ids2Titles(const BookIdCollection& ids) {
TitleCollection titles;
for ( const auto& bookId : ids ) {
titles.push_back(lib->getBookById(bookId).getTitle());
titles.push_back(lib.getBookById(bookId).getTitle());
}
std::sort(titles.begin(), titles.end());
return titles;
}
std::shared_ptr<kiwix::Library> lib;
kiwix::Library lib;
};
TEST_F(LibraryTest, getBookMarksTest)
{
auto bookId1 = lib->getBooksIds()[0];
auto bookId2 = lib->getBooksIds()[1];
auto bookId1 = lib.getBooksIds()[0];
auto bookId2 = lib.getBooksIds()[1];
lib->addBookmark(createBookmark(bookId1));
lib->addBookmark(createBookmark("invalid-bookmark-id"));
lib->addBookmark(createBookmark(bookId2));
auto onlyValidBookmarks = lib->getBookmarks();
auto allBookmarks = lib->getBookmarks(false);
lib.addBookmark(createBookmark(bookId1));
lib.addBookmark(createBookmark("invalid-bookmark-id"));
lib.addBookmark(createBookmark(bookId2));
auto onlyValidBookmarks = lib.getBookmarks();
auto allBookmarks = lib.getBookmarks(false);
EXPECT_EQ(onlyValidBookmarks[0].getBookId(), bookId1);
EXPECT_EQ(onlyValidBookmarks[1].getBookId(), bookId2);
@@ -348,11 +346,11 @@ TEST_F(LibraryTest, getBookMarksTest)
TEST_F(LibraryTest, sanityCheck)
{
EXPECT_EQ(lib->getBookCount(true, true), 12U);
EXPECT_EQ(lib->getBooksLanguages(),
EXPECT_EQ(lib.getBookCount(true, true), 12U);
EXPECT_EQ(lib.getBooksLanguages(),
std::vector<std::string>({"deu", "eng", "fra", "ita", "spa"})
);
EXPECT_EQ(lib->getBooksCreators(), std::vector<std::string>({
EXPECT_EQ(lib.getBooksCreators(), std::vector<std::string>({
"Islam Stack Exchange",
"Movies & TV Stack Exchange",
"Mythology & Folklore Stack Exchange",
@@ -363,7 +361,7 @@ TEST_F(LibraryTest, sanityCheck)
"Wikipedia",
"Wikiquote"
}));
EXPECT_EQ(lib->getBooksPublishers(), std::vector<std::string>({
EXPECT_EQ(lib.getBooksPublishers(), std::vector<std::string>({
"",
"Kiwix",
"Kiwix & Some Enthusiasts",
@@ -373,22 +371,22 @@ TEST_F(LibraryTest, sanityCheck)
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());
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, emptyFilter)
{
const auto bookIds = lib->filter(kiwix::Filter());
EXPECT_EQ(bookIds, lib->getBooksIds());
const auto bookIds = lib.filter(kiwix::Filter());
EXPECT_EQ(bookIds, lib.getBooksIds());
}
#define EXPECT_FILTER_RESULTS(f, ...) \
EXPECT_EQ( \
ids2Titles(lib->filter(f)), \
ids2Titles(lib.filter(f)), \
TitleCollection({ __VA_ARGS__ }) \
)
@@ -680,38 +678,17 @@ TEST_F(LibraryTest, filterByPublisher)
TEST_F(LibraryTest, filterByName)
{
EXPECT_FILTER_RESULTS(kiwix::Filter().name("wikibooks.de"),
EXPECT_FILTER_RESULTS(kiwix::Filter().name("wikibooks_de"),
"An example ZIM archive"
);
// Parsing the query with `name:` prefix splits the token on the dot, as if it was 2 sentences.
// It creates a query "XNwikibook@1 PHRASE 2 XNde@2".
// I haven't found the syntax to not split on dot.
EXPECT_FILTER_RESULTS(kiwix::Filter().query("name:wikibooks.de"),
/* no results */
);
EXPECT_FILTER_RESULTS(kiwix::Filter().name("wikibooks"),
/* no results */
);
// Wikibooks is in `tags` so it matches.
EXPECT_FILTER_RESULTS(kiwix::Filter().query("wikibooks"),
EXPECT_FILTER_RESULTS(kiwix::Filter().query("name:wikibooks_de"),
"An example ZIM archive"
);
// But "wikibooks.de" is only in name and `query` doesn't looks in name.
EXPECT_FILTER_RESULTS(kiwix::Filter().query("wikibooks.de"),
EXPECT_FILTER_RESULTS(kiwix::Filter().query("wikibooks_de"),
/* no results */
);
EXPECT_FILTER_RESULTS(kiwix::Filter().name("wikipedia_en_ray_charles"),
"Ray Charles"
);
EXPECT_FILTER_RESULTS(kiwix::Filter().query("name:wikipedia_en_ray_charles"),
"Ray Charles"
);
}
TEST_F(LibraryTest, filterByCategory)
@@ -759,33 +736,33 @@ TEST_F(LibraryTest, filterByMultipleCriteria)
TEST_F(LibraryTest, getBookByPath)
{
kiwix::Book book = lib->getBookById(lib->getBooksIds()[0]);
kiwix::Book book = lib.getBookById(lib.getBooksIds()[0]);
#ifdef _WIN32
auto path = "C:\\some\\abs\\path.zim";
#else
auto path = "/some/abs/path.zim";
#endif
book.setPath(path);
lib->addBook(book);
EXPECT_EQ(lib->getBookByPath(path).getId(), book.getId());
EXPECT_THROW(lib->getBookByPath("non/existant/path.zim"), std::out_of_range);
lib.addBook(book);
EXPECT_EQ(lib.getBookByPath(path).getId(), book.getId());
EXPECT_THROW(lib.getBookByPath("non/existant/path.zim"), std::out_of_range);
}
TEST_F(LibraryTest, removeBookByIdRemovesTheBook)
{
const auto initialBookCount = lib->getBookCount(true, true);
const auto initialBookCount = lib.getBookCount(true, true);
ASSERT_GT(initialBookCount, 0U);
EXPECT_NO_THROW(lib->getBookById("raycharles"));
lib->removeBookById("raycharles");
EXPECT_EQ(initialBookCount - 1, lib->getBookCount(true, true));
EXPECT_THROW(lib->getBookById("raycharles"), std::out_of_range);
EXPECT_NO_THROW(lib.getBookById("raycharles"));
lib.removeBookById("raycharles");
EXPECT_EQ(initialBookCount - 1, lib.getBookCount(true, true));
EXPECT_THROW(lib.getBookById("raycharles"), std::out_of_range);
};
TEST_F(LibraryTest, removeBookByIdDropsTheReader)
{
EXPECT_NE(nullptr, lib->getArchiveById("raycharles"));
lib->removeBookById("raycharles");
EXPECT_THROW(lib->getArchiveById("raycharles"), std::out_of_range);
EXPECT_NE(nullptr, lib.getArchiveById("raycharles"));
lib.removeBookById("raycharles");
EXPECT_THROW(lib.getArchiveById("raycharles"), std::out_of_range);
};
TEST_F(LibraryTest, removeBookByIdUpdatesTheSearchDB)
@@ -793,17 +770,17 @@ TEST_F(LibraryTest, removeBookByIdUpdatesTheSearchDB)
kiwix::Filter f;
f.local(true).valid(true).query(R"(title:"ray charles")", false);
EXPECT_NO_THROW(lib->getBookById("raycharles"));
EXPECT_EQ(1U, lib->filter(f).size());
EXPECT_NO_THROW(lib.getBookById("raycharles"));
EXPECT_EQ(1U, lib.filter(f).size());
lib->removeBookById("raycharles");
lib.removeBookById("raycharles");
EXPECT_THROW(lib->getBookById("raycharles"), std::out_of_range);
EXPECT_EQ(0U, lib->filter(f).size());
EXPECT_THROW(lib.getBookById("raycharles"), std::out_of_range);
EXPECT_EQ(0U, lib.filter(f).size());
// make sure that Library::filter() doesn't add an empty book with
// an id surviving in the search DB
EXPECT_THROW(lib->getBookById("raycharles"), std::out_of_range);
EXPECT_THROW(lib.getBookById("raycharles"), std::out_of_range);
};
TEST_F(LibraryTest, removeBooksNotUpdatedSince)
@@ -823,18 +800,18 @@ TEST_F(LibraryTest, removeBooksNotUpdatedSince)
"Wikiquote"
);
const uint64_t rev = lib->getRevision();
for ( const auto& id : lib->filter(kiwix::Filter().query("exchange")) ) {
lib->addBook(lib->getBookByIdThreadSafe(id));
const uint64_t rev = lib.getRevision();
for ( const auto& id : lib.filter(kiwix::Filter().query("exchange")) ) {
lib.addBook(lib.getBookByIdThreadSafe(id));
}
EXPECT_GT(lib->getRevision(), rev);
EXPECT_GT(lib.getRevision(), rev);
const uint64_t rev2 = lib->getRevision();
const uint64_t rev2 = lib.getRevision();
EXPECT_EQ(9u, lib->removeBooksNotUpdatedSince(rev));
EXPECT_EQ(9u, lib.removeBooksNotUpdatedSince(rev));
EXPECT_GT(lib->getRevision(), rev2);
EXPECT_GT(lib.getRevision(), rev2);
EXPECT_FILTER_RESULTS(kiwix::Filter(),
"Islam Stack Exchange",

View File

@@ -301,41 +301,20 @@ TEST_F(LibraryServerTest, catalog_search_by_tag)
TEST_F(LibraryServerTest, catalog_search_by_category)
{
{
const auto r = zfs1_->GET("/ROOT%23%3F/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>Filtered zims (category=jazz)</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"
);
}
{
const auto r = zfs1_->GET("/ROOT%23%3F/catalog/search?category=jazz,wikipedia");
EXPECT_EQ(r->status, 200);
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
OPDS_FEED_TAG
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
" <title>Filtered zims (category=jazz%2Cwikipedia)</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
CHARLES_RAY_CATALOG_ENTRY
"</feed>\n"
);
}
const auto r = zfs1_->GET("/ROOT%23%3F/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>Filtered zims (category=jazz)</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_language)
@@ -814,40 +793,6 @@ TEST_F(LibraryServerTest, catalog_v2_entries_filtered_by_language)
}
}
TEST_F(LibraryServerTest, catalog_v2_entries_filtered_by_category)
{
{
const auto r = zfs1_->GET("/ROOT%23%3F/catalog/v2/entries?category=jazz");
EXPECT_EQ(r->status, 200);
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
CATALOG_V2_ENTRIES_PREAMBLE("?category=jazz")
" <title>Filtered Entries (category=jazz)</title>\n"
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
" <totalResults>1</totalResults>\n"
" <startIndex>0</startIndex>\n"
" <itemsPerPage>1</itemsPerPage>\n"
CHARLES_RAY_CATALOG_ENTRY
"</feed>\n"
);
}
{
const auto r = zfs1_->GET("/ROOT%23%3F/catalog/v2/entries?category=jazz,wikipedia");
EXPECT_EQ(r->status, 200);
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
CATALOG_V2_ENTRIES_PREAMBLE("?category=jazz%2Cwikipedia")
" <title>Filtered Entries (category=jazz%2Cwikipedia)</title>\n"
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
" <totalResults>2</totalResults>\n"
" <startIndex>0</startIndex>\n"
" <itemsPerPage>2</itemsPerPage>\n"
RAY_CHARLES_CATALOG_ENTRY
CHARLES_RAY_CATALOG_ENTRY
"</feed>\n"
);
}
}
TEST_F(LibraryServerTest, catalog_v2_entries_multiple_filters)
{
{
@@ -1028,12 +973,7 @@ TEST_F(LibraryServerTest, no_name_mapper_catalog_v2_individual_entry_access)
" <title>Welcome to Kiwix Server</title>\n" \
" <link\n" \
" type=\"text/css\"\n" \
" href=\"/ROOT%23%3F/skin/kiwix.css?cacheid=2158fad9\"\n" \
" rel=\"Stylesheet\"\n" \
" />\n" \
" <link\n" \
" type=\"text/css\"\n" \
" href=\"/ROOT%23%3F/skin/index.css?cacheid=1e78e7cf\"\n" \
" href=\"/ROOT%23%3F/skin/index.css?cacheid=e4d76d16\"\n" \
" rel=\"Stylesheet\"\n" \
" />\n" \
" <link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"/ROOT%23%3F/skin/favicon/apple-touch-icon.png?cacheid=f86f8df3\">\n" \
@@ -1150,7 +1090,7 @@ TEST_F(LibraryServerTest, no_name_mapper_catalog_v2_individual_entry_access)
" </div>\n" \
" <div id=\"kiwixfooter\" class=\"kiwixfooter\">Powered by&nbsp;<a href=\"https://kiwix.org\">Kiwix</a></div>\n" \
" </body>\n" \
"</html>\n"
"</html>"
#define FILTERS_HTML(SELECTED_ENG) \
" <div class=\"kiwixNav__filters\">\n" \

View File

@@ -8,18 +8,18 @@
TEST(ManagerTest, addBookFromPathAndGetIdTest)
{
auto lib = kiwix::Library::create();
kiwix::Manager manager = kiwix::Manager(lib);
kiwix::Library lib;
kiwix::Manager manager = kiwix::Manager(&lib);
auto bookId = manager.addBookFromPathAndGetId("./test/example.zim");
ASSERT_NE(bookId, "");
kiwix::Book book = lib->getBookById(bookId);
kiwix::Book book = lib.getBookById(bookId);
EXPECT_EQ(book.getPath(), kiwix::computeAbsolutePath("", "./test/example.zim"));
const std::string pathToSave = "./pathToSave";
const std::string url = "url";
bookId = manager.addBookFromPathAndGetId("./test/example.zim", pathToSave, url, true);
book = lib->getBookById(bookId);
book = lib.getBookById(bookId);
auto savedPath = kiwix::computeAbsolutePath(kiwix::removeLastPathElement(manager.writableLibraryPath), pathToSave);
EXPECT_EQ(book.getPath(), savedPath);
EXPECT_EQ(book.getUrl(), url);
@@ -48,11 +48,11 @@ const char sampleLibraryXML[] = R"(
TEST(ManagerTest, readXml)
{
auto lib = kiwix::Library::create();
kiwix::Manager manager = kiwix::Manager(lib);
kiwix::Library lib;
kiwix::Manager manager = kiwix::Manager(&lib);
EXPECT_EQ(true, manager.readXml(sampleLibraryXML, true, "/data/lib.xml", true));
kiwix::Book book = lib->getBookById("0d0bcd57-d3f6-cb22-44cc-a723ccb4e1b2");
kiwix::Book book = lib.getBookById("0d0bcd57-d3f6-cb22-44cc-a723ccb4e1b2");
EXPECT_EQ("/data/zimfiles/unittest.zim", book.getPath());
EXPECT_EQ("https://example.com/zimfiles/unittest.zim", book.getUrl());
EXPECT_EQ("Unit Test", book.getTitle());
@@ -70,24 +70,24 @@ TEST(ManagerTest, readXml)
TEST(Manager, reload)
{
auto lib = kiwix::Library::create();
kiwix::Manager manager(lib);
kiwix::Library lib;
kiwix::Manager manager(&lib);
manager.reload({ "./test/library.xml" });
EXPECT_EQ(lib->getBooksIds(), (kiwix::Library::BookIdCollection{
EXPECT_EQ(lib.getBooksIds(), (kiwix::Library::BookIdCollection{
"charlesray",
"raycharles",
"raycharles_uncategorized"
}));
lib->removeBookById("raycharles");
EXPECT_EQ(lib->getBooksIds(), (kiwix::Library::BookIdCollection{
lib.removeBookById("raycharles");
EXPECT_EQ(lib.getBooksIds(), (kiwix::Library::BookIdCollection{
"charlesray",
"raycharles_uncategorized"
}));
manager.reload({ "./test/library.xml" });
EXPECT_EQ(lib->getBooksIds(), kiwix::Library::BookIdCollection({
EXPECT_EQ(lib.getBooksIds(), kiwix::Library::BookIdCollection({
"charlesray",
"raycharles",
"raycharles_uncategorized"

View File

@@ -5,8 +5,6 @@ tests = [
'stringTools',
'pathTools',
'otherTools',
'opdsParsingTools',
'languageTools',
'kiwixserve',
'book',
'manager',

View File

@@ -18,20 +18,18 @@ const char libraryXML[] = R"(
)";
class NameMapperTest : public ::testing::Test {
public:
NameMapperTest(): lib(kiwix::Library::create()) {}
protected:
void SetUp() override {
kiwix::Manager manager(lib);
kiwix::Manager manager(&lib);
manager.readXml(libraryXML, false, "./library.xml", true);
for ( const std::string& id : lib->getBooksIds() ) {
kiwix::Book bookCopy = lib->getBookById(id);
for ( const std::string& id : lib.getBooksIds() ) {
kiwix::Book bookCopy = lib.getBookById(id);
bookCopy.setPathValid(true);
lib->addBook(bookCopy);
lib.addBook(bookCopy);
}
}
std::shared_ptr<kiwix::Library> lib;
kiwix::Library lib;
};
class CapturedStderr
@@ -75,13 +73,13 @@ void checkUnaliasedEntriesInNameMapper(const kiwix::NameMapper& nm)
TEST_F(NameMapperTest, HumanReadableNameMapperWithoutAliases)
{
CapturedStderr stderror;
kiwix::HumanReadableNameMapper nm(*lib, false);
kiwix::HumanReadableNameMapper nm(lib, false);
EXPECT_EQ("", std::string(stderror));
checkUnaliasedEntriesInNameMapper(nm);
EXPECT_THROW(nm.getIdForName("zero_four"), std::out_of_range);
lib->removeBookById("04-2021-10");
lib.removeBookById("04-2021-10");
EXPECT_EQ("zero_four_2021-10", nm.getNameForId("04-2021-10"));
EXPECT_EQ("04-2021-10", nm.getIdForName("zero_four_2021-10"));
EXPECT_THROW(nm.getIdForName("zero_four"), std::out_of_range);
@@ -90,7 +88,7 @@ TEST_F(NameMapperTest, HumanReadableNameMapperWithoutAliases)
TEST_F(NameMapperTest, HumanReadableNameMapperWithAliases)
{
CapturedStderr stderror;
kiwix::HumanReadableNameMapper nm(*lib, true);
kiwix::HumanReadableNameMapper nm(lib, true);
EXPECT_EQ(
"Path collision: /data/zero_four_2021-10.zim and"
" /data/zero_four_2021-11.zim can't share the same URL path 'zero_four'."
@@ -101,7 +99,7 @@ TEST_F(NameMapperTest, HumanReadableNameMapperWithAliases)
checkUnaliasedEntriesInNameMapper(nm);
EXPECT_EQ("04-2021-10", nm.getIdForName("zero_four"));
lib->removeBookById("04-2021-10");
lib.removeBookById("04-2021-10");
EXPECT_EQ("zero_four_2021-10", nm.getNameForId("04-2021-10"));
EXPECT_EQ("04-2021-10", nm.getIdForName("zero_four_2021-10"));
EXPECT_EQ("04-2021-10", nm.getIdForName("zero_four"));
@@ -116,7 +114,7 @@ TEST_F(NameMapperTest, UpdatableNameMapperWithoutAliases)
checkUnaliasedEntriesInNameMapper(nm);
EXPECT_THROW(nm.getIdForName("zero_four"), std::out_of_range);
lib->removeBookById("04-2021-10");
lib.removeBookById("04-2021-10");
nm.update();
EXPECT_THROW(nm.getNameForId("04-2021-10"), std::out_of_range);
EXPECT_THROW(nm.getIdForName("zero_four_2021-10"), std::out_of_range);
@@ -139,7 +137,7 @@ TEST_F(NameMapperTest, UpdatableNameMapperWithAliases)
{
CapturedStderr nmUpdateStderror;
lib->removeBookById("04-2021-10");
lib.removeBookById("04-2021-10");
nm.update();
EXPECT_EQ("", std::string(nmUpdateStderror));
}

View File

@@ -1,131 +0,0 @@
/*
* Copyright (C) 2023 Nikhil Tanwar (2002nikhiltanwar@gmail.com)
*
* 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 "../include/tools.h"
typedef kiwix::FeedLanguages FeedLanguages;
typedef kiwix::FeedCategories FeedCategories;
namespace
{
const char sampleLanguageOpdsStream[] = R"(
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"
xmlns:dc="http://purl.org/dc/terms/"
xmlns:opds="https://specs.opds.io/opds-1.2"
xmlns:thr="http://purl.org/syndication/thread/1.0">
<id>1e587935-0f7b-dad6-eddc-ef3fafd4c3ed</id>
<link rel="self"
href="/catalog/v2/languages"
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
<link rel="start"
href="/catalog/v2/root.xml"
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
<title>List of languages</title>
<updated>2023-07-11T15:35:09Z</updated>
<entry>
<title>Abkhazian</title>
<dc:language>abk</dc:language>
<thr:count>3</thr:count>
<link rel="subsection"
href="/catalog/v2/entries?lang=abk"
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
<updated>2023-07-11T15:35:09Z</updated>
<id>2e4d9a1c-9750-0418-8124-a0c663e206f7</id>
</entry>
<entry>
<title>isiZulu</title>
<dc:language>zul</dc:language>
<thr:count>4</thr:count>
<link rel="subsection"
href="/catalog/v2/entries?lang=zul"
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
<updated>2023-07-11T15:35:09Z</updated>
<id>76eec223-994d-9b95-e309-baee06e585b0</id>
</entry>
</feed>
)";
const char sampleCategoriesOpdsStream[] = R"(
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"
xmlns:opds="https://specs.opds.io/opds-1.2">
<id>231da20c-0fe0-7345-11b2-d29f50364108</id>
<link rel="self"
href="/catalog/v2/categories"
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
<link rel="start"
href="/catalog/v2/root.xml"
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
<title>List of categories</title>
<updated>2023-07-11T15:35:09Z</updated>
<entry>
<title>gutenberg</title>
<link rel="subsection"
href="/catalog/v2/entries?category=gutenberg"
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
<updated>2023-07-11T15:35:09Z</updated>
<id>401dbe68-2f7a-5503-b431-054801c30bab</id>
<content type="text">All entries with category of 'gutenberg'.</content>
</entry>
<entry>
<title>iFixit</title>
<link rel="subsection"
href="/catalog/v2/entries?category=iFixit"
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
<updated>2023-07-11T15:35:09Z</updated>
<id>c18e5459-af23-5fbf-0622-ff271bd9a5ad</id>
<content type="text">All entries with category of 'iFixit'.</content>
</entry>
<entry>
<title>wikivoyage</title>
<link rel="subsection"
href="/catalog/v2/entries?category=wikivoyage"
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
<updated>2023-07-11T15:35:09Z</updated>
<id>9a75be6c-7a35-6f52-1a69-bee9ad248459</id>
<content type="text">All entries with category of 'wikivoyage'.</content>
</entry>
<entry>
<title>wiktionary</title>
<link rel="subsection"
href="/catalog/v2/entries?category=wiktionary"
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
<updated>2023-07-11T15:35:09Z</updated>
<id>7adb9f1a-73d7-0391-1238-d2e2c300ddaa</id>
<content type="text">All entries with category of 'wiktionary'.</content>
</entry>
</feed>
)";
TEST(OpdsParsingTest, languageTest)
{
FeedLanguages expectedLanguagesFromFeed = {{"abk", "Abkhazian"}, {"zul", "isiZulu"}};
EXPECT_EQ(kiwix::readLanguagesFromFeed(sampleLanguageOpdsStream), expectedLanguagesFromFeed);
}
TEST(OpdsParsingTest, categoryTest)
{
FeedCategories expectedCategoriesFromFeed = {"gutenberg", "iFixit", "wikivoyage", "wiktionary"};
EXPECT_EQ(kiwix::readCategoriesFromFeed(sampleCategoriesOpdsStream), expectedCategoriesFromFeed);
}
}

View File

@@ -100,7 +100,7 @@ TEST(Suggestions, specialCharHandling)
{
// HTML special symbols (<, >, &, ", and ') must be HTML-escaped
// Backslash symbols (\) must be duplicated.
const std::string SYMBOLS("\t\n\r" R"(\<>&'"~!@#$%^*()_+`-=[]{}|:;,.?)");
const std::string SYMBOLS(R"(\<>&'"~!@#$%^*()_+`-=[]{}|:;,.?)");
{
kiwix::Suggestions s;
s.add(zim::SuggestionItem("Title with " + SYMBOLS,
@@ -110,10 +110,10 @@ TEST(Suggestions, specialCharHandling)
CHECK_SUGGESTIONS(s.getJSON(),
R"EXPECTEDJSON([
{
"value" : "Title with \u0009\u0010\u0013\\&lt;&gt;&amp;&apos;&quot;~!@#$%^*()_+`-=[]{}|:;,.?",
"label" : "Snippet with \u0009\u0010\u0013\\&lt;&gt;&amp;&apos;&quot;~!@#$%^*()_+`-=[]{}|:;,.?",
"value" : "Title with \\&lt;&gt;&amp;&apos;&quot;~!@#$%^*()_+`-=[]{}|:;,.?",
"label" : "Snippet with \\&lt;&gt;&amp;&apos;&quot;~!@#$%^*()_+`-=[]{}|:;,.?",
"kind" : "path"
, "path" : "Path with \u0009\u0010\u0013\\&lt;&gt;&amp;&apos;&quot;~!@#$%^*()_+`-=[]{}|:;,.?"
, "path" : "Path with \\&lt;&gt;&amp;&apos;&quot;~!@#$%^*()_+`-=[]{}|:;,.?"
}
]
)EXPECTEDJSON"
@@ -128,10 +128,10 @@ R"EXPECTEDJSON([
CHECK_SUGGESTIONS(s.getJSON(),
R"EXPECTEDJSON([
{
"value" : "Snippetless title with \u0009\u0010\u0013\\&lt;&gt;&amp;&apos;&quot;~!@#$%^*()_+`-=[]{}|:;,.?",
"label" : "Snippetless title with \u0009\u0010\u0013\\&lt;&gt;&amp;&apos;&quot;~!@#$%^*()_+`-=[]{}|:;,.?",
"value" : "Snippetless title with \\&lt;&gt;&amp;&apos;&quot;~!@#$%^*()_+`-=[]{}|:;,.?",
"label" : "Snippetless title with \\&lt;&gt;&amp;&apos;&quot;~!@#$%^*()_+`-=[]{}|:;,.?",
"kind" : "path"
, "path" : "Path with \u0009\u0010\u0013\\&lt;&gt;&amp;&apos;&quot;~!@#$%^*()_+`-=[]{}|:;,.?"
, "path" : "Path with \\&lt;&gt;&amp;&apos;&quot;~!@#$%^*()_+`-=[]{}|:;,.?"
}
]
)EXPECTEDJSON"
@@ -145,8 +145,8 @@ R"EXPECTEDJSON([
CHECK_SUGGESTIONS(s.getJSON(),
R"EXPECTEDJSON([
{
"value" : "text with \u0009\u0010\u0013\\&lt;&gt;&amp;&apos;&quot;~!@#$%^*()_+`-=[]{}|:;,.? ",
"label" : "containing &apos;text with \u0009\u0010\u0013\\&lt;&gt;&amp;&apos;&quot;~!@#$%^*()_+`-=[]{}|:;,.?&apos;...",
"value" : "text with \\&lt;&gt;&amp;&apos;&quot;~!@#$%^*()_+`-=[]{}|:;,.? ",
"label" : "containing &apos;text with \\&lt;&gt;&amp;&apos;&quot;~!@#$%^*()_+`-=[]{}|:;,.?&apos;...",
"kind" : "pattern"
//EOLWHITESPACEMARKER
}

View File

@@ -54,28 +54,26 @@ const ResourceCollection resources200Compressible{
{ DYNAMIC_CONTENT, "/ROOT%23%3F/viewer" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/viewer?cacheid=whatever" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/autoComplete/autoComplete.min.js" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/autoComplete/autoComplete.min.js?cacheid=1191aaaf" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/autoComplete/css/autoComplete.css" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/autoComplete/css/autoComplete.css?cacheid=ef30cd42" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/autoComplete.min.js" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/autoComplete.min.js?cacheid=1191aaaf" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/css/autoComplete.css" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/css/autoComplete.css?cacheid=08951e06" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/i18n.js" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/i18n.js?cacheid=6a8c6fb2" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/i18n.js?cacheid=2cf0f8c5" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/index.css" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.css?cacheid=1e78e7cf" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.css?cacheid=e4d76d16" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/index.js" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.js?cacheid=ce19da2a" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.js?cacheid=07b06fca" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/iso6391To3.js" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/iso6391To3.js?cacheid=ecde2bb3" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/isotope.pkgd.min.js" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/isotope.pkgd.min.js?cacheid=2e48d392" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/kiwix.css" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/kiwix.css?cacheid=2158fad9" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/mustache.min.js" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/mustache.min.js?cacheid=bd23c4fb" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/taskbar.css" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/taskbar.css?cacheid=e014a885" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/taskbar.css?cacheid=bbdaf425" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/viewer.js" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/viewer.js?cacheid=201653b8" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/viewer.js?cacheid=bb748367" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/fonts/Poppins.ttf" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/fonts/Poppins.ttf?cacheid=af705837" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/fonts/Roboto.ttf" },
@@ -149,7 +147,7 @@ const ResourceCollection resources200Uncompressible{
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/search_results.css" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/search_results.css?cacheid=76d39c84" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/languages.js" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/languages.js?cacheid=96f2cf73" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/languages.js?cacheid=648526e1" },
{ ZIM_CONTENT, "/ROOT%23%3F/raw/zimfile/meta/Title" },
{ ZIM_CONTENT, "/ROOT%23%3F/raw/zimfile/meta/Description" },
@@ -276,8 +274,7 @@ TEST_F(ServerTest, CacheIdsOfStaticResources)
const std::vector<UrlAndExpectedResult> testData{
{
/* url */ "/ROOT%23%3F/",
R"EXPECTEDRESULT( href="/ROOT%23%3F/skin/kiwix.css?cacheid=2158fad9"
href="/ROOT%23%3F/skin/index.css?cacheid=1e78e7cf"
R"EXPECTEDRESULT( href="/ROOT%23%3F/skin/index.css?cacheid=e4d76d16"
<link rel="apple-touch-icon" sizes="180x180" href="/ROOT%23%3F/skin/favicon/apple-touch-icon.png?cacheid=f86f8df3">
<link rel="icon" type="image/png" sizes="32x32" href="/ROOT%23%3F/skin/favicon/favicon-32x32.png?cacheid=79ded625">
<link rel="icon" type="image/png" sizes="16x16" href="/ROOT%23%3F/skin/favicon/favicon-16x16.png?cacheid=a986fedc">
@@ -285,19 +282,15 @@ R"EXPECTEDRESULT( href="/ROOT%23%3F/skin/kiwix.css?cacheid=2158fad9"
<link rel="mask-icon" href="/ROOT%23%3F/skin/favicon/safari-pinned-tab.svg?cacheid=8d487e95" color="#5bbad5">
<link rel="shortcut icon" href="/ROOT%23%3F/skin/favicon/favicon.ico?cacheid=92663314">
<meta name="msapplication-config" content="/ROOT%23%3F/skin/favicon/browserconfig.xml?cacheid=f29a7c4a">
<script type="module" src="/ROOT%23%3F/skin/i18n.js?cacheid=6a8c6fb2" defer></script>
<script type="text/javascript" src="/ROOT%23%3F/skin/languages.js?cacheid=96f2cf73" defer></script>
src: url("/ROOT%23%3F/skin/fonts/Poppins.ttf?cacheid=af705837") format("truetype");
src: url("/ROOT%23%3F/skin/fonts/Roboto.ttf?cacheid=84d10248") format("truetype");
<script type="module" src="/ROOT%23%3F/skin/i18n.js?cacheid=2cf0f8c5" defer></script>
<script type="text/javascript" src="/ROOT%23%3F/skin/languages.js?cacheid=648526e1" defer></script>
<script src="/ROOT%23%3F/skin/isotope.pkgd.min.js?cacheid=2e48d392" defer></script>
<script src="/ROOT%23%3F/skin/iso6391To3.js?cacheid=ecde2bb3"></script>
<script type="text/javascript" src="/ROOT%23%3F/skin/index.js?cacheid=ce19da2a" defer></script>
<script type="text/javascript" src="/ROOT%23%3F/skin/index.js?cacheid=07b06fca" defer></script>
<img src="/ROOT%23%3F/skin/feed.svg?cacheid=055b333f"
<img src="/ROOT%23%3F/skin/langSelector.svg?cacheid=00b59961"
)EXPECTEDRESULT"
},
{
/* url */ "/ROOT%23%3F/skin/kiwix.css",
R"EXPECTEDRESULT( src: url("../skin/fonts/Poppins.ttf?cacheid=af705837") format("truetype");
src: url("../skin/fonts/Roboto.ttf?cacheid=84d10248") format("truetype");
)EXPECTEDRESULT"
},
{
@@ -315,16 +308,15 @@ R"EXPECTEDRESULT( <img src="${root}/skin/download
},
{
/* url */ "/ROOT%23%3F/viewer",
R"EXPECTEDRESULT( <link type="text/css" href="./skin/kiwix.css?cacheid=2158fad9" rel="Stylesheet" />
<link type="text/css" href="./skin/taskbar.css?cacheid=e014a885" rel="Stylesheet" />
<link type="text/css" href="./skin/autoComplete/css/autoComplete.css?cacheid=ef30cd42" rel="Stylesheet" />
<script type="module" src="./skin/i18n.js?cacheid=6a8c6fb2" defer></script>
<script type="text/javascript" src="./skin/languages.js?cacheid=96f2cf73" defer></script>
<script type="text/javascript" src="./skin/viewer.js?cacheid=201653b8" defer></script>
<script type="text/javascript" src="./skin/autoComplete/autoComplete.min.js?cacheid=1191aaaf"></script>
R"EXPECTEDRESULT( <link type="text/css" href="./skin/taskbar.css?cacheid=bbdaf425" rel="Stylesheet" />
<link type="text/css" href="./skin/css/autoComplete.css?cacheid=08951e06" rel="Stylesheet" />
<script type="module" src="./skin/i18n.js?cacheid=2cf0f8c5" defer></script>
<script type="text/javascript" src="./skin/languages.js?cacheid=648526e1" defer></script>
<script type="text/javascript" src="./skin/viewer.js?cacheid=bb748367" defer></script>
<script type="text/javascript" src="./skin/autoComplete.min.js?cacheid=1191aaaf"></script>
const blankPageUrl = root + "/skin/blank.html?cacheid=6b1fa032";
<img src="./skin/langSelector.svg?cacheid=00b59961">
<label for="kiwix_button_show_toggle"><img src="./skin/caret.png?cacheid=22b942b4" alt=""></label>
src="./skin/langSelector.svg?cacheid=00b59961">
src="./skin/blank.html?cacheid=6b1fa032" title="ZIM content" width="100%"
)EXPECTEDRESULT"
},
@@ -379,7 +371,7 @@ const char* urls404[] = {
"/ROOT%23%3",
"/ROOT%23%3Fxyz",
"/ROOT%23%3F/skin/non-existent-skin-resource",
"/ROOT%23%3F/skin/autoComplete/autoComplete.min.js?cacheid=wrongcacheid",
"/ROOT%23%3F/skin/autoComplete.min.js?cacheid=wrongcacheid",
"/ROOT%23%3F/catalog",
"/ROOT%23%3F/catalog/",
"/ROOT%23%3F/catalog/non-existent-item",
@@ -1064,21 +1056,15 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
{
"English": "en"
},
{
"español": "es"
},
{
"suomi": "fi"
},
{
"Français": "fr"
"français": "fr"
},
{
"עברית": "he"
},
{
"हिन्दी": "hi"
},
{
"Հայերեն": "hy"
},
@@ -1103,18 +1089,9 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
{
"македонски": "mk"
},
{
"Bahasa Melayu": "ms"
},
{
"Nederlands": "nl"
},
{
"ߒߞߏ": "nqo"
},
{
"ଓଡ଼ିଆ": "or"
},
{
"Polski": "pl"
},
@@ -1127,21 +1104,12 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
{
"slovenčina": "sk"
},
{
"سرائیکی": "skr-arab"
},
{
"slovenščina": "sl"
},
{
"Shqip": "sq"
},
{
"Svenska": "sv"
},
{
"ఇంగ్లీషు": "te"
},
{
"Türkçe": "tr"
},
@@ -1162,6 +1130,7 @@ TEST_F(ServerTest, UserLanguageControl)
const std::string url;
const std::string acceptLanguageHeader;
const char* const requestCookie; // Cookie: header of the request
const char* const responseSetCookie; // Set-Cookie: header of the response
const std::string expectedH1;
operator TestContext() const
@@ -1188,6 +1157,7 @@ TEST_F(ServerTest, UserLanguageControl)
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "",
/*Request Cookie:*/ NO_COOKIE,
/*Response Set-Cookie:*/ "userlang=en;Path=/ROOT%23%3F;Max-Age=31536000",
/* expected <h1> */ "Not Found"
},
{
@@ -1195,6 +1165,7 @@ TEST_F(ServerTest, UserLanguageControl)
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article?userlang=en",
/*Accept-Language:*/ "",
/*Request Cookie:*/ NO_COOKIE,
/*Response Set-Cookie:*/ "userlang=en;Path=/ROOT%23%3F;Max-Age=31536000",
/* expected <h1> */ "Not Found"
},
{
@@ -1202,6 +1173,7 @@ TEST_F(ServerTest, UserLanguageControl)
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article?userlang=test",
/*Accept-Language:*/ "",
/*Request Cookie:*/ NO_COOKIE,
/*Response Set-Cookie:*/ "userlang=test;Path=/ROOT%23%3F;Max-Age=31536000",
/* expected <h1> */ "[I18N TESTING] Content not found, but at least the server is alive"
},
{
@@ -1209,6 +1181,7 @@ TEST_F(ServerTest, UserLanguageControl)
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "*",
/*Request Cookie:*/ NO_COOKIE,
/*Response Set-Cookie:*/ "userlang=en;Path=/ROOT%23%3F;Max-Age=31536000",
/* expected <h1> */ "Not Found"
},
{
@@ -1216,20 +1189,71 @@ TEST_F(ServerTest, UserLanguageControl)
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "test",
/*Request Cookie:*/ NO_COOKIE,
/*Response Set-Cookie:*/ "userlang=test;Path=/ROOT%23%3F;Max-Age=31536000",
/* expected <h1> */ "[I18N TESTING] Content not found, but at least the server is alive"
},
{
"userlang cookie is ignored",
"userlang cookie is respected",
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "",
/*Request Cookie:*/ "userlang=test",
/* expected <h1> */ "Not Found"
/*Response Set-Cookie:*/ NO_COOKIE,
/* expected <h1> */ "[I18N TESTING] Content not found, but at least the server is alive"
},
{
"userlang cookie is correctly parsed",
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "",
/*Request Cookie:*/ "anothercookie=123; userlang=test",
/*Response Set-Cookie:*/ NO_COOKIE,
/* expected <h1> */ "[I18N TESTING] Content not found, but at least the server is alive"
},
{
"userlang cookie is correctly parsed",
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "",
/*Request Cookie:*/ "userlang=test; anothercookie=abc",
/*Response Set-Cookie:*/ NO_COOKIE,
/* expected <h1> */ "[I18N TESTING] Content not found, but at least the server is alive"
},
{
"userlang cookie is correctly parsed",
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "",
/*Request Cookie:*/ "cookie1=abc; userlang=test; cookie2=xyz",
/*Response Set-Cookie:*/ NO_COOKIE,
/* expected <h1> */ "[I18N TESTING] Content not found, but at least the server is alive"
},
{
"Multiple userlang cookies are not a problem",
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "",
/*Request Cookie:*/ "cookie1=abc; userlang=en; userlang=test; cookie2=xyz",
/*Response Set-Cookie:*/ NO_COOKIE,
/* expected <h1> */ "[I18N TESTING] Content not found, but at least the server is alive"
},
{
"userlang query parameter takes precedence over Accept-Language",
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article?userlang=en",
/*Accept-Language:*/ "test",
/*Request Cookie:*/ NO_COOKIE,
/*Response Set-Cookie:*/ "userlang=en;Path=/ROOT%23%3F;Max-Age=31536000",
/* expected <h1> */ "Not Found"
},
{
"userlang query parameter takes precedence over its cookie counterpart",
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article?userlang=en",
/*Accept-Language:*/ "",
/*Request Cookie:*/ "userlang=test",
/*Response Set-Cookie:*/ "userlang=en;Path=/ROOT%23%3F;Max-Age=31536000",
/* expected <h1> */ "Not Found"
},
{
"userlang in cookies takes precedence over Accept-Language",
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "test",
/*Request Cookie:*/ "userlang=en",
/*Response Set-Cookie:*/ NO_COOKIE,
/* expected <h1> */ "Not Found"
},
{
@@ -1239,6 +1263,7 @@ TEST_F(ServerTest, UserLanguageControl)
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "test;q=0.9, en;q=0.2",
/*Request Cookie:*/ NO_COOKIE,
/*Response Set-Cookie:*/ "userlang=test;Path=/ROOT%23%3F;Max-Age=31536000",
/* expected <h1> */ "[I18N TESTING] Content not found, but at least the server is alive"
},
{
@@ -1248,6 +1273,7 @@ TEST_F(ServerTest, UserLanguageControl)
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "test;q=0.2, en;q=0.9",
/*Request Cookie:*/ NO_COOKIE,
/*Response Set-Cookie:*/ "userlang=en;Path=/ROOT%23%3F;Max-Age=31536000",
/* expected <h1> */ "Not Found"
},
};
@@ -1263,7 +1289,12 @@ TEST_F(ServerTest, UserLanguageControl)
headers.insert({"Cookie", t.requestCookie});
}
const auto r = zfs1_->GET(t.url.c_str(), headers);
EXPECT_FALSE(r->has_header("Set-Cookie"));
if ( t.responseSetCookie ) {
ASSERT_TRUE(r->has_header("Set-Cookie")) << t;
EXPECT_EQ(t.responseSetCookie, getHeaderValue(r->headers, "Set-Cookie")) << t;
} else {
EXPECT_FALSE(r->has_header("Set-Cookie"));
}
std::regex_search(r->body, h1Match, h1Regex);
const std::string h1(h1Match[1]);
EXPECT_EQ(h1, t.expectedH1) << t;
@@ -1958,8 +1989,7 @@ TEST_F(ServerTest, viewerSettings)
R"(const viewerSettings = {
toolbarEnabled: false,
linkBlockingEnabled: false,
libraryButtonEnabled: false,
defaultUserLanguage: "en"
libraryButtonEnabled: false
}
)");
}
@@ -1970,8 +2000,7 @@ R"(const viewerSettings = {
R"(const viewerSettings = {
toolbarEnabled: false,
linkBlockingEnabled: true,
libraryButtonEnabled: false,
defaultUserLanguage: "en"
libraryButtonEnabled: false
}
)");
}
@@ -1982,8 +2011,7 @@ R"(const viewerSettings = {
R"(const viewerSettings = {
toolbarEnabled: true,
linkBlockingEnabled: false,
libraryButtonEnabled: false,
defaultUserLanguage: "en"
libraryButtonEnabled: false
}
)");
}
@@ -1994,47 +2022,7 @@ R"(const viewerSettings = {
R"(const viewerSettings = {
toolbarEnabled: true,
linkBlockingEnabled: false,
libraryButtonEnabled: true,
defaultUserLanguage: "en"
}
)");
}
{
resetServer(ZimFileServer::WITH_TASKBAR_AND_LIBRARY_BUTTON);
const Headers headers{ {"Accept-Language", "fr"} };
ASSERT_EQ(zfs1_->GET("/ROOT%23%3F/viewer_settings.js", headers)->body,
R"(const viewerSettings = {
toolbarEnabled: true,
linkBlockingEnabled: false,
libraryButtonEnabled: true,
defaultUserLanguage: "fr"
}
)");
}
{
resetServer(ZimFileServer::WITH_TASKBAR_AND_LIBRARY_BUTTON);
const Headers headers{ {"Accept-Language", "test;q=0.2, en;q=0.9"} };
ASSERT_EQ(zfs1_->GET("/ROOT%23%3F/viewer_settings.js", headers)->body,
R"(const viewerSettings = {
toolbarEnabled: true,
linkBlockingEnabled: false,
libraryButtonEnabled: true,
defaultUserLanguage: "en"
}
)");
}
{
resetServer(ZimFileServer::WITH_TASKBAR_AND_LIBRARY_BUTTON);
const Headers headers{ {"Accept-Language", "test;q=0.9, en;q=0.2"} };
ASSERT_EQ(zfs1_->GET("/ROOT%23%3F/viewer_settings.js", headers)->body,
R"(const viewerSettings = {
toolbarEnabled: true,
linkBlockingEnabled: false,
libraryButtonEnabled: true,
defaultUserLanguage: "test"
libraryButtonEnabled: true
}
)");
}

View File

@@ -98,17 +98,16 @@ private:
void run(int serverPort, std::string indexTemplateString = "");
private: // data
std::shared_ptr<kiwix::Library> library;
kiwix::Library library;
kiwix::Manager manager;
std::shared_ptr<kiwix::NameMapper> nameMapper;
std::unique_ptr<kiwix::NameMapper> nameMapper;
std::unique_ptr<kiwix::Server> server;
std::unique_ptr<httplib::Client> client;
const Cfg cfg;
};
ZimFileServer::ZimFileServer(int serverPort, Cfg _cfg, std::string libraryFilePath)
: library(kiwix::Library::create())
, manager(this->library)
: manager(&this->library)
, cfg(_cfg)
{
if ( kiwix::isRelativePath(libraryFilePath) )
@@ -121,8 +120,7 @@ ZimFileServer::ZimFileServer(int serverPort,
Cfg _cfg,
const FilePathCollection& zimpaths,
std::string indexTemplateString)
: library(kiwix::Library::create())
, manager(this->library)
: manager(&this->library)
, cfg(_cfg)
{
for ( const auto& zimpath : zimpaths ) {
@@ -138,9 +136,9 @@ void ZimFileServer::run(int serverPort, std::string indexTemplateString)
if (cfg.options & NO_NAME_MAPPER) {
nameMapper.reset(new kiwix::IdNameMapper());
} else {
nameMapper.reset(new kiwix::HumanReadableNameMapper(*library, false));
nameMapper.reset(new kiwix::HumanReadableNameMapper(library, false));
}
server.reset(new kiwix::Server(library, nameMapper));
server.reset(new kiwix::Server(&library, nameMapper.get()));
server->setRoot(cfg.root);
server->setAddress(address);
server->setPort(serverPort);