mirror of
https://github.com/kiwix/libkiwix.git
synced 2025-12-30 09:58:10 -05:00
Compare commits
161 Commits
update_htt
...
10.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce24b1fa5f | ||
|
|
9193719c8f | ||
|
|
d0d253beed | ||
|
|
cf95d513d6 | ||
|
|
e72c0b75f6 | ||
|
|
4d996584fa | ||
|
|
dd3338c2d0 | ||
|
|
b19eb1ea61 | ||
|
|
6d14639f77 | ||
|
|
89e3a57a05 | ||
|
|
b94e4b7e3b | ||
|
|
68465079f0 | ||
|
|
f6309bb4c8 | ||
|
|
45e9b76b19 | ||
|
|
5a9dbf85ec | ||
|
|
cd412867d9 | ||
|
|
01edd830bc | ||
|
|
ceb46f1069 | ||
|
|
270773d6ba | ||
|
|
234606b170 | ||
|
|
b8328a78f6 | ||
|
|
08c3a9d8b2 | ||
|
|
744065276b | ||
|
|
e3f8046915 | ||
|
|
3198a849e2 | ||
|
|
fcae21c55b | ||
|
|
7da81acaa3 | ||
|
|
a7d19b170a | ||
|
|
000029166b | ||
|
|
2ccea8e370 | ||
|
|
84587e7f03 | ||
|
|
38fc187303 | ||
|
|
2dc8fc9ab3 | ||
|
|
60b4be2286 | ||
|
|
a8a68c54a2 | ||
|
|
367e5d2636 | ||
|
|
fcd865bb81 | ||
|
|
715151d725 | ||
|
|
e5eeb08206 | ||
|
|
ac6f91798f | ||
|
|
e5d26a4699 | ||
|
|
3d64a9d9a9 | ||
|
|
fb7d9f02c8 | ||
|
|
96e0d15ab4 | ||
|
|
39732e2bcf | ||
|
|
7dfafe0196 | ||
|
|
3052d0787a | ||
|
|
d4db090bb9 | ||
|
|
0633f68f80 | ||
|
|
2a5db3e7ab | ||
|
|
468a080b09 | ||
|
|
1705f938b5 | ||
|
|
0112e6102d | ||
|
|
e4d99f0374 | ||
|
|
aa50845e22 | ||
|
|
3dbcbe542b | ||
|
|
854058f842 | ||
|
|
c9eb3196f7 | ||
|
|
dc15a9a824 | ||
|
|
160a74f5f8 | ||
|
|
78c10346f2 | ||
|
|
6f1799db9f | ||
|
|
e108fb0e47 | ||
|
|
9482bfb95b | ||
|
|
66c40817ee | ||
|
|
22e5327dcf | ||
|
|
d29611ed75 | ||
|
|
b1bc883bf5 | ||
|
|
a8577d7a01 | ||
|
|
8bdcb90818 | ||
|
|
91a4491b74 | ||
|
|
f36d8e9851 | ||
|
|
5e1988640c | ||
|
|
aa3bd8560b | ||
|
|
9c047844c0 | ||
|
|
843d315b93 | ||
|
|
f1035fa472 | ||
|
|
6c95458f5e | ||
|
|
9554ab5db0 | ||
|
|
4b563e567e | ||
|
|
a77fae0993 | ||
|
|
ed2f914e10 | ||
|
|
872ddd9cb3 | ||
|
|
20b5a2b971 | ||
|
|
d8c525289b | ||
|
|
f7b853373c | ||
|
|
d21879ebda | ||
|
|
35dc59d4bb | ||
|
|
a89924a23f | ||
|
|
7f6a6055a9 | ||
|
|
90910c5cab | ||
|
|
250f46c7f9 | ||
|
|
0be00b791f | ||
|
|
ff050dc811 | ||
|
|
9f3459f3f3 | ||
|
|
7ea12697f4 | ||
|
|
ab53e0cff1 | ||
|
|
27414f7731 | ||
|
|
e1db9164c8 | ||
|
|
fa8d0f8e07 | ||
|
|
3589a51fff | ||
|
|
01ac0b2fe1 | ||
|
|
405ea29900 | ||
|
|
7161db8e2a | ||
|
|
262e13845c | ||
|
|
1d5383435d | ||
|
|
ad2eb52553 | ||
|
|
473d2d2a69 | ||
|
|
02b9e32d18 | ||
|
|
c2927ce6f7 | ||
|
|
b712c732f2 | ||
|
|
298247ca9b | ||
|
|
3aeeeeee76 | ||
|
|
226dac2604 | ||
|
|
76a5e3a877 | ||
|
|
2d6a7fe88d | ||
|
|
6199c11505 | ||
|
|
8fffa59974 | ||
|
|
4ccbdcb740 | ||
|
|
5f3c34ed93 | ||
|
|
d62c4fd521 | ||
|
|
339f845fb0 | ||
|
|
3296a020a1 | ||
|
|
571e417d1e | ||
|
|
913a368a12 | ||
|
|
0e48baf9f9 | ||
|
|
c7b88398bd | ||
|
|
4a01081e83 | ||
|
|
eb6a0d6456 | ||
|
|
e2544799a1 | ||
|
|
9f42884507 | ||
|
|
8a6adddc16 | ||
|
|
c8da5eea2b | ||
|
|
bd29c4c7ef | ||
|
|
e52a4a646b | ||
|
|
537ba7e6b9 | ||
|
|
f4bc3c8ced | ||
|
|
5263f6880c | ||
|
|
c129952605 | ||
|
|
9f0db6b7fa | ||
|
|
7d8a83cc97 | ||
|
|
ec5a423924 | ||
|
|
811b73a4f1 | ||
|
|
3e5372ef29 | ||
|
|
abfd9d88d8 | ||
|
|
4f65811011 | ||
|
|
59e5c7ff4e | ||
|
|
ef1ad4bf47 | ||
|
|
8d50d5e293 | ||
|
|
d76e670d5b | ||
|
|
8f5ffc5ef5 | ||
|
|
98f9c57e12 | ||
|
|
db06b6b797 | ||
|
|
513c547d99 | ||
|
|
c3d2e01157 | ||
|
|
4adad9b281 | ||
|
|
251f3a01ed | ||
|
|
0fcf166111 | ||
|
|
eea6f9fe27 | ||
|
|
0b9ff92e42 | ||
|
|
1f6fb238ba |
15
.github/workflows/ci.yml
vendored
15
.github/workflows/ci.yml
vendored
@@ -8,10 +8,10 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v1
|
||||
- name: Setup python 3.5
|
||||
uses: actions/setup-python@v1
|
||||
- name: Setup python 3.10
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.5'
|
||||
python-version: '3.10'
|
||||
- name: Install packages
|
||||
run: |
|
||||
brew update
|
||||
@@ -37,17 +37,8 @@ jobs:
|
||||
export LD_LIBRARY_PATH=$HOME/BUILD_native_dyn/INSTALL/lib:$HOME/BUILD_native_dyn/INSTALL/lib64
|
||||
cd build
|
||||
meson test --verbose
|
||||
ninja coverage
|
||||
env:
|
||||
SKIP_BIG_MEMORY_TEST: 1
|
||||
- name: Publish coverage
|
||||
shell: bash
|
||||
run: |
|
||||
curl https://codecov.io/bash -o codecov.sh
|
||||
bash codecov.sh -n osx_native_dyn -Z
|
||||
rm codecov.sh
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
Linux:
|
||||
strategy:
|
||||
|
||||
18
.github/workflows/package.yml
vendored
18
.github/workflows/package.yml
vendored
@@ -8,8 +8,8 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
distro:
|
||||
- ubuntu-jammy
|
||||
- ubuntu-impish
|
||||
- ubuntu-hirsute
|
||||
- ubuntu-focal
|
||||
- ubuntu-bionic
|
||||
steps:
|
||||
@@ -34,18 +34,18 @@ jobs:
|
||||
email: release+launchpad@kiwix.org
|
||||
distro: ${{ matrix.distro }}
|
||||
|
||||
- uses: legoktm/gh-action-build-deb@ubuntu-impish
|
||||
if: matrix.distro == 'ubuntu-impish'
|
||||
name: Build package for ubuntu-impish
|
||||
id: build-ubuntu-impish
|
||||
- uses: legoktm/gh-action-build-deb@ubuntu-jammy
|
||||
if: matrix.distro == 'ubuntu-jammy'
|
||||
name: Build package for ubuntu-jammy
|
||||
id: build-ubuntu-jammy
|
||||
with:
|
||||
args: --no-sign
|
||||
ppa: ${{ steps.ppa.outputs.ppa }}
|
||||
|
||||
- uses: legoktm/gh-action-build-deb@ubuntu-hirsute
|
||||
if: matrix.distro == 'ubuntu-hirsute'
|
||||
name: Build package for ubuntu-hirsute
|
||||
id: build-ubuntu-hirsute
|
||||
- uses: legoktm/gh-action-build-deb@ubuntu-impish
|
||||
if: matrix.distro == 'ubuntu-impish'
|
||||
name: Build package for ubuntu-impish
|
||||
id: build-ubuntu-impish
|
||||
with:
|
||||
args: --no-sign
|
||||
ppa: ${{ steps.ppa.outputs.ppa }}
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,3 +5,5 @@ subprojects/googletest-release*
|
||||
build/
|
||||
.vscode/
|
||||
builddir/
|
||||
.cache/
|
||||
.clangd/
|
||||
|
||||
79
ChangeLog
79
ChangeLog
@@ -1,7 +1,84 @@
|
||||
libkiwix 10.0.1
|
||||
===============
|
||||
|
||||
* [server] The catalog search interpret `count=0` as no limit.
|
||||
This was the case for a long time. This was changed unintentionally
|
||||
(@veloman-yunkan #686)
|
||||
* [server] Correctly generere a human friendly title in the server frontend.
|
||||
(@juuz0 #687, @kelson42 #689)
|
||||
* [server] Fix download button if there is no url do download from.
|
||||
(@juuz0 #691)
|
||||
* Add non-minified isotope.pkdg.js
|
||||
Needed for debian packaging as we need the source and minified version is
|
||||
not the source (@legoktm #693)
|
||||
* [server] Add a tooltip with the full language for the lang tag.
|
||||
* CI fixes (@kelson42 @legoktm)
|
||||
|
||||
libkiwix 10.0.0
|
||||
===============
|
||||
|
||||
* ...
|
||||
This release is huge release.
|
||||
The project has been renamed to libkiwix, it is more coherent with the library name.
|
||||
|
||||
* Server front page :
|
||||
- Use js in the front page to display the available book,
|
||||
using the OPDS stream as source. The front page is now populated only with
|
||||
the visible books and user can search for books. (@MananJethwany #530, #541, #534)
|
||||
(@kelson42 #628)
|
||||
- Revamp css (@MananJethwany #559)
|
||||
- Correctly Convert 3iso language code to 2iso (@juuz0 #672)
|
||||
|
||||
* Server suggestions search :
|
||||
- Add pagination for suggestion search (@maneeshpm #591)
|
||||
- Fix suggestion system (@MananJethwany #498)
|
||||
- Provide the kind and path (when adapted) to the suggestion answer (@MananJethwany #464)
|
||||
- The displayed suggestion have now highligth on the searched terms (@maneeshpm #505)
|
||||
- Properly handle html encoding of suggestions (@veloman-yunkan #458)
|
||||
|
||||
* Server improvements :
|
||||
- Remove meta endpoints (@mgautier #669)
|
||||
- Add raw endpoints to get the raw content of a zim (@mgautierfr #646)
|
||||
- Add details on 404 error pages (@soumyankar #490)
|
||||
- Fix headbar insertion when `<head>` tag has attributes (@kelson42 #440)
|
||||
- Better headbar insertion (after charset definition) (@kelson42 #442)
|
||||
|
||||
* New OPDS Stream v2 :
|
||||
- Add a list of categories (@veloman-yunkan)
|
||||
- Support for partial entries (@veloman-yunkan #602)
|
||||
- Support multiple icons size in the OPDS stream (@veloman-yunkan #577 #630)
|
||||
- Add language endpoint to catalog (@veloman-yunkan #553)
|
||||
- Add illustration API to get the illustration of a book (@mgautierfr #645)
|
||||
- OPDS search can now filter books by category (@veloman-yunkan #459)
|
||||
|
||||
* Library improvements :
|
||||
- Allow the libray to be live reloaded when the library.xml changes (@veloman-yunkan #636)
|
||||
- Properly handle removing of book from the library (@veloman-yunkan #485)
|
||||
- Use xapian to search for books in the library (@veloman-yunkan #460, #488)
|
||||
|
||||
* Added methods/functions :
|
||||
- Fix `fileExist` and introduce `fileReadable` (@juuz0 #668)
|
||||
- Add `getVersions` and `printVersions` functions (@kelson42 #665)
|
||||
- Add `getNetworkInterfaces()` and `getBestPublicIP()` functions (@juuz0 #622)
|
||||
- Add `get_zimid()` method to the search result (@maneeshpm #510)
|
||||
|
||||
* Various improvements :
|
||||
- Better secret value for aria2c rpc (@juuz0 #666)
|
||||
- Avoid duplicated Archive/Reader in the Searcher (@veloman-yunkan #648)
|
||||
- Add basic documentation (@mgautierfr #640)
|
||||
- Do not use Reader internally (@maneeshpm #536 #576)
|
||||
- Remove dependency headers from our public headers (@mgautierfr #574)
|
||||
- Downloader now don't write metalink on the filesystem (@kelson42 #502)
|
||||
- Support opening a zim file using a fd (@veloman-yukan #429)
|
||||
- Use C++11 std::thread instead of pthread (@mgautierfr #445)
|
||||
- [READER] Do not crash if zim file has no `Counter` metadata (@mgautierfr #449)
|
||||
- Ensure libzim dependency is compiled with xapian (@mgautierfr #434)
|
||||
- Support video and audio mimetype in `getMediaCount` (@kelson42 #439)
|
||||
- Better parsing of the counterMap (@kelson42 #437)
|
||||
- Adapt libkiwix to libzim 7.0.0 (@mgautierfr #428)
|
||||
- Remove deprecated methods (@mgautierfr)
|
||||
- CI: Build package for Ubuntu Hirsute, Impish and Jammy (@legoktm #431 #568) and remove Groovy
|
||||
- Fix compilation for FreeBSD (@swills g#432)
|
||||
- Many fixes and improvement (@MananJethwany, @maneeshpm, @veloman-yunkan, @mgautierfr)
|
||||
|
||||
kiwix-lib 9.4.1
|
||||
===============
|
||||
|
||||
12
README.md
12
README.md
@@ -91,6 +91,14 @@ Meson. If you want statically linked libraries, you can add
|
||||
|
||||
Depending of you system, `ninja` may be called `ninja-build`.
|
||||
|
||||
The android wrapper uses deprecated methods of libkiwix so it cannot be compiled
|
||||
with `werror=true` (the default). So you must pass `-Dwerror=false` to meson:
|
||||
|
||||
```bash
|
||||
meson . build -Dwrapper=android -Dwerror=false
|
||||
ninja -C build
|
||||
```
|
||||
|
||||
Testing
|
||||
-------
|
||||
|
||||
@@ -154,7 +162,7 @@ to use custom welcome page mention `customIndexPage` argument in `kiwix::interna
|
||||
|
||||
to create a HTML template with custom JS you need to have a look at various OPDS based endpoints as mentioned [here](https://wiki.kiwix.org/wiki/OPDS) to load books.
|
||||
|
||||
To use JS provided by kiwix-serve you can use the following template to start with ->
|
||||
To use JS provided by kiwix-serve you can use the following template to start with ->
|
||||
|
||||
```
|
||||
<!DOCTYPE html>
|
||||
@@ -184,7 +192,7 @@ To use JS provided by kiwix-serve you can use the following template to start wi
|
||||
- To get number of books listed add - `<h3 class="kiwixHomeBody__results"></h3>` under body tag.
|
||||
- To add language select box add - `<select id="languageFilter"></select>` under body tag.
|
||||
- To add language select box add - `<select id="categoryFilter"></select>` under body tag.
|
||||
- To add search box for books use following form -
|
||||
- To add search box for books use following form -
|
||||
```
|
||||
<form id='kiwixSearchForm'>
|
||||
<input type="text" name="q" placeholder="Search" id="searchFilter" class='kiwixSearch filter'>
|
||||
|
||||
@@ -26,7 +26,7 @@ task writePom {
|
||||
project {
|
||||
groupId 'org.kiwix.kiwixlib'
|
||||
artifactId 'kiwixlib'
|
||||
version '10.0.0' + (System.env.KIWIXLIB_BUILDVERSION == null ? '' : '-'+System.env.KIWIXLIB_BUILDVERSION)
|
||||
version '10.0.1' + (System.env.KIWIXLIB_BUILDVERSION == null ? '' : '-'+System.env.KIWIXLIB_BUILDVERSION)
|
||||
packaging 'aar'
|
||||
name 'kiwixlib'
|
||||
url 'https://github.com/kiwix/libkiwix'
|
||||
|
||||
4
debian/control
vendored
4
debian/control
vendored
@@ -4,7 +4,7 @@ Maintainer: Kiwix team <kiwix@kiwix.org>
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
meson,
|
||||
pkg-config,
|
||||
libzim-dev (>= 6.1.8),
|
||||
libzim-dev (>= 7.2.0~),
|
||||
libcurl4-gnutls-dev,
|
||||
libicu-dev,
|
||||
libgtest-dev,
|
||||
@@ -23,7 +23,7 @@ Section: libdevel
|
||||
Architecture: any
|
||||
Multi-Arch: same
|
||||
Depends: libkiwix10 (= ${binary:Version}), ${misc:Depends}, python3,
|
||||
libzim-dev (>= 6.0.0),
|
||||
libzim-dev (>= 7.2.0~),
|
||||
libicu-dev,
|
||||
libpugixml-dev,
|
||||
libcurl4-gnutls-dev,
|
||||
|
||||
2
docs/.gitignore
vendored
Normal file
2
docs/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
api
|
||||
xml
|
||||
72
docs/conf.py
Normal file
72
docs/conf.py
Normal file
@@ -0,0 +1,72 @@
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# This file only contains a selection of the most common options. For a full
|
||||
# list see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
# -- Path setup --------------------------------------------------------------
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#
|
||||
import os
|
||||
# import sys
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = 'libkiwix'
|
||||
copyright = '2022, libkiwix-team'
|
||||
author = 'libzim-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.
|
||||
extensions = [
|
||||
'breathe',
|
||||
'exhale'
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This pattern also affects html_static_path and html_extra_path.
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
|
||||
|
||||
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,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
breathe_projects = {
|
||||
"libkiwix": "./xml"
|
||||
}
|
||||
breathe_default_project = 'libkiwix'
|
||||
|
||||
exhale_args = {
|
||||
"containmentFolder": "./api",
|
||||
"rootFileName": "ref_api.rst",
|
||||
"rootFileTitle": "Reference API",
|
||||
"doxygenStripFromPath":"..",
|
||||
"treeViewIsBootstrap": True,
|
||||
"createTreeView" : True,
|
||||
"exhaleExecutesDoxygen": True,
|
||||
"exhaleDoxygenStdin": "INPUT = ../include"
|
||||
}
|
||||
|
||||
primary_domain = 'cpp'
|
||||
|
||||
highlight_language = 'cpp'
|
||||
14
docs/index.rst
Normal file
14
docs/index.rst
Normal file
@@ -0,0 +1,14 @@
|
||||
.. libkiwix documentation master file, created by
|
||||
sphinx-quickstart on Fri Jul 24 15:40:50 2020.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Welcome to libzim's documentation!
|
||||
==================================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Contents:
|
||||
|
||||
usage
|
||||
api/ref_api
|
||||
7
docs/meson.build
Normal file
7
docs/meson.build
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
sphinx = find_program('sphinx-build', native:true)
|
||||
|
||||
sphinx_target = run_target('doc',
|
||||
command: [sphinx, '-bhtml',
|
||||
meson.current_source_dir(),
|
||||
meson.current_build_dir()])
|
||||
3
docs/requirements.txt
Normal file
3
docs/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
breathe
|
||||
exhale
|
||||
sphinx<4
|
||||
17
docs/usage.rst
Normal file
17
docs/usage.rst
Normal file
@@ -0,0 +1,17 @@
|
||||
Libkiwix programming
|
||||
====================
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
libkiwix is written in C++. To use the library, you need the include files of libkiwix have
|
||||
to link against libzim.
|
||||
|
||||
Errors are handled with exceptions. When something goes wrong, libzim throws an error,
|
||||
which is always derived from std::exception.
|
||||
|
||||
All classes are defined in the namespace kiwix.
|
||||
|
||||
libkiwix is a set of tools to manage zim files and provide some common functionnality.
|
||||
While libkiwix has some wrappers around libzim classes, they are deprecated and will be removed
|
||||
in the future.
|
||||
@@ -21,6 +21,10 @@
|
||||
#define KIWIX_BOOK_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include "common.h"
|
||||
|
||||
namespace pugi {
|
||||
class xml_node;
|
||||
@@ -41,12 +45,31 @@ class Reader;
|
||||
*/
|
||||
class Book
|
||||
{
|
||||
public:
|
||||
public: // types
|
||||
class Illustration
|
||||
{
|
||||
friend class Book;
|
||||
public:
|
||||
uint16_t width = 48;
|
||||
uint16_t height = 48;
|
||||
std::string mimeType;
|
||||
std::string url;
|
||||
|
||||
const std::string& getData() const;
|
||||
|
||||
private:
|
||||
mutable std::string data;
|
||||
mutable std::mutex mutex;
|
||||
};
|
||||
|
||||
typedef std::vector<std::shared_ptr<const Illustration>> Illustrations;
|
||||
|
||||
public: // functions
|
||||
Book();
|
||||
~Book();
|
||||
|
||||
bool update(const Book& other);
|
||||
void update(const Reader& reader);
|
||||
DEPRECATED void update(const Reader& reader);
|
||||
void update(const zim::Archive& archive);
|
||||
void updateFromXml(const pugi::xml_node& node, const std::string& baseDir);
|
||||
void updateFromOpds(const pugi::xml_node& node, const std::string& urlHost);
|
||||
@@ -73,9 +96,13 @@ class Book
|
||||
const uint64_t& getArticleCount() const { return m_articleCount; }
|
||||
const uint64_t& getMediaCount() const { return m_mediaCount; }
|
||||
const uint64_t& getSize() const { return m_size; }
|
||||
const std::string& getFavicon() const;
|
||||
const std::string& getFaviconUrl() const { return m_faviconUrl; }
|
||||
const std::string& getFaviconMimeType() const { return m_faviconMimeType; }
|
||||
DEPRECATED const std::string& getFavicon() const;
|
||||
DEPRECATED const std::string& getFaviconUrl() const;
|
||||
DEPRECATED const std::string& getFaviconMimeType() const;
|
||||
|
||||
Illustrations getIllustrations() const;
|
||||
std::shared_ptr<const Illustration> getIllustration(unsigned int size) const;
|
||||
|
||||
const std::string& getDownloadId() const { return m_downloadId; }
|
||||
|
||||
void setReadOnly(bool readOnly) { m_readOnly = readOnly; }
|
||||
@@ -96,14 +123,13 @@ class Book
|
||||
void setArticleCount(uint64_t articleCount) { m_articleCount = articleCount; }
|
||||
void setMediaCount(uint64_t mediaCount) { m_mediaCount = mediaCount; }
|
||||
void setSize(uint64_t size) { m_size = size; }
|
||||
void setFavicon(const std::string& favicon) { m_favicon = favicon; }
|
||||
void setFaviconMimeType(const std::string& faviconMimeType) { m_faviconMimeType = faviconMimeType; }
|
||||
void setDownloadId(const std::string& downloadId) { m_downloadId = downloadId; }
|
||||
|
||||
private:
|
||||
private: // functions
|
||||
std::string getCategoryFromTags() const;
|
||||
const Illustration& getDefaultIllustration() const;
|
||||
|
||||
protected:
|
||||
protected: // data
|
||||
std::string m_id;
|
||||
std::string m_downloadId;
|
||||
std::string m_path;
|
||||
@@ -124,9 +150,11 @@ class Book
|
||||
uint64_t m_mediaCount = 0;
|
||||
bool m_readOnly = false;
|
||||
uint64_t m_size = 0;
|
||||
mutable std::string m_favicon;
|
||||
std::string m_faviconUrl;
|
||||
std::string m_faviconMimeType;
|
||||
Illustrations m_illustrations;
|
||||
|
||||
// Used as the return value of getDefaultIllustration() when no default
|
||||
// illustration is found in the book
|
||||
static const Illustration missingDefaultIllustration;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ class Entry
|
||||
*
|
||||
* @param article a zim::Article object
|
||||
*/
|
||||
Entry(zim::Entry entry);
|
||||
DEPRECATED Entry(zim::Entry entry) : Entry(entry, true) {};
|
||||
virtual ~Entry() = default;
|
||||
|
||||
/**
|
||||
@@ -176,6 +176,16 @@ class Entry
|
||||
|
||||
private:
|
||||
zim::Entry entry;
|
||||
|
||||
private:
|
||||
// Entry is deprecated, so we've marked the constructor as deprecated.
|
||||
// But we still need to construct the entry (in our deprecated code)
|
||||
// To avoid warning because we use deprecated function, we create a second
|
||||
// constructor not deprecated. The `bool marker` is unused, it sole purpose
|
||||
// is to change the signature to have two different constructor.
|
||||
// This one is not deprecated and we must use it in our private code.
|
||||
Entry(zim::Entry entry, bool marker);
|
||||
friend class Reader;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -22,4 +22,4 @@
|
||||
|
||||
#include "library.h"
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <zim/archive.h>
|
||||
|
||||
#include "book.h"
|
||||
@@ -139,20 +140,51 @@ private: // functions
|
||||
bool accept(const Book& book) const;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A Library store several books.
|
||||
* This class is not part of the libkiwix API. Its only purpose is
|
||||
* to simplify the implementation of the Library's move operations
|
||||
* and avoid bugs should new data members be added to Library.
|
||||
*/
|
||||
class Library
|
||||
class LibraryBase
|
||||
{
|
||||
std::map<std::string, kiwix::Book> m_books;
|
||||
protected: // types
|
||||
typedef uint64_t LibraryRevision;
|
||||
|
||||
struct Entry : Book
|
||||
{
|
||||
LibraryRevision lastUpdatedRevision = 0;
|
||||
|
||||
// May also keep the Archive and Reader pointers here and get
|
||||
// rid of the m_readers and m_archives data members in Library
|
||||
};
|
||||
|
||||
protected: // data
|
||||
LibraryRevision m_revision;
|
||||
std::map<std::string, Entry> m_books;
|
||||
std::map<std::string, std::shared_ptr<Reader>> m_readers;
|
||||
std::map<std::string, std::shared_ptr<zim::Archive>> m_archives;
|
||||
std::vector<kiwix::Bookmark> m_bookmarks;
|
||||
class BookDB;
|
||||
std::unique_ptr<BookDB> m_bookDB;
|
||||
|
||||
protected: // functions
|
||||
LibraryBase();
|
||||
~LibraryBase();
|
||||
|
||||
LibraryBase(LibraryBase&& );
|
||||
LibraryBase& operator=(LibraryBase&& );
|
||||
};
|
||||
|
||||
/**
|
||||
* A Library store several books.
|
||||
*/
|
||||
class Library : private LibraryBase
|
||||
{
|
||||
// all data fields must be added in LibraryBase
|
||||
mutable std::mutex m_mutex;
|
||||
|
||||
public:
|
||||
typedef LibraryRevision Revision;
|
||||
typedef std::vector<std::string> BookIdCollection;
|
||||
typedef std::map<std::string, int> AttributeCounts;
|
||||
|
||||
@@ -180,6 +212,11 @@ class Library
|
||||
*/
|
||||
bool addBook(const Book& book);
|
||||
|
||||
/**
|
||||
* A self-explanatory alias for addBook()
|
||||
*/
|
||||
bool addOrUpdateBook(const Book& book) { return addBook(book); }
|
||||
|
||||
/**
|
||||
* Add a bookmark to the library.
|
||||
*
|
||||
@@ -196,11 +233,14 @@ class Library
|
||||
*/
|
||||
bool removeBookmark(const std::string& zimId, const std::string& url);
|
||||
|
||||
// XXX: This is a non-thread-safe operation
|
||||
const Book& getBookById(const std::string& id) const;
|
||||
Book& getBookById(const std::string& id);
|
||||
// XXX: This is a non-thread-safe operation
|
||||
const Book& getBookByPath(const std::string& path) const;
|
||||
Book& getBookByPath(const std::string& path);
|
||||
std::shared_ptr<Reader> getReaderById(const std::string& id);
|
||||
|
||||
Book getBookByIdThreadSafe(const std::string& id) const;
|
||||
|
||||
DEPRECATED std::shared_ptr<Reader> getReaderById(const std::string& id);
|
||||
std::shared_ptr<zim::Archive> getArchiveById(const std::string& id);
|
||||
|
||||
/**
|
||||
@@ -285,17 +325,6 @@ class Library
|
||||
*/
|
||||
BookIdCollection getBooksIds() const;
|
||||
|
||||
/**
|
||||
* Filter the library and generate a new one with the keep elements.
|
||||
*
|
||||
* This is equivalent to `listBookIds(ALL, UNSORTED, search)`.
|
||||
*
|
||||
* @param search List only books with search in the title or description.
|
||||
* @return The list of bookIds corresponding to the query.
|
||||
*/
|
||||
DEPRECATED BookIdCollection filter(const std::string& search) const;
|
||||
|
||||
|
||||
/**
|
||||
* Filter the library and return the id of the keep elements.
|
||||
*
|
||||
@@ -315,36 +344,22 @@ class Library
|
||||
void sort(BookIdCollection& bookIds, supportedListSortBy sortBy, bool ascending) const;
|
||||
|
||||
/**
|
||||
* List books in the library.
|
||||
* Return the current revision of the library.
|
||||
*
|
||||
* @param mode The mode of listing :
|
||||
* - LOCAL : list only local books (with a path).
|
||||
* - REMOTE : list only remote books (with an url).
|
||||
* - VALID : list only valid books (without a path or with a
|
||||
* path pointing to a valid zim file).
|
||||
* - NOLOCAL : list only books without valid path.
|
||||
* - NOREMOTE : list only books without url.
|
||||
* - NOVALID : list only books not valid.
|
||||
* - ALL : Do not do any filter (LOCAL or REMOTE)
|
||||
* - Flags can be combined.
|
||||
* @param sortBy Attribute to sort by the book list.
|
||||
* @param search List only books with search in the title, description.
|
||||
* @param language List only books in this language.
|
||||
* @param creator List only books of this creator.
|
||||
* @param publisher List only books of this publisher.
|
||||
* @param maxSize Do not list book bigger than maxSize.
|
||||
* Set to 0 to cancel this filter.
|
||||
* @return The list of bookIds corresponding to the query.
|
||||
* The revision of the library is updated (incremented by one) only by
|
||||
* the addBook() operation.
|
||||
*
|
||||
* @return Current revision of the library.
|
||||
*/
|
||||
DEPRECATED BookIdCollection listBooksIds(
|
||||
int supportedListMode = ALL,
|
||||
supportedListSortBy sortBy = UNSORTED,
|
||||
const std::string& search = "",
|
||||
const std::string& language = "",
|
||||
const std::string& creator = "",
|
||||
const std::string& publisher = "",
|
||||
const std::vector<std::string>& tags = {},
|
||||
size_t maxSize = 0) const;
|
||||
LibraryRevision getRevision() const;
|
||||
|
||||
/**
|
||||
* Remove books that have not been updated since the specified revision.
|
||||
*
|
||||
* @param rev the library revision to use
|
||||
* @return Count of books that were removed by this operation.
|
||||
*/
|
||||
uint32_t removeBooksNotUpdatedSince(LibraryRevision rev);
|
||||
|
||||
friend class OPDSDumper;
|
||||
friend class libXMLDumper;
|
||||
@@ -357,6 +372,7 @@ private: // functions
|
||||
std::vector<std::string> getBookPropValueSet(BookStrPropMemFn p) const;
|
||||
BookIdCollection filterViaBookDB(const Filter& filter) const;
|
||||
void updateBookDB(const Book& book);
|
||||
void dropReader(const std::string& bookId);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace pugi {
|
||||
class xml_document;
|
||||
@@ -34,26 +35,25 @@ class xml_document;
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
class LibraryManipulator {
|
||||
public:
|
||||
virtual ~LibraryManipulator() {}
|
||||
virtual bool addBookToLibrary(Book book) = 0;
|
||||
virtual void addBookmarkToLibrary(Bookmark bookmark) = 0;
|
||||
};
|
||||
class LibraryManipulator
|
||||
{
|
||||
public: // functions
|
||||
explicit LibraryManipulator(Library* library);
|
||||
virtual ~LibraryManipulator();
|
||||
|
||||
class DefaultLibraryManipulator : public LibraryManipulator {
|
||||
public:
|
||||
DefaultLibraryManipulator(Library* library) :
|
||||
library(library) {}
|
||||
virtual ~DefaultLibraryManipulator() {}
|
||||
bool addBookToLibrary(Book book) {
|
||||
return library->addBook(book);
|
||||
}
|
||||
void addBookmarkToLibrary(Bookmark bookmark) {
|
||||
library->addBookmark(bookmark);
|
||||
}
|
||||
private:
|
||||
kiwix::Library* library;
|
||||
Library& getLibrary() const { return library; }
|
||||
|
||||
bool addBookToLibrary(const Book& book);
|
||||
void addBookmarkToLibrary(const Bookmark& bookmark);
|
||||
uint32_t removeBooksNotUpdatedSince(Library::Revision rev);
|
||||
|
||||
protected: // overrides
|
||||
virtual void bookWasAddedToLibrary(const Book& book);
|
||||
virtual void bookmarkWasAddedToLibrary(const Bookmark& bookmark);
|
||||
virtual void booksWereRemovedFromLibrary();
|
||||
|
||||
private: // data
|
||||
kiwix::Library& library;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -61,10 +61,12 @@ class DefaultLibraryManipulator : public LibraryManipulator {
|
||||
*/
|
||||
class Manager
|
||||
{
|
||||
public:
|
||||
Manager(LibraryManipulator* manipulator);
|
||||
Manager(Library* library);
|
||||
~Manager();
|
||||
public: // types
|
||||
typedef std::vector<std::string> Paths;
|
||||
|
||||
public: // functions
|
||||
explicit Manager(LibraryManipulator* manipulator);
|
||||
explicit Manager(Library* library);
|
||||
|
||||
/**
|
||||
* Read a `library.xml` and add book in the file to the library.
|
||||
@@ -72,10 +74,22 @@ class Manager
|
||||
* @param path The (utf8) path to the `library.xml`.
|
||||
* @param readOnly Set if the libray path could be overwritten latter with
|
||||
* updated content.
|
||||
* @param trustLibrary use book metadata coming from XML.
|
||||
* @return True if file has been properly parsed.
|
||||
*/
|
||||
bool readFile(const std::string& path, bool readOnly = true, bool trustLibrary = true);
|
||||
|
||||
/**
|
||||
* Sync the contents of the library with one or more `library.xml` files.
|
||||
*
|
||||
* The metadata of the library files is trusted unconditionally.
|
||||
* Any books not present in the input library.xml files are removed
|
||||
* from the library.
|
||||
*
|
||||
* @param paths The (utf8) paths to the `library.xml` files.
|
||||
*/
|
||||
void reload(const Paths& paths);
|
||||
|
||||
/**
|
||||
* Load a library content store in the string.
|
||||
*
|
||||
@@ -150,8 +164,7 @@ class Manager
|
||||
uint64_t m_itemsPerPage = 0;
|
||||
|
||||
protected:
|
||||
kiwix::LibraryManipulator* manipulator;
|
||||
bool mustDeleteManipulator;
|
||||
std::shared_ptr<kiwix::LibraryManipulator> manipulator;
|
||||
|
||||
bool readBookFromPath(const std::string& path, Book* book);
|
||||
bool parseXmlDom(const pugi::xml_document& doc,
|
||||
|
||||
@@ -14,7 +14,8 @@ headers = [
|
||||
'server.h',
|
||||
'kiwixserve.h',
|
||||
'name_mapper.h',
|
||||
'tools.h'
|
||||
'tools.h',
|
||||
'version.h'
|
||||
]
|
||||
|
||||
install_headers(headers, subdir:'kiwix')
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
@@ -31,15 +33,15 @@ class Library;
|
||||
class NameMapper {
|
||||
public:
|
||||
virtual ~NameMapper() = default;
|
||||
virtual std::string getNameForId(const std::string& id) = 0;
|
||||
virtual std::string getIdForName(const std::string& name) = 0;
|
||||
virtual std::string getNameForId(const std::string& id) const = 0;
|
||||
virtual std::string getIdForName(const std::string& name) const = 0;
|
||||
};
|
||||
|
||||
|
||||
class IdNameMapper : public NameMapper {
|
||||
public:
|
||||
virtual std::string getNameForId(const std::string& id) { return id; };
|
||||
virtual std::string getIdForName(const std::string& name) { return name; };
|
||||
virtual std::string getNameForId(const std::string& id) const { return id; };
|
||||
virtual std::string getIdForName(const std::string& name) const { return name; };
|
||||
};
|
||||
|
||||
class HumanReadableNameMapper : public NameMapper {
|
||||
@@ -50,11 +52,29 @@ class HumanReadableNameMapper : public NameMapper {
|
||||
public:
|
||||
HumanReadableNameMapper(kiwix::Library& library, bool withAlias);
|
||||
virtual ~HumanReadableNameMapper() = default;
|
||||
virtual std::string getNameForId(const std::string& id);
|
||||
virtual std::string getIdForName(const std::string& name);
|
||||
virtual std::string getNameForId(const std::string& id) const;
|
||||
virtual std::string getIdForName(const std::string& name) const;
|
||||
};
|
||||
|
||||
class UpdatableNameMapper : public NameMapper {
|
||||
typedef std::shared_ptr<NameMapper> NameMapperHandle;
|
||||
public:
|
||||
UpdatableNameMapper(Library& library, bool withAlias);
|
||||
|
||||
virtual std::string getNameForId(const std::string& id) const;
|
||||
virtual std::string getIdForName(const std::string& name) const;
|
||||
|
||||
void update();
|
||||
|
||||
private:
|
||||
NameMapperHandle currentNameMapper() const;
|
||||
|
||||
private:
|
||||
mutable std::mutex mutex;
|
||||
Library& library;
|
||||
NameMapperHandle nameMapper;
|
||||
const bool withAlias;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,6 @@ namespace kiwix
|
||||
class SuggestionItem
|
||||
{
|
||||
// Functions
|
||||
// Temporarily making the constructor public until the code move is complete
|
||||
public:
|
||||
// Create a sugggestion item.
|
||||
explicit SuggestionItem(const std::string& title, const std::string& normalizedTitle,
|
||||
@@ -66,13 +65,13 @@ class SuggestionItem
|
||||
std::string normalizedTitle;
|
||||
std::string path;
|
||||
std::string snippet;
|
||||
|
||||
friend class Reader;
|
||||
};
|
||||
|
||||
/**
|
||||
* The Reader class is the class who allow to get an entry content from a zim
|
||||
* file.
|
||||
*
|
||||
* Reader is now deprecated. Directly use `zim::Archive`.
|
||||
*/
|
||||
|
||||
using SuggestionsList_t = std::vector<SuggestionItem>;
|
||||
@@ -88,17 +87,18 @@ class Reader
|
||||
* unsplitted path as if the file were not splitted
|
||||
* (.zim extesion).
|
||||
*/
|
||||
explicit Reader(const string zimFilePath);
|
||||
explicit DEPRECATED Reader(const string zimFilePath);
|
||||
|
||||
/**
|
||||
* Create a Reader to read a zim file given by the Archive.
|
||||
*
|
||||
* @param archive The shared pointer to the Archive object.
|
||||
*/
|
||||
explicit Reader(const std::shared_ptr<zim::Archive> archive);
|
||||
explicit DEPRECATED Reader(const std::shared_ptr<zim::Archive> archive)
|
||||
: Reader(archive, true) {};
|
||||
#ifndef _WIN32
|
||||
explicit Reader(int fd);
|
||||
Reader(int fd, zim::offset_type offset, zim::size_type size);
|
||||
explicit DEPRECATED Reader(int fd);
|
||||
DEPRECATED Reader(int fd, zim::offset_type offset, zim::size_type size);
|
||||
#endif
|
||||
~Reader() = default;
|
||||
|
||||
@@ -491,6 +491,15 @@ class Reader
|
||||
|
||||
private:
|
||||
std::map<const std::string, unsigned int> parseCounterMetadata() const;
|
||||
|
||||
// Reader is deprecated, so we've marked the constructor as deprecated.
|
||||
// But we still need to construct the reader (in our deprecated code)
|
||||
// To avoid warning because we use deprecated function, we create a
|
||||
// constructor not deprecated. The `bool marker` is unused, it sole purpose
|
||||
// is to change the signature to have a different constructor.
|
||||
// This one is not deprecated and we must use it in our private code.
|
||||
Reader(const std::shared_ptr<zim::Archive> archive, bool marker);
|
||||
friend class Library;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -35,12 +35,24 @@ class SearchRenderer
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* The default constructor.
|
||||
* Construct a SearchRenderer from a Searcher.
|
||||
*
|
||||
* @param humanReadableName The global zim's humanReadableName.
|
||||
* Used to generate pagination links.
|
||||
* This method is now deprecated. Construct the renderer from a
|
||||
* `zim::SearchResultSet`
|
||||
*
|
||||
* @param searcher The `Searcher` to render.
|
||||
* @param mapper The `NameMapper` to use to do the rendering.
|
||||
*/
|
||||
DEPRECATED SearchRenderer(Searcher* searcher, NameMapper* mapper);
|
||||
|
||||
/**
|
||||
* Construct a SearchRenderer from a SearchResultSet.
|
||||
*
|
||||
* @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(Searcher* searcher, NameMapper* mapper);
|
||||
SearchRenderer(zim::SearchResultSet srs, NameMapper* mapper,
|
||||
unsigned int start, unsigned int estimatedResultCount);
|
||||
|
||||
|
||||
@@ -56,6 +56,8 @@ struct SuggestionInternal;
|
||||
/**
|
||||
* The Searcher class is reponsible to do different kind of search using the
|
||||
* fulltext index.
|
||||
*
|
||||
* The Searcher is now deprecated. Use libzim search feature.
|
||||
*/
|
||||
class Searcher
|
||||
{
|
||||
@@ -63,7 +65,7 @@ class Searcher
|
||||
/**
|
||||
* The default constructor.
|
||||
*/
|
||||
Searcher();
|
||||
DEPRECATED Searcher();
|
||||
|
||||
~Searcher();
|
||||
|
||||
|
||||
@@ -60,7 +60,9 @@ namespace kiwix
|
||||
{ m_withTaskbar = withTaskbar; m_withLibraryButton = withLibraryButton; }
|
||||
void setBlockExternalLinks(bool blockExternalLinks)
|
||||
{ m_blockExternalLinks = blockExternalLinks; }
|
||||
|
||||
int getPort();
|
||||
std::string getAddress();
|
||||
|
||||
protected:
|
||||
Library* mp_library;
|
||||
NameMapper* mp_nameMapper;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
namespace kiwix {
|
||||
|
||||
@@ -177,16 +178,25 @@ std::string converta2toa3(const std::string& a2code);
|
||||
*/
|
||||
std::string getFileContent(const std::string& path);
|
||||
|
||||
/** checks if file exists.
|
||||
/** Checks if file exists.
|
||||
*
|
||||
* This function returns boolean stating if file exists or not.
|
||||
* This function returns boolean stating if file exists.
|
||||
*
|
||||
* @param path The absolute path provided in string format.
|
||||
* @return Boolean representing if file exists or not.
|
||||
*/
|
||||
bool fileExists(const std::string& path);
|
||||
|
||||
/** provides mimetype from filename.
|
||||
/** Checks if file is readable.
|
||||
*
|
||||
* This function returns boolean stating if file is readable.
|
||||
*
|
||||
* @param path The absolute path provided in string format.
|
||||
* @return Boolean representing if file is readale or not.
|
||||
*/
|
||||
bool fileReadable(const std::string& path);
|
||||
|
||||
/** Provides mimetype from filename.
|
||||
*
|
||||
* This function provides mimetype from file-name.
|
||||
*
|
||||
@@ -194,5 +204,17 @@ bool fileExists(const std::string& path);
|
||||
* @return mimetype from filename in string format.
|
||||
*/
|
||||
std::string getMimeTypeForFile(const std::string& filename);
|
||||
|
||||
/** Provides all available network interfaces
|
||||
*
|
||||
* This function provides the available IPv4 network interfaces
|
||||
*/
|
||||
std::map<std::string, std::string> getNetworkInterfaces();
|
||||
|
||||
/** Provides the best IP address
|
||||
* This function provides the best IP address from the list given by getNetworkInterfaces
|
||||
*/
|
||||
std::string getBestPublicIp();
|
||||
|
||||
}
|
||||
#endif // KIWIX_TOOLS_H
|
||||
|
||||
33
include/version.h
Normal file
33
include/version.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2021 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 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 Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef KIWIX_VERSION_H
|
||||
#define KIWIX_VERSION_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
typedef std::vector<std::pair<std::string, std::string>> LibVersions;
|
||||
LibVersions getVersions();
|
||||
void printVersions(std::ostream& out = std::cout);
|
||||
}
|
||||
|
||||
#endif // KIWIX_VERSION_H
|
||||
@@ -1,5 +1,5 @@
|
||||
project('libkiwix', 'cpp',
|
||||
version : '10.0.0', # Also change this in android-kiwix-lib-publisher/kiwixLibAndroid/build.gradle
|
||||
version : '10.0.1', # Also change this in android-kiwix-lib-publisher/kiwixLibAndroid/build.gradle
|
||||
license : 'GPLv3+',
|
||||
default_options : ['c_std=c11', 'cpp_std=c++11', 'werror=true'])
|
||||
|
||||
@@ -44,7 +44,7 @@ else
|
||||
error('Cannot found header mustache.hpp')
|
||||
endif
|
||||
|
||||
libzim_dep = dependency('libzim', version : '>=7.0.0', static:static_deps)
|
||||
libzim_dep = dependency('libzim', version : '>=7.2.0', static:static_deps)
|
||||
if not compiler.has_header_symbol('zim/zim.h', 'LIBZIM_WITH_XAPIAN')
|
||||
error('Libzim seems to be compiled without xapian. Xapian support is mandatory.')
|
||||
endif
|
||||
@@ -65,7 +65,7 @@ all_deps = [thread_dep, libicu_dep, libzim_dep, pugixml_dep, libcurl_dep, microh
|
||||
inc = include_directories('include', extra_include)
|
||||
|
||||
conf = configuration_data()
|
||||
conf.set('VERSION', '"@0@"'.format(meson.project_version()))
|
||||
conf.set('LIBKIWIX_VERSION', '"@0@"'.format(meson.project_version()))
|
||||
|
||||
if build_machine.system() == 'windows'
|
||||
extra_link_args = ['-lshlwapi', '-lwinmm']
|
||||
@@ -78,6 +78,9 @@ subdir('scripts')
|
||||
subdir('static')
|
||||
subdir('src')
|
||||
subdir('test')
|
||||
if get_option('doc')
|
||||
subdir('docs')
|
||||
endif
|
||||
|
||||
pkg_requires = ['libzim', 'icu-i18n', 'pugixml', 'libcurl', 'libmicrohttpd', 'xapian-core']
|
||||
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
option('wrapper', type:'array', choices:['java', 'android'], value:[],
|
||||
description: 'The wrapper to generate.')
|
||||
option('doc', type : 'boolean', value : false,
|
||||
description : 'Build the documentations.')
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
#include "aria2.h"
|
||||
#include "xmlrpc.h"
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include "tools.h"
|
||||
#include "tools/pathTools.h"
|
||||
#include "tools/stringTools.h"
|
||||
#include "tools/otherTools.h"
|
||||
#include "downloader.h" // For AriaError
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -30,7 +32,7 @@ namespace kiwix {
|
||||
Aria2::Aria2():
|
||||
mp_aria(nullptr),
|
||||
m_port(42042),
|
||||
m_secret("kiwixariarpc"),
|
||||
m_secret(getNewRpcSecret()),
|
||||
m_curlErrorBuffer(new char[CURL_ERROR_SIZE]),
|
||||
mp_curl(nullptr)
|
||||
{
|
||||
@@ -68,7 +70,7 @@ Aria2::Aria2():
|
||||
callCmd.push_back(rpc_secret.c_str());
|
||||
callCmd.push_back(rpc_port.c_str());
|
||||
callCmd.push_back(download_dir.c_str());
|
||||
if (fileExists(session_file)) {
|
||||
if (fileReadable(session_file)) {
|
||||
callCmd.push_back(inputFile.c_str());
|
||||
}
|
||||
callCmd.push_back(session.c_str());
|
||||
@@ -195,6 +197,13 @@ std::string Aria2::tellStatus(const std::string& gid, const std::vector<std::str
|
||||
return doRequest(methodCall);
|
||||
}
|
||||
|
||||
std::string Aria2::getNewRpcSecret()
|
||||
{
|
||||
std::string uuid = gen_uuid("");
|
||||
uuid.erase(std::remove(uuid.begin(), uuid.end(), '-'));
|
||||
return uuid.substr(0, 9);
|
||||
}
|
||||
|
||||
std::vector<std::string> Aria2::tellActive()
|
||||
{
|
||||
MethodCall methodCall("aria2.tellActive", m_secret);
|
||||
|
||||
@@ -37,6 +37,7 @@ class Aria2
|
||||
|
||||
std::string addUri(const std::vector<std::string>& uri, const std::vector<std::pair<std::string, std::string>>& options = {});
|
||||
std::string tellStatus(const std::string& gid, const std::vector<std::string>& statusKey);
|
||||
static std::string getNewRpcSecret();
|
||||
std::vector<std::string> tellActive();
|
||||
std::vector<std::string> tellWaiting();
|
||||
void saveSession();
|
||||
|
||||
117
src/book.cpp
117
src/book.cpp
@@ -41,11 +41,17 @@ Book::Book() :
|
||||
m_readOnly(false)
|
||||
{
|
||||
}
|
||||
|
||||
/* Destructor */
|
||||
Book::~Book()
|
||||
{
|
||||
}
|
||||
|
||||
Book::Illustrations Book::getIllustrations() const
|
||||
{
|
||||
return m_illustrations;
|
||||
}
|
||||
|
||||
bool Book::update(const kiwix::Book& other)
|
||||
{
|
||||
if (m_readOnly)
|
||||
@@ -54,30 +60,7 @@ bool Book::update(const kiwix::Book& other)
|
||||
if (m_id != other.m_id)
|
||||
return false;
|
||||
|
||||
m_readOnly = other.m_readOnly;
|
||||
m_path = other.m_path;
|
||||
m_pathValid = other.m_pathValid;
|
||||
m_title = other.m_title;
|
||||
m_description = other.m_description;
|
||||
m_language = other.m_language;
|
||||
m_creator = other.m_creator;
|
||||
m_publisher = other.m_publisher;
|
||||
m_date = other.m_date;
|
||||
m_url = other.m_url;
|
||||
m_name = other.m_name;
|
||||
m_flavour = other.m_flavour;
|
||||
m_tags = other.m_tags;
|
||||
m_category = other.m_category;
|
||||
m_origId = other.m_origId;
|
||||
m_articleCount = other.m_articleCount;
|
||||
m_mediaCount = other.m_mediaCount;
|
||||
m_size = other.m_size;
|
||||
m_favicon = other.m_favicon;
|
||||
m_faviconMimeType = other.m_faviconMimeType;
|
||||
m_faviconUrl = other.m_faviconUrl;
|
||||
|
||||
m_downloadId = other.m_downloadId;
|
||||
|
||||
*this = other;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -104,7 +87,16 @@ void Book::update(const zim::Archive& archive) {
|
||||
m_mediaCount = getArchiveMediaCount(archive);
|
||||
m_size = static_cast<uint64_t>(getArchiveFileSize(archive)) << 10;
|
||||
|
||||
getArchiveFavicon(archive, 48, m_favicon, m_faviconMimeType);
|
||||
m_illustrations.clear();
|
||||
for ( const auto illustrationSize : archive.getIllustrationSizes() ) {
|
||||
const auto illustration = std::make_shared<Illustration>();
|
||||
const zim::Item illustrationItem = archive.getIllustrationItem(illustrationSize);
|
||||
illustration->width = illustration->height = illustrationSize;
|
||||
illustration->mimeType = illustrationItem.getMimetype();
|
||||
illustration->data = illustrationItem.getData();
|
||||
// NOTE: illustration->url is left uninitialized
|
||||
m_illustrations.push_back(illustration);
|
||||
}
|
||||
}
|
||||
|
||||
#define ATTR(name) node.attribute(name).value()
|
||||
@@ -116,7 +108,7 @@ void Book::updateFromXml(const pugi::xml_node& node, const std::string& baseDir)
|
||||
path = computeAbsolutePath(baseDir, path);
|
||||
}
|
||||
m_path = path;
|
||||
m_pathValid = fileExists(path);
|
||||
m_pathValid = fileReadable(path);
|
||||
m_title = ATTR("title");
|
||||
m_description = ATTR("description");
|
||||
m_language = ATTR("language");
|
||||
@@ -131,9 +123,14 @@ void Book::updateFromXml(const pugi::xml_node& node, const std::string& baseDir)
|
||||
m_articleCount = strtoull(ATTR("articleCount"), 0, 0);
|
||||
m_mediaCount = strtoull(ATTR("mediaCount"), 0, 0);
|
||||
m_size = strtoull(ATTR("size"), 0, 0) << 10;
|
||||
m_favicon = base64_decode(ATTR("favicon"));
|
||||
m_faviconMimeType = ATTR("faviconMimeType");
|
||||
m_faviconUrl = ATTR("faviconUrl");
|
||||
std::string favicon_mimetype = ATTR("faviconMimeType");
|
||||
if (! favicon_mimetype.empty()) {
|
||||
const auto favicon = std::make_shared<Illustration>();
|
||||
favicon->data = base64_decode(ATTR("favicon"));
|
||||
favicon->mimeType = favicon_mimetype;
|
||||
favicon->url = ATTR("faviconUrl");
|
||||
m_illustrations.assign(1, favicon);
|
||||
}
|
||||
try {
|
||||
m_downloadId = ATTR("downloadId");
|
||||
} catch(...) {}
|
||||
@@ -181,8 +178,11 @@ void Book::updateFromOpds(const pugi::xml_node& node, const std::string& urlHost
|
||||
m_size = strtoull(linkNode.attribute("length").value(), 0, 0);
|
||||
}
|
||||
if (rel == "http://opds-spec.org/image/thumbnail") {
|
||||
m_faviconUrl = urlHost + linkNode.attribute("href").value();
|
||||
m_faviconMimeType = linkNode.attribute("type").value();
|
||||
const auto favicon = std::make_shared<Illustration>();
|
||||
favicon->data.clear();
|
||||
favicon->url = urlHost + linkNode.attribute("href").value();
|
||||
favicon->mimeType = linkNode.attribute("type").value();
|
||||
m_illustrations.assign(1, favicon);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@ std::string Book::getHumanReadableIdFromPath() const
|
||||
{
|
||||
std::string id = m_path;
|
||||
if (!id.empty()) {
|
||||
kiwix::removeAccents(id);
|
||||
id = kiwix::removeAccents(id);
|
||||
|
||||
#ifdef _WIN32
|
||||
id = replaceRegex(id, "", "^.*\\\\");
|
||||
@@ -215,15 +215,54 @@ void Book::setPath(const std::string& path)
|
||||
: path;
|
||||
}
|
||||
|
||||
const std::string& Book::getFavicon() const {
|
||||
if (m_favicon.empty() && !m_faviconUrl.empty()) {
|
||||
try {
|
||||
m_favicon = download(m_faviconUrl);
|
||||
} catch(...) {
|
||||
std::cerr << "Cannot download favicon from " << m_faviconUrl;
|
||||
const Book::Illustration Book::missingDefaultIllustration;
|
||||
|
||||
std::shared_ptr<const Book::Illustration> Book::getIllustration(unsigned int size) const
|
||||
{
|
||||
for ( const auto& ilPtr : m_illustrations ) {
|
||||
if (ilPtr->width == size && ilPtr->height == size) {
|
||||
return ilPtr;
|
||||
}
|
||||
}
|
||||
return m_favicon;
|
||||
throw std::runtime_error("Cannot find illustration");
|
||||
}
|
||||
|
||||
const Book::Illustration& Book::getDefaultIllustration() const
|
||||
{
|
||||
try {
|
||||
return *getIllustration(48);
|
||||
} catch (...) {
|
||||
return missingDefaultIllustration;
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& Book::Illustration::getData() const
|
||||
{
|
||||
if (data.empty() && !url.empty()) {
|
||||
const std::lock_guard<std::mutex> l(mutex);
|
||||
if ( data.empty() ) {
|
||||
try {
|
||||
data = download(url);
|
||||
} catch(...) {
|
||||
std::cerr << "Cannot download favicon from " << url;
|
||||
}
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
const std::string& Book::getFavicon() const {
|
||||
return getDefaultIllustration().getData();
|
||||
}
|
||||
|
||||
const std::string& Book::getFaviconUrl() const
|
||||
{
|
||||
return getDefaultIllustration().url;
|
||||
}
|
||||
|
||||
const std::string& Book::getFaviconMimeType() const
|
||||
{
|
||||
return getDefaultIllustration().mimeType;
|
||||
}
|
||||
|
||||
std::string Book::getTagStr(const std::string& tagName) const {
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
|
||||
#mesondefine VERSION
|
||||
#mesondefine LIBKIWIX_VERSION
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
Entry::Entry(zim::Entry entry)
|
||||
Entry::Entry(zim::Entry entry, bool _marker)
|
||||
: entry(entry)
|
||||
{
|
||||
}
|
||||
@@ -53,7 +53,7 @@ Entry Entry::getRedirectEntry() const
|
||||
throw NoEntry();
|
||||
}
|
||||
|
||||
return entry.getRedirectEntry();
|
||||
return Entry(entry.getRedirectEntry(), true);
|
||||
}
|
||||
|
||||
Entry Entry::getFinalEntry() const
|
||||
@@ -67,7 +67,7 @@ Entry Entry::getFinalEntry() const
|
||||
if (final_entry.isRedirect()) {
|
||||
throw NoEntry();
|
||||
}
|
||||
return final_entry;
|
||||
return Entry(final_entry, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
198
src/library.cpp
198
src/library.cpp
@@ -49,23 +49,48 @@ std::string normalizeText(const std::string& text)
|
||||
return removeAccents(text);
|
||||
}
|
||||
|
||||
bool booksReferToTheSameArchive(const Book& book1, const Book& book2)
|
||||
{
|
||||
return book1.isPathValid()
|
||||
&& book2.isPathValid()
|
||||
&& book1.getPath() == book2.getPath();
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
class Library::BookDB : public Xapian::WritableDatabase
|
||||
class LibraryBase::BookDB : public Xapian::WritableDatabase
|
||||
{
|
||||
public:
|
||||
BookDB() : Xapian::WritableDatabase("", Xapian::DB_BACKEND_INMEMORY) {}
|
||||
};
|
||||
|
||||
/* Constructor */
|
||||
Library::Library()
|
||||
LibraryBase::LibraryBase()
|
||||
: m_bookDB(new BookDB)
|
||||
{
|
||||
}
|
||||
|
||||
Library::Library(Library&& ) = default;
|
||||
LibraryBase::~LibraryBase()
|
||||
{
|
||||
}
|
||||
|
||||
Library& Library::operator=(Library&& ) = default;
|
||||
LibraryBase::LibraryBase(LibraryBase&& ) = default;
|
||||
LibraryBase& LibraryBase::operator=(LibraryBase&& ) = default;
|
||||
|
||||
/* Constructor */
|
||||
Library::Library()
|
||||
{
|
||||
}
|
||||
|
||||
Library::Library(Library&& other)
|
||||
: LibraryBase(std::move(other))
|
||||
{
|
||||
}
|
||||
|
||||
Library& Library::operator=(Library&& other)
|
||||
{
|
||||
LibraryBase::operator=(std::move(other));
|
||||
return *this;
|
||||
}
|
||||
|
||||
/* Destructor */
|
||||
Library::~Library()
|
||||
@@ -75,25 +100,37 @@ Library::~Library()
|
||||
|
||||
bool Library::addBook(const Book& book)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
++m_revision;
|
||||
/* Try to find it */
|
||||
updateBookDB(book);
|
||||
try {
|
||||
auto& oldbook = m_books.at(book.getId());
|
||||
oldbook.update(book);
|
||||
if ( ! booksReferToTheSameArchive(oldbook, book) ) {
|
||||
dropReader(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;
|
||||
return false;
|
||||
} catch (std::out_of_range&) {
|
||||
m_books[book.getId()] = book;
|
||||
Entry& newEntry = m_books[book.getId()];
|
||||
static_cast<Book&>(newEntry) = book;
|
||||
newEntry.lastUpdatedRevision = m_revision;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void Library::addBookmark(const Bookmark& bookmark)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
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++) {
|
||||
if (it->getBookId() == zimId && it->getUrl() == url) {
|
||||
m_bookmarks.erase(it);
|
||||
@@ -104,28 +141,63 @@ bool Library::removeBookmark(const std::string& zimId, const std::string& url)
|
||||
}
|
||||
|
||||
|
||||
void Library::dropReader(const std::string& id)
|
||||
{
|
||||
m_readers.erase(id);
|
||||
m_archives.erase(id);
|
||||
}
|
||||
|
||||
bool Library::removeBookById(const std::string& id)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_bookDB->delete_document("Q" + id);
|
||||
m_readers.erase(id);
|
||||
m_archives.erase(id);
|
||||
dropReader(id);
|
||||
return m_books.erase(id) == 1;
|
||||
}
|
||||
|
||||
Library::Revision Library::getRevision() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return m_revision;
|
||||
}
|
||||
|
||||
uint32_t Library::removeBooksNotUpdatedSince(LibraryRevision libraryRevision)
|
||||
{
|
||||
BookIdCollection booksToRemove;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
for ( const auto& entry : m_books) {
|
||||
if ( entry.second.lastUpdatedRevision <= libraryRevision ) {
|
||||
booksToRemove.push_back(entry.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t countOfRemovedBooks = 0;
|
||||
for ( const auto& id : booksToRemove ) {
|
||||
if ( removeBookById(id) )
|
||||
++countOfRemovedBooks;
|
||||
}
|
||||
return countOfRemovedBooks;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
Book& Library::getBookById(const std::string& id)
|
||||
Book Library::getBookByIdThreadSafe(const std::string& id) const
|
||||
{
|
||||
const Library& const_self = *this;
|
||||
return const_cast<Book&>(const_self.getBookById(id));
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return getBookById(id);
|
||||
}
|
||||
|
||||
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) {
|
||||
auto& book = it.second;
|
||||
if (book.getPath() == path)
|
||||
@@ -136,37 +208,26 @@ const Book& Library::getBookByPath(const std::string& path) const
|
||||
throw std::out_of_range(ss.str());
|
||||
}
|
||||
|
||||
Book& Library::getBookByPath(const std::string& path)
|
||||
{
|
||||
const Library& const_self = *this;
|
||||
return const_cast<Book&>(const_self.getBookByPath(path));
|
||||
}
|
||||
|
||||
std::shared_ptr<Reader> Library::getReaderById(const std::string& id)
|
||||
{
|
||||
try {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return m_readers.at(id);
|
||||
} catch (std::out_of_range& e) {}
|
||||
|
||||
try {
|
||||
auto reader = make_shared<Reader>(m_archives.at(id));
|
||||
m_readers[id] = reader;
|
||||
return reader;
|
||||
} catch (std::out_of_range& e) {}
|
||||
|
||||
auto book = getBookById(id);
|
||||
if (!book.isPathValid())
|
||||
const auto archive = getArchiveById(id);
|
||||
if ( !archive )
|
||||
return nullptr;
|
||||
|
||||
auto archive = make_shared<zim::Archive>(book.getPath());
|
||||
m_archives[id] = archive;
|
||||
auto reader = make_shared<Reader>(archive);
|
||||
const shared_ptr<Reader> reader(new Reader(archive, true));
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_readers[id] = reader;
|
||||
return reader;
|
||||
}
|
||||
|
||||
std::shared_ptr<zim::Archive> Library::getArchiveById(const std::string& id)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
try {
|
||||
return m_archives.at(id);
|
||||
} catch (std::out_of_range& e) {}
|
||||
@@ -183,6 +244,7 @@ std::shared_ptr<zim::Archive> Library::getArchiveById(const std::string& id)
|
||||
unsigned int Library::getBookCount(const bool localBooks,
|
||||
const bool remoteBooks) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
unsigned int result = 0;
|
||||
for (auto& pair: m_books) {
|
||||
auto& book = pair.second;
|
||||
@@ -196,20 +258,30 @@ unsigned int Library::getBookCount(const bool localBooks,
|
||||
|
||||
bool Library::writeToFile(const std::string& path) const
|
||||
{
|
||||
const auto allBookIds = getBooksIds();
|
||||
|
||||
auto baseDir = removeLastPathElement(path);
|
||||
LibXMLDumper dumper(this);
|
||||
dumper.setBaseDir(baseDir);
|
||||
return writeTextFile(path, dumper.dumpLibXMLContent(getBooksIds()));
|
||||
std::string xml;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
xml = dumper.dumpLibXMLContent(allBookIds);
|
||||
};
|
||||
return writeTextFile(path, xml);
|
||||
}
|
||||
|
||||
bool Library::writeBookmarksToFile(const std::string& path) const
|
||||
{
|
||||
LibXMLDumper dumper(this);
|
||||
return writeTextFile(path, dumper.dumpLibXMLBookmark());
|
||||
// NOTE: LibXMLDumper::dumpLibXMLBookmark uses Library in a thread-safe way
|
||||
const std::string xml = dumper.dumpLibXMLBookmark();
|
||||
return writeTextFile(path, xml);
|
||||
}
|
||||
|
||||
Library::AttributeCounts Library::getBookAttributeCounts(BookStrPropMemFn p) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
AttributeCounts propValueCounts;
|
||||
|
||||
for (const auto& pair: m_books) {
|
||||
@@ -242,6 +314,7 @@ Library::AttributeCounts Library::getBooksLanguagesWithCounts() const
|
||||
|
||||
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) {
|
||||
@@ -272,6 +345,7 @@ const std::vector<kiwix::Bookmark> Library::getBookmarks(bool onlyValidBookmarks
|
||||
}
|
||||
std::vector<kiwix::Bookmark> validBookmarks;
|
||||
auto booksId = getBooksIds();
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
for(auto& bookmark:m_bookmarks) {
|
||||
if (std::find(booksId.begin(), booksId.end(), bookmark.getBookId()) != booksId.end()) {
|
||||
validBookmarks.push_back(bookmark);
|
||||
@@ -282,6 +356,7 @@ const std::vector<kiwix::Bookmark> Library::getBookmarks(bool onlyValidBookmarks
|
||||
|
||||
Library::BookIdCollection Library::getBooksIds() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
BookIdCollection bookIds;
|
||||
|
||||
for (auto& pair: m_books) {
|
||||
@@ -291,15 +366,6 @@ Library::BookIdCollection Library::getBooksIds() const
|
||||
return bookIds;
|
||||
}
|
||||
|
||||
Library::BookIdCollection Library::filter(const std::string& search) const
|
||||
{
|
||||
if (search.empty()) {
|
||||
return getBooksIds();
|
||||
}
|
||||
|
||||
return filter(Filter().query(search));
|
||||
}
|
||||
|
||||
|
||||
void Library::updateBookDB(const Book& book)
|
||||
{
|
||||
@@ -471,6 +537,7 @@ Library::BookIdCollection Library::filterViaBookDB(const Filter& filter) const
|
||||
|
||||
BookIdCollection bookIds;
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
Xapian::Enquire enquire(*m_bookDB);
|
||||
enquire.set_query(query);
|
||||
const auto results = enquire.get_mset(0, m_books.size());
|
||||
@@ -484,7 +551,9 @@ Library::BookIdCollection Library::filterViaBookDB(const Filter& filter) const
|
||||
Library::BookIdCollection Library::filter(const Filter& filter) const
|
||||
{
|
||||
BookIdCollection result;
|
||||
for(auto id : filterViaBookDB(filter)) {
|
||||
const auto preliminaryResult = filterViaBookDB(filter);
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
for(auto id : preliminaryResult) {
|
||||
if(filter.accept(m_books.at(id))) {
|
||||
result.push_back(id);
|
||||
}
|
||||
@@ -553,6 +622,11 @@ std::string Comparator<PUBLISHER>::get_key(const std::string& id)
|
||||
|
||||
void Library::sort(BookIdCollection& bookIds, supportedListSortBy sort, bool ascending) const
|
||||
{
|
||||
// NOTE: Can reimplement this method in a way that doesn't require locking
|
||||
// NOTE: for the entire duration of the sort. Will need to obtain (under a
|
||||
// NOTE: lock) the required atributes from the books once, and then the
|
||||
// NOTE: sorting will run on a copy of data without locking.
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
switch(sort) {
|
||||
case TITLE:
|
||||
std::sort(bookIds.begin(), bookIds.end(), Comparator<TITLE>(this, ascending));
|
||||
@@ -575,48 +649,6 @@ void Library::sort(BookIdCollection& bookIds, supportedListSortBy sort, bool asc
|
||||
}
|
||||
|
||||
|
||||
Library::BookIdCollection Library::listBooksIds(
|
||||
int mode,
|
||||
supportedListSortBy sortBy,
|
||||
const std::string& search,
|
||||
const std::string& language,
|
||||
const std::string& creator,
|
||||
const std::string& publisher,
|
||||
const std::vector<std::string>& tags,
|
||||
size_t maxSize) const {
|
||||
|
||||
Filter _filter;
|
||||
if (mode & LOCAL)
|
||||
_filter.local(true);
|
||||
if (mode & NOLOCAL)
|
||||
_filter.local(false);
|
||||
if (mode & VALID)
|
||||
_filter.valid(true);
|
||||
if (mode & NOVALID)
|
||||
_filter.valid(false);
|
||||
if (mode & REMOTE)
|
||||
_filter.remote(true);
|
||||
if (mode & NOREMOTE)
|
||||
_filter.remote(false);
|
||||
if (!tags.empty())
|
||||
_filter.acceptTags(tags);
|
||||
if (maxSize != 0)
|
||||
_filter.maxSize(maxSize);
|
||||
if (!language.empty())
|
||||
_filter.lang(language);
|
||||
if (!publisher.empty())
|
||||
_filter.publisher(publisher);
|
||||
if (!creator.empty())
|
||||
_filter.creator(creator);
|
||||
if (!search.empty())
|
||||
_filter.query(search);
|
||||
|
||||
auto bookIds = filter(_filter);
|
||||
|
||||
sort(bookIds, sortBy, true);
|
||||
return bookIds;
|
||||
}
|
||||
|
||||
Filter::Filter()
|
||||
: activeFilters(0),
|
||||
_maxSize(0)
|
||||
|
||||
@@ -60,10 +60,13 @@ void LibXMLDumper::handleBook(Book book, pugi::xml_node root_node) {
|
||||
ADD_ATTR_NOT_EMPTY(entry_node, "name", book.getName());
|
||||
ADD_ATTR_NOT_EMPTY(entry_node, "flavour", book.getFlavour());
|
||||
ADD_ATTR_NOT_EMPTY(entry_node, "tags", book.getTags());
|
||||
ADD_ATTR_NOT_EMPTY(entry_node, "faviconMimeType", book.getFaviconMimeType());
|
||||
ADD_ATTR_NOT_EMPTY(entry_node, "faviconUrl", book.getFaviconUrl());
|
||||
if (!book.getFavicon().empty())
|
||||
ADD_ATTRIBUTE(entry_node, "favicon", base64_encode(book.getFavicon()));
|
||||
try {
|
||||
auto defaultIllustration = book.getIllustration(48);
|
||||
ADD_ATTR_NOT_EMPTY(entry_node, "faviconMimeType", defaultIllustration->mimeType);
|
||||
ADD_ATTR_NOT_EMPTY(entry_node, "faviconUrl", defaultIllustration->url);
|
||||
if (!defaultIllustration->getData().empty())
|
||||
ADD_ATTRIBUTE(entry_node, "favicon", base64_encode(defaultIllustration->getData()));
|
||||
} catch(...) {}
|
||||
} else {
|
||||
ADD_ATTRIBUTE(entry_node, "origId", book.getOrigId());
|
||||
}
|
||||
@@ -91,7 +94,7 @@ void LibXMLDumper::handleBookmark(Bookmark bookmark, pugi::xml_node root_node) {
|
||||
auto book_node = entry_node.append_child("book");
|
||||
|
||||
try {
|
||||
auto book = library->getBookById(bookmark.getBookId());
|
||||
auto book = library->getBookByIdThreadSafe(bookmark.getBookId());
|
||||
ADD_TEXT_ENTRY(book_node, "id", book.getId());
|
||||
ADD_TEXT_ENTRY(book_node, "title", book.getTitle());
|
||||
ADD_TEXT_ENTRY(book_node, "language", book.getLanguage());
|
||||
|
||||
@@ -26,28 +26,81 @@
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct NoDelete
|
||||
{
|
||||
template<class T> void operator()(T*) {}
|
||||
};
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// LibraryManipulator
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
LibraryManipulator::LibraryManipulator(Library* library)
|
||||
: library(*library)
|
||||
{}
|
||||
|
||||
LibraryManipulator::~LibraryManipulator()
|
||||
{}
|
||||
|
||||
bool LibraryManipulator::addBookToLibrary(const Book& book)
|
||||
{
|
||||
const auto ret = library.addBook(book);
|
||||
if ( ret ) {
|
||||
bookWasAddedToLibrary(book);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void LibraryManipulator::addBookmarkToLibrary(const Bookmark& bookmark)
|
||||
{
|
||||
library.addBookmark(bookmark);
|
||||
bookmarkWasAddedToLibrary(bookmark);
|
||||
}
|
||||
|
||||
uint32_t LibraryManipulator::removeBooksNotUpdatedSince(Library::Revision rev)
|
||||
{
|
||||
const auto n = library.removeBooksNotUpdatedSince(rev);
|
||||
if ( n != 0 ) {
|
||||
booksWereRemovedFromLibrary();
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
void LibraryManipulator::bookWasAddedToLibrary(const Book& book)
|
||||
{
|
||||
}
|
||||
|
||||
void LibraryManipulator::bookmarkWasAddedToLibrary(const Bookmark& bookmark)
|
||||
{
|
||||
}
|
||||
|
||||
void LibraryManipulator::booksWereRemovedFromLibrary()
|
||||
{
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Manager
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/* Constructor */
|
||||
Manager::Manager(LibraryManipulator* manipulator):
|
||||
writableLibraryPath(""),
|
||||
manipulator(manipulator),
|
||||
mustDeleteManipulator(false)
|
||||
manipulator(manipulator, NoDelete())
|
||||
{
|
||||
}
|
||||
|
||||
Manager::Manager(Library* library) :
|
||||
writableLibraryPath(""),
|
||||
manipulator(new DefaultLibraryManipulator(library)),
|
||||
mustDeleteManipulator(true)
|
||||
manipulator(new LibraryManipulator(library))
|
||||
{
|
||||
}
|
||||
|
||||
/* Destructor */
|
||||
Manager::~Manager()
|
||||
{
|
||||
if (mustDeleteManipulator) {
|
||||
delete manipulator;
|
||||
}
|
||||
}
|
||||
bool Manager::parseXmlDom(const pugi::xml_document& doc,
|
||||
bool readOnly,
|
||||
const std::string& libraryPath,
|
||||
@@ -249,4 +302,21 @@ bool Manager::readBookmarkFile(const std::string& path)
|
||||
return true;
|
||||
}
|
||||
|
||||
void Manager::reload(const Paths& paths)
|
||||
{
|
||||
const auto libRevision = manipulator->getLibrary().getRevision();
|
||||
for (std::string path : paths) {
|
||||
if (!path.empty()) {
|
||||
if ( kiwix::isRelativePath(path) )
|
||||
path = kiwix::computeAbsolutePath(kiwix::getCurrentDirectory(), path);
|
||||
|
||||
if (!readFile(path, false, true)) {
|
||||
throw std::runtime_error("Failed to load the XML library file '" + path + "'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
manipulator->removeBooksNotUpdatedSince(libRevision);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,7 +28,8 @@ kiwix_sources = [
|
||||
'server/response.cpp',
|
||||
'server/internalServer.cpp',
|
||||
'server/internalServer_catalog_v2.cpp',
|
||||
'opds_catalog.cpp'
|
||||
'opds_catalog.cpp',
|
||||
'version.cpp'
|
||||
]
|
||||
kiwix_sources += lib_resources
|
||||
|
||||
|
||||
@@ -51,12 +51,54 @@ HumanReadableNameMapper::HumanReadableNameMapper(kiwix::Library& library, bool w
|
||||
}
|
||||
}
|
||||
|
||||
std::string HumanReadableNameMapper::getNameForId(const std::string& id) {
|
||||
std::string HumanReadableNameMapper::getNameForId(const std::string& id) const {
|
||||
return m_idToName.at(id);
|
||||
}
|
||||
|
||||
std::string HumanReadableNameMapper::getIdForName(const std::string& name) {
|
||||
std::string HumanReadableNameMapper::getIdForName(const std::string& name) const {
|
||||
return m_nameToId.at(name);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// UpdatableNameMapper
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
UpdatableNameMapper::UpdatableNameMapper(Library& lib, bool withAlias)
|
||||
: library(lib)
|
||||
, withAlias(withAlias)
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
void UpdatableNameMapper::update()
|
||||
{
|
||||
const auto newNameMapper = new HumanReadableNameMapper(library, withAlias);
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
nameMapper.reset(newNameMapper);
|
||||
}
|
||||
|
||||
UpdatableNameMapper::NameMapperHandle
|
||||
UpdatableNameMapper::currentNameMapper() const
|
||||
{
|
||||
// Return a copy of the handle to the current NameMapper object. It will
|
||||
// ensure that the object survives any call to UpdatableNameMapper::update()
|
||||
// made before the completion of any pending operation on that object.
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
return nameMapper;
|
||||
}
|
||||
|
||||
std::string UpdatableNameMapper::getNameForId(const std::string& id) const
|
||||
{
|
||||
// Ensure that the current nameMapper object survives a concurrent call
|
||||
// to UpdatableNameMapper::update()
|
||||
return currentNameMapper()->getNameForId(id);
|
||||
}
|
||||
|
||||
std::string UpdatableNameMapper::getIdForName(const std::string& name) const
|
||||
{
|
||||
// Ensure that the current nameMapper object survives a concurrent call
|
||||
// to UpdatableNameMapper::update()
|
||||
return currentNameMapper()->getIdForName(name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -58,11 +58,12 @@ IllustrationInfo getBookIllustrationInfo(const Book& book)
|
||||
{
|
||||
kainjow::mustache::list illustrations;
|
||||
if ( book.isPathValid() ) {
|
||||
for ( auto illustration_size : zim::Archive(book.getPath()).getIllustrationSizes() ) {
|
||||
for ( const auto& illustration : book.getIllustrations() ) {
|
||||
// For now, we are handling only sizexsize@1 illustration.
|
||||
// So we can simply pass one size to mustache.
|
||||
illustrations.push_back(kainjow::mustache::object{
|
||||
{"icon_width", to_string(illustration_size)},
|
||||
{"icon_height", to_string(illustration_size)},
|
||||
{"icon_scale", "1"},
|
||||
{"icon_size", to_string(illustration->width)},
|
||||
{"icon_mimetype", illustration->mimeType}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -80,7 +81,7 @@ kainjow::mustache::object getSingleBookData(const Book& book)
|
||||
{"title", book.getTitle()},
|
||||
{"description", book.getDescription()},
|
||||
{"language", book.getLanguage()},
|
||||
{"content_id", book.getHumanReadableIdFromPath()},
|
||||
{"content_id", urlEncode(book.getHumanReadableIdFromPath(), true)},
|
||||
{"updated", book.getDate() + "T00:00:00Z"},
|
||||
{"category", book.getCategory()},
|
||||
{"flavour", book.getFlavour()},
|
||||
@@ -95,23 +96,29 @@ kainjow::mustache::object getSingleBookData(const Book& book)
|
||||
};
|
||||
}
|
||||
|
||||
std::string getSingleBookEntryXML(const Book& book, bool withXMLHeader, const std::string& endpointRoot, bool partial)
|
||||
std::string getSingleBookEntryXML(const Book& book, bool withXMLHeader, const std::string& rootLocation, const std::string& endpointRoot, bool partial)
|
||||
{
|
||||
auto data = getSingleBookData(book);
|
||||
data["with_xml_header"] = MustacheData(withXMLHeader);
|
||||
data["dump_partial_entries"] = MustacheData(partial);
|
||||
data["endpoint_root"] = endpointRoot;
|
||||
data["root"] = rootLocation;
|
||||
return render_template(RESOURCE::templates::catalog_v2_entry_xml, data);
|
||||
}
|
||||
|
||||
BooksData getBooksData(const Library* library, const std::vector<std::string>& bookIds, const std::string& endpointRoot, bool partial)
|
||||
BooksData getBooksData(const Library* library, const std::vector<std::string>& bookIds, const std::string& rootLocation, const std::string& endpointRoot, bool partial)
|
||||
{
|
||||
BooksData booksData;
|
||||
for ( const auto& bookId : bookIds ) {
|
||||
const Book& book = library->getBookById(bookId);
|
||||
booksData.push_back(kainjow::mustache::object{
|
||||
{"entry", getSingleBookEntryXML(book, false, endpointRoot, partial)}
|
||||
});
|
||||
try {
|
||||
const Book book = library->getBookByIdThreadSafe(bookId);
|
||||
booksData.push_back(kainjow::mustache::object{
|
||||
{"entry", getSingleBookEntryXML(book, false, rootLocation, endpointRoot, partial)}
|
||||
});
|
||||
} catch ( const std::out_of_range& ) {
|
||||
// the book was removed from the library since its id was obtained
|
||||
// ignore it
|
||||
}
|
||||
}
|
||||
|
||||
return booksData;
|
||||
@@ -130,7 +137,7 @@ std::string getLanguageSelfName(const std::string& lang) {
|
||||
|
||||
string OPDSDumper::dumpOPDSFeed(const std::vector<std::string>& bookIds, const std::string& query) const
|
||||
{
|
||||
const auto booksData = getBooksData(library, bookIds, "", false);
|
||||
const auto booksData = getBooksData(library, bookIds, rootLocation, "", false);
|
||||
const kainjow::mustache::object template_data{
|
||||
{"date", gen_date_str()},
|
||||
{"root", rootLocation},
|
||||
@@ -148,7 +155,7 @@ string OPDSDumper::dumpOPDSFeed(const std::vector<std::string>& bookIds, const s
|
||||
string OPDSDumper::dumpOPDSFeedV2(const std::vector<std::string>& bookIds, const std::string& query, bool partial) const
|
||||
{
|
||||
const auto endpointRoot = rootLocation + "/catalog/v2";
|
||||
const auto booksData = getBooksData(library, bookIds, endpointRoot, partial);
|
||||
const auto booksData = getBooksData(library, bookIds, rootLocation, endpointRoot, partial);
|
||||
|
||||
const char* const endpoint = partial ? "/partial_entries" : "/entries";
|
||||
const kainjow::mustache::object template_data{
|
||||
@@ -169,7 +176,7 @@ string OPDSDumper::dumpOPDSFeedV2(const std::vector<std::string>& bookIds, const
|
||||
|
||||
std::string OPDSDumper::dumpOPDSCompleteEntry(const std::string& bookId) const
|
||||
{
|
||||
return getSingleBookEntryXML(library->getBookById(bookId), true, "", false);
|
||||
return getSingleBookEntryXML(library->getBookById(bookId), true, rootLocation, "", false);
|
||||
}
|
||||
|
||||
std::string OPDSDumper::categoriesOPDSFeed() const
|
||||
|
||||
@@ -52,7 +52,7 @@ Reader::Reader(const string zimFilePath)
|
||||
srand(time(nullptr));
|
||||
}
|
||||
|
||||
Reader::Reader(const std::shared_ptr<zim::Archive> archive)
|
||||
Reader::Reader(const std::shared_ptr<zim::Archive> archive, bool _marker)
|
||||
: zimArchive(archive),
|
||||
zimFilePath(archive->getFilename())
|
||||
{}
|
||||
@@ -122,7 +122,7 @@ string Reader::getId() const
|
||||
Entry Reader::getRandomPage() const
|
||||
{
|
||||
try {
|
||||
return zimArchive->getRandomEntry();
|
||||
return Entry(zimArchive->getRandomEntry(), true);
|
||||
} catch(...) {
|
||||
throw NoEntry();
|
||||
}
|
||||
@@ -130,7 +130,7 @@ Entry Reader::getRandomPage() const
|
||||
|
||||
Entry Reader::getMainPage() const
|
||||
{
|
||||
return zimArchive->getMainEntry();
|
||||
return Entry(zimArchive->getMainEntry(), true);
|
||||
}
|
||||
|
||||
bool Reader::getFavicon(string& content, string& mimeType) const
|
||||
@@ -242,7 +242,7 @@ string Reader::getScraper() const
|
||||
Entry Reader::getEntryFromPath(const std::string& path) const
|
||||
{
|
||||
try {
|
||||
return kiwix::getEntryFromPath(*zimArchive, path);
|
||||
return Entry(kiwix::getEntryFromPath(*zimArchive, path), true);
|
||||
} catch (zim::EntryNotFound& e) {
|
||||
throw NoEntry();
|
||||
}
|
||||
@@ -256,7 +256,7 @@ Entry Reader::getEntryFromEncodedPath(const std::string& path) const
|
||||
Entry Reader::getEntryFromTitle(const std::string& title) const
|
||||
{
|
||||
try {
|
||||
return zimArchive->getEntryByTitle(title);
|
||||
return Entry(zimArchive->getEntryByTitle(title), true);
|
||||
} catch(zim::EntryNotFound& e) {
|
||||
throw NoEntry();
|
||||
}
|
||||
|
||||
@@ -94,6 +94,12 @@ bool Searcher::add_reader(Reader* reader)
|
||||
if (!reader->hasFulltextIndex()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for ( const Reader* const existing_reader : readers ) {
|
||||
if ( existing_reader->getZimArchive()->getUuid() == reader->getZimArchive()->getUuid() )
|
||||
return false;
|
||||
}
|
||||
|
||||
this->readers.push_back(reader);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -71,4 +71,14 @@ void Server::setRoot(const std::string& root)
|
||||
}
|
||||
}
|
||||
|
||||
int Server::getPort()
|
||||
{
|
||||
return mp_server->getPort();
|
||||
}
|
||||
|
||||
std::string Server::getAddress()
|
||||
{
|
||||
return mp_server->getAddress();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ extern "C" {
|
||||
#include "tools/regexTools.h"
|
||||
#include "tools/stringTools.h"
|
||||
#include "tools/archiveTools.h"
|
||||
#include "tools/networkTools.h"
|
||||
#include "library.h"
|
||||
#include "name_mapper.h"
|
||||
#include "entry.h"
|
||||
@@ -95,16 +96,6 @@ inline std::string normalizeRootUrl(std::string rootUrl)
|
||||
return rootUrl.empty() ? rootUrl : "/" + rootUrl;
|
||||
}
|
||||
|
||||
unsigned parseIllustration(const std::string& s)
|
||||
{
|
||||
int nw(0), nh(0), nEnd(0);
|
||||
long int w(-1), h(-1);
|
||||
if ( sscanf(s.c_str(), "Illustration_%n%ldx%n%ld@1%n)", &nw, &w, &nh, &h, &nEnd) == 2
|
||||
&& nEnd == (int)s.size() && !isspace(s[nw]) && !isspace(s[nh]) && w == h && w >= 0) {
|
||||
return w;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
} // unnamed namespace
|
||||
|
||||
static IdNameMapper defaultNameMapper;
|
||||
@@ -159,15 +150,16 @@ bool InternalServer::start() {
|
||||
sockAddr.sin_family = AF_INET;
|
||||
sockAddr.sin_port = htons(m_port);
|
||||
if (m_addr.empty()) {
|
||||
if (0 != INADDR_ANY)
|
||||
if (0 != INADDR_ANY) {
|
||||
sockAddr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
}
|
||||
m_addr = kiwix::getBestPublicIp();
|
||||
} else {
|
||||
if (inet_pton(AF_INET, m_addr.c_str(), &(sockAddr.sin_addr.s_addr)) == 0) {
|
||||
std::cerr << "Ip address " << m_addr << " is not a valid ip address" << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
mp_daemon = MHD_start_daemon(flags,
|
||||
m_port,
|
||||
NULL,
|
||||
@@ -271,7 +263,7 @@ std::unique_ptr<Response> InternalServer::handle_request(const RequestContext& r
|
||||
{
|
||||
try {
|
||||
if (! request.is_valid_url())
|
||||
return Response::build_404(*this, request, "", "");
|
||||
return Response::build_404(*this, request.get_full_url(), "", "");
|
||||
|
||||
const ETag etag = get_matching_if_none_match_etag(request);
|
||||
if ( etag )
|
||||
@@ -283,8 +275,8 @@ std::unique_ptr<Response> InternalServer::handle_request(const RequestContext& r
|
||||
if (startsWith(request.get_url(), "/catalog/"))
|
||||
return handle_catalog(request);
|
||||
|
||||
if (request.get_url() == "/meta")
|
||||
return handle_meta(request);
|
||||
if (startsWith(request.get_url(), "/raw/"))
|
||||
return handle_raw(request);
|
||||
|
||||
if (request.get_url() == "/search")
|
||||
return handle_search(request);
|
||||
@@ -378,60 +370,6 @@ SuggestionsList_t getSuggestions(const zim::Archive* const archive,
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Archive and Zim handlers end
|
||||
**/
|
||||
|
||||
std::unique_ptr<Response> InternalServer::handle_meta(const RequestContext& request)
|
||||
{
|
||||
std::string bookName;
|
||||
std::string bookId;
|
||||
std::string meta_name;
|
||||
std::shared_ptr<zim::Archive> archive;
|
||||
try {
|
||||
bookName = request.get_argument("content");
|
||||
bookId = mp_nameMapper->getIdForName(bookName);
|
||||
meta_name = request.get_argument("name");
|
||||
archive = mp_library->getArchiveById(bookId);
|
||||
} catch (const std::out_of_range& e) {
|
||||
return Response::build_404(*this, request, bookName, "");
|
||||
}
|
||||
|
||||
if (archive == nullptr) {
|
||||
return Response::build_404(*this, request, bookName, "");
|
||||
}
|
||||
|
||||
std::string content;
|
||||
std::string mimeType = "text";
|
||||
|
||||
if (meta_name == "title") {
|
||||
content = getArchiveTitle(*archive);
|
||||
} else if (meta_name == "description") {
|
||||
content = getMetaDescription(*archive);
|
||||
} else if (meta_name == "language") {
|
||||
content = getMetaLanguage(*archive);
|
||||
} else if (meta_name == "name") {
|
||||
content = getMetaName(*archive);
|
||||
} else if (meta_name == "tags") {
|
||||
content = getMetaTags(*archive);
|
||||
} else if (meta_name == "date") {
|
||||
content = getMetaDate(*archive);
|
||||
} else if (meta_name == "creator") {
|
||||
content = getMetaCreator(*archive);
|
||||
} else if (meta_name == "publisher") {
|
||||
content = getMetaPublisher(*archive);
|
||||
} else if (meta_name == "favicon") {
|
||||
getArchiveFavicon(*archive, 48, content, mimeType);
|
||||
} else if (const unsigned illustrationSize = parseIllustration(meta_name)) {
|
||||
getArchiveFavicon(*archive, illustrationSize, content, mimeType);
|
||||
} else {
|
||||
return Response::build_404(*this, request, bookName, "");
|
||||
}
|
||||
|
||||
auto response = ContentResponse::build(*this, content, mimeType);
|
||||
response->set_cacheable();
|
||||
return std::move(response);
|
||||
}
|
||||
|
||||
std::unique_ptr<Response> InternalServer::handle_suggest(const RequestContext& request)
|
||||
{
|
||||
@@ -439,38 +377,26 @@ std::unique_ptr<Response> InternalServer::handle_suggest(const RequestContext& r
|
||||
printf("** running handle_suggest\n");
|
||||
}
|
||||
|
||||
std::string content;
|
||||
std::string mimeType;
|
||||
|
||||
std::string bookName;
|
||||
std::string bookId;
|
||||
std::string queryString;
|
||||
std::shared_ptr<zim::Archive> archive;
|
||||
try {
|
||||
bookName = request.get_argument("content");
|
||||
bookId = mp_nameMapper->getIdForName(bookName);
|
||||
queryString = request.get_argument("term");
|
||||
const std::string bookId = mp_nameMapper->getIdForName(bookName);
|
||||
archive = mp_library->getArchiveById(bookId);
|
||||
} catch (const std::out_of_range&) {
|
||||
return Response::build_404(*this, request, bookName, "");
|
||||
}
|
||||
|
||||
auto start = 0;
|
||||
try {
|
||||
start = request.get_argument<unsigned int>("start");
|
||||
} catch (const std::exception&) {}
|
||||
|
||||
unsigned int count = 10;
|
||||
try {
|
||||
count = request.get_argument<unsigned int>("count");
|
||||
} catch (const std::exception&) {}
|
||||
|
||||
if (count == 0) {
|
||||
count = 10;
|
||||
// error handled by the archive == nullptr check below
|
||||
}
|
||||
|
||||
if (archive == nullptr) {
|
||||
return Response::build_404(*this, request, bookName, "");
|
||||
const std::string error_details = "No such book: " + bookName;
|
||||
return Response::build_404(*this, "", bookName, "", error_details);
|
||||
}
|
||||
|
||||
const auto queryString = request.get_optional_param("term", std::string());
|
||||
const auto start = request.get_optional_param<unsigned int>("start", 0);
|
||||
unsigned int count = request.get_optional_param<unsigned int>("count", 10);
|
||||
if (count == 0) {
|
||||
count = 10;
|
||||
}
|
||||
|
||||
if (m_verbose.load()) {
|
||||
@@ -532,7 +458,7 @@ std::unique_ptr<Response> InternalServer::handle_skin(const RequestContext& requ
|
||||
response->set_cacheable();
|
||||
return std::move(response);
|
||||
} catch (const ResourceNotFound& e) {
|
||||
return Response::build_404(*this, request, "", "");
|
||||
return Response::build_404(*this, request.get_full_url(), "", "");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -542,13 +468,6 @@ std::unique_ptr<Response> InternalServer::handle_search(const RequestContext& re
|
||||
printf("** running handle_search\n");
|
||||
}
|
||||
|
||||
std::string bookName;
|
||||
std::string bookId;
|
||||
try {
|
||||
bookName = request.get_argument("content");
|
||||
bookId = mp_nameMapper->getIdForName(bookName);
|
||||
} catch (const std::out_of_range&) {}
|
||||
|
||||
std::string patternString;
|
||||
try {
|
||||
patternString = request.get_argument("pattern");
|
||||
@@ -567,8 +486,11 @@ std::unique_ptr<Response> InternalServer::handle_search(const RequestContext& re
|
||||
} catch(const std::out_of_range&) {}
|
||||
catch(const std::invalid_argument&) {}
|
||||
|
||||
std::string bookName;
|
||||
std::shared_ptr<zim::Archive> archive;
|
||||
try {
|
||||
bookName = request.get_argument("content");
|
||||
const std::string bookId = mp_nameMapper->getIdForName(bookName);
|
||||
archive = mp_library->getArchiveById(bookId);
|
||||
} catch (const std::out_of_range&) {}
|
||||
|
||||
@@ -661,25 +583,26 @@ std::unique_ptr<Response> InternalServer::handle_random(const RequestContext& re
|
||||
}
|
||||
|
||||
std::string bookName;
|
||||
std::string bookId;
|
||||
std::shared_ptr<zim::Archive> archive;
|
||||
try {
|
||||
bookName = request.get_argument("content");
|
||||
bookId = mp_nameMapper->getIdForName(bookName);
|
||||
const std::string bookId = mp_nameMapper->getIdForName(bookName);
|
||||
archive = mp_library->getArchiveById(bookId);
|
||||
} catch (const std::out_of_range&) {
|
||||
return Response::build_404(*this, request, bookName, "");
|
||||
// error handled by the archive == nullptr check below
|
||||
}
|
||||
|
||||
if (archive == nullptr) {
|
||||
return Response::build_404(*this, request, bookName, "");
|
||||
const std::string error_details = "No such book: " + bookName;
|
||||
return Response::build_404(*this, "", bookName, "", error_details);
|
||||
}
|
||||
|
||||
try {
|
||||
auto entry = archive->getRandomEntry();
|
||||
return build_redirect(bookName, getFinalItem(*archive, entry));
|
||||
} catch(zim::EntryNotFound& e) {
|
||||
return Response::build_404(*this, request, bookName, "");
|
||||
const std::string error_details = "Oops! Failed to pick a random article :(";
|
||||
return Response::build_404(*this, "", bookName, getArchiveTitle(*archive), error_details);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -691,7 +614,7 @@ std::unique_ptr<Response> InternalServer::handle_captured_external(const Request
|
||||
} catch (const std::out_of_range& e) {}
|
||||
|
||||
if (source.empty())
|
||||
return Response::build_404(*this, request, "", "");
|
||||
return Response::build_404(*this, request.get_full_url(), "", "");
|
||||
|
||||
auto data = get_default_data();
|
||||
data.set("source", source);
|
||||
@@ -710,7 +633,7 @@ std::unique_ptr<Response> InternalServer::handle_catalog(const RequestContext& r
|
||||
host = request.get_header("Host");
|
||||
url = request.get_url_part(1);
|
||||
} catch (const std::out_of_range&) {
|
||||
return Response::build_404(*this, request, "", "");
|
||||
return Response::build_404(*this, request.get_full_url(), "", "");
|
||||
}
|
||||
|
||||
if (url == "v2") {
|
||||
@@ -718,7 +641,7 @@ std::unique_ptr<Response> InternalServer::handle_catalog(const RequestContext& r
|
||||
}
|
||||
|
||||
if (url != "searchdescription.xml" && url != "root.xml" && url != "search") {
|
||||
return Response::build_404(*this, request, "", "");
|
||||
return Response::build_404(*this, request.get_full_url(), "", "");
|
||||
}
|
||||
|
||||
if (url == "searchdescription.xml") {
|
||||
@@ -797,7 +720,8 @@ InternalServer::search_catalog(const RequestContext& request,
|
||||
const auto totalResults = bookIdsToDump.size();
|
||||
const size_t count = request.get_optional_param("count", 10UL);
|
||||
const size_t startIndex = request.get_optional_param("start", 0UL);
|
||||
bookIdsToDump = subrange(bookIdsToDump, startIndex, count);
|
||||
const size_t intendedCount = count > 0 ? count : bookIdsToDump.size();
|
||||
bookIdsToDump = subrange(bookIdsToDump, startIndex, intendedCount);
|
||||
opdsDumper.setOpenSearchInfo(totalResults, startIndex, bookIdsToDump.size());
|
||||
return bookIdsToDump;
|
||||
}
|
||||
@@ -854,7 +778,7 @@ std::unique_ptr<Response> InternalServer::handle_content(const RequestContext& r
|
||||
std::string searchURL = m_root+"/search?pattern="+pattern; // Make a full search on the entire library.
|
||||
const std::string details = searchSuggestionHTML(searchURL, kiwix::urlDecode(pattern));
|
||||
|
||||
return Response::build_404(*this, request, bookName, "", details);
|
||||
return Response::build_404(*this, request.get_full_url(), bookName, "", details);
|
||||
}
|
||||
|
||||
auto urlStr = request.get_url().substr(bookName.size()+1);
|
||||
@@ -887,7 +811,65 @@ std::unique_ptr<Response> InternalServer::handle_content(const RequestContext& r
|
||||
std::string searchURL = m_root+"/search?content="+bookName+"&pattern="+pattern; // Make a search on this specific book only.
|
||||
const std::string details = searchSuggestionHTML(searchURL, kiwix::urlDecode(pattern));
|
||||
|
||||
return Response::build_404(*this, request, bookName, getArchiveTitle(*archive), details);
|
||||
return Response::build_404(*this, request.get_full_url(), bookName, getArchiveTitle(*archive), details);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::unique_ptr<Response> InternalServer::handle_raw(const RequestContext& request)
|
||||
{
|
||||
if (m_verbose.load()) {
|
||||
printf("** running handle_raw\n");
|
||||
}
|
||||
|
||||
std::string bookName;
|
||||
std::string kind;
|
||||
try {
|
||||
bookName = request.get_url_part(1);
|
||||
kind = request.get_url_part(2);
|
||||
} catch (const std::out_of_range& e) {
|
||||
return Response::build_404(*this, request.get_full_url(), bookName, "", "");
|
||||
}
|
||||
|
||||
if (kind != "meta" && kind!= "content") {
|
||||
const std::string error_details = kind + " is not a valid request for raw content.";
|
||||
return Response::build_404(*this, request.get_full_url(), bookName, "", error_details);
|
||||
}
|
||||
|
||||
std::shared_ptr<zim::Archive> archive;
|
||||
try {
|
||||
const std::string bookId = mp_nameMapper->getIdForName(bookName);
|
||||
archive = mp_library->getArchiveById(bookId);
|
||||
} catch (const std::out_of_range& e) {}
|
||||
|
||||
if (archive == nullptr) {
|
||||
const std::string error_details = "No such book: " + bookName;
|
||||
return Response::build_404(*this, request.get_full_url(), bookName, "", error_details);
|
||||
}
|
||||
|
||||
// Remove the beggining of the path:
|
||||
// /raw/<bookName>/<kind>/foo
|
||||
// ^^^^^ ^ ^
|
||||
// 5 + 1 + 1 = 7
|
||||
auto itemPath = request.get_url().substr(bookName.size()+kind.size()+7);
|
||||
|
||||
try {
|
||||
if (kind == "meta") {
|
||||
auto item = archive->getMetadataItem(itemPath);
|
||||
return ItemResponse::build(*this, request, item, /*raw=*/true);
|
||||
} else {
|
||||
auto entry = archive->getEntryByPath(itemPath);
|
||||
if (entry.isRedirect()) {
|
||||
return build_redirect(bookName, entry.getItem(true));
|
||||
}
|
||||
return ItemResponse::build(*this, request, entry.getItem(), /*raw=*/true);
|
||||
}
|
||||
} catch (zim::EntryNotFound& e ) {
|
||||
if (m_verbose.load()) {
|
||||
printf("Failed to find %s\n", itemPath.c_str());
|
||||
}
|
||||
const std::string error_details = "Cannot find " + kind + " entry " + itemPath;
|
||||
return Response::build_404(*this, request.get_full_url(), bookName, getArchiveTitle(*archive), error_details);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,8 @@ class InternalServer {
|
||||
void** cont_cls);
|
||||
bool start();
|
||||
void stop();
|
||||
std::string getAddress() { return m_addr; }
|
||||
int getPort() { return m_port; }
|
||||
|
||||
private: // functions
|
||||
std::unique_ptr<Response> handle_request(const RequestContext& request);
|
||||
@@ -80,12 +82,13 @@ class InternalServer {
|
||||
std::unique_ptr<Response> handle_catalog_v2_complete_entry(const RequestContext& request, const std::string& entryId);
|
||||
std::unique_ptr<Response> handle_catalog_v2_categories(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_catalog_v2_languages(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_meta(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_catalog_v2_illustration(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_search(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_suggest(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_random(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_captured_external(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_content(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_raw(const RequestContext& request);
|
||||
|
||||
std::vector<std::string> search_catalog(const RequestContext& request,
|
||||
kiwix::OPDSDumper& opdsDumper);
|
||||
@@ -114,8 +117,8 @@ class InternalServer {
|
||||
std::string m_library_id;
|
||||
|
||||
friend std::unique_ptr<Response> Response::build(const InternalServer& server);
|
||||
friend std::unique_ptr<ContentResponse> ContentResponse::build(const InternalServer& server, const std::string& content, const std::string& mimetype, bool isHomePage);
|
||||
friend std::unique_ptr<Response> ItemResponse::build(const InternalServer& server, const RequestContext& request, const zim::Item& item);
|
||||
friend std::unique_ptr<ContentResponse> ContentResponse::build(const InternalServer& server, const std::string& content, const std::string& mimetype, bool isHomePage, bool raw);
|
||||
friend std::unique_ptr<Response> ItemResponse::build(const InternalServer& server, const RequestContext& request, const zim::Item& item, bool raw);
|
||||
friend std::unique_ptr<Response> Response::build_500(const InternalServer& server, const std::string& msg);
|
||||
|
||||
};
|
||||
|
||||
@@ -43,7 +43,7 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2(const RequestContext
|
||||
try {
|
||||
url = request.get_url_part(2);
|
||||
} catch (const std::out_of_range&) {
|
||||
return Response::build_404(*this, request, "", "");
|
||||
return Response::build_404(*this, request.get_full_url(), "", "");
|
||||
}
|
||||
|
||||
if (url == "root.xml") {
|
||||
@@ -66,8 +66,10 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2(const RequestContext
|
||||
return handle_catalog_v2_categories(request);
|
||||
} else if (url == "languages") {
|
||||
return handle_catalog_v2_languages(request);
|
||||
} else if (url == "illustration") {
|
||||
return handle_catalog_v2_illustration(request);
|
||||
} else {
|
||||
return Response::build_404(*this, request, "", "");
|
||||
return Response::build_404(*this, request.get_full_url(), "", "");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +110,7 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_complete_entry(const
|
||||
try {
|
||||
mp_library->getBookById(entryId);
|
||||
} catch (const std::out_of_range&) {
|
||||
return Response::build_404(*this, request, "", "");
|
||||
return Response::build_404(*this, request.get_full_url(), "", "");
|
||||
}
|
||||
|
||||
OPDSDumper opdsDumper(mp_library);
|
||||
@@ -146,4 +148,18 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_languages(const Requ
|
||||
);
|
||||
}
|
||||
|
||||
std::unique_ptr<Response> InternalServer::handle_catalog_v2_illustration(const RequestContext& request)
|
||||
{
|
||||
try {
|
||||
const auto bookName = request.get_url_part(3);
|
||||
const auto bookId = mp_nameMapper->getIdForName(bookName);
|
||||
auto book = mp_library->getBookByIdThreadSafe(bookId);
|
||||
auto size = request.get_argument<unsigned int>("size");
|
||||
auto illustration = book.getIllustration(size);
|
||||
return ContentResponse::build(*this, illustration->getData(), illustration->mimeType);
|
||||
} catch(...) {
|
||||
return Response::build_404(*this, request.get_full_url(), "", "");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace kiwix
|
||||
|
||||
@@ -83,10 +83,12 @@ std::unique_ptr<Response> Response::build_304(const InternalServer& server, cons
|
||||
return response;
|
||||
}
|
||||
|
||||
std::unique_ptr<Response> Response::build_404(const InternalServer& server, const RequestContext& request, const std::string& bookName, const std::string& bookTitle, const std::string& details)
|
||||
std::unique_ptr<Response> Response::build_404(const InternalServer& server, const std::string& url, const std::string& bookName, const std::string& bookTitle, const std::string& details)
|
||||
{
|
||||
MustacheData results;
|
||||
results.set("url", request.get_full_url());
|
||||
if ( !url.empty() ) {
|
||||
results.set("url", url);
|
||||
}
|
||||
results.set("details", details);
|
||||
|
||||
auto response = ContentResponse::build(server, RESOURCE::templates::_404_html, results, "text/html");
|
||||
@@ -115,7 +117,16 @@ std::unique_ptr<Response> Response::build_500(const InternalServer& server, cons
|
||||
data.set("error", msg);
|
||||
auto content = render_template(RESOURCE::templates::_500_html, data);
|
||||
std::unique_ptr<Response> response (
|
||||
new ContentResponse(server.m_root, true, false, false, false, content, "text/html"));
|
||||
new ContentResponse(
|
||||
server.m_root, //root
|
||||
true, //verbose
|
||||
true, //raw
|
||||
false, //withTaskbar
|
||||
false, //withLibraryButton
|
||||
false, //blockExternalLinks
|
||||
content, //content
|
||||
"text/html" //mimetype
|
||||
));
|
||||
response->set_code(MHD_HTTP_INTERNAL_SERVER_ERROR);
|
||||
return response;
|
||||
}
|
||||
@@ -236,8 +247,11 @@ ContentResponse::can_compress(const RequestContext& request) const
|
||||
bool
|
||||
ContentResponse::contentDecorationAllowed() const
|
||||
{
|
||||
return (startsWith(m_mimeType, "text/html")
|
||||
&& m_mimeType.find(";raw=true") == std::string::npos);
|
||||
if (m_raw) {
|
||||
return false;
|
||||
}
|
||||
return (startsWith(m_mimeType, "text/html")
|
||||
&& m_mimeType.find(";raw=true") == std::string::npos);
|
||||
}
|
||||
|
||||
MHD_Response*
|
||||
@@ -325,11 +339,12 @@ void ContentResponse::set_taskbar(const std::string& bookName, const std::string
|
||||
}
|
||||
|
||||
|
||||
ContentResponse::ContentResponse(const std::string& root, bool verbose, bool withTaskbar, bool withLibraryButton, bool blockExternalLinks, const std::string& content, const std::string& mimetype) :
|
||||
ContentResponse::ContentResponse(const std::string& root, bool verbose, bool raw, bool withTaskbar, bool withLibraryButton, bool blockExternalLinks, const std::string& content, const std::string& mimetype) :
|
||||
Response(verbose),
|
||||
m_root(root),
|
||||
m_content(content),
|
||||
m_mimeType(mimetype),
|
||||
m_raw(raw),
|
||||
m_withTaskbar(withTaskbar),
|
||||
m_withLibraryButton(withLibraryButton),
|
||||
m_blockExternalLinks(blockExternalLinks),
|
||||
@@ -339,11 +354,17 @@ ContentResponse::ContentResponse(const std::string& root, bool verbose, bool wit
|
||||
add_header(MHD_HTTP_HEADER_CONTENT_TYPE, m_mimeType);
|
||||
}
|
||||
|
||||
std::unique_ptr<ContentResponse> ContentResponse::build(const InternalServer& server, const std::string& content, const std::string& mimetype, bool isHomePage)
|
||||
std::unique_ptr<ContentResponse> ContentResponse::build(
|
||||
const InternalServer& server,
|
||||
const std::string& content,
|
||||
const std::string& mimetype,
|
||||
bool isHomePage,
|
||||
bool raw)
|
||||
{
|
||||
return std::unique_ptr<ContentResponse>(new ContentResponse(
|
||||
server.m_root,
|
||||
server.m_verbose.load(),
|
||||
raw,
|
||||
server.m_withTaskbar && !isHomePage,
|
||||
server.m_withLibraryButton,
|
||||
server.m_blockExternalLinks,
|
||||
@@ -351,7 +372,13 @@ std::unique_ptr<ContentResponse> ContentResponse::build(const InternalServer& se
|
||||
mimetype));
|
||||
}
|
||||
|
||||
std::unique_ptr<ContentResponse> ContentResponse::build(const InternalServer& server, const std::string& template_str, kainjow::mustache::data data, const std::string& mimetype, bool isHomePage) {
|
||||
std::unique_ptr<ContentResponse> ContentResponse::build(
|
||||
const InternalServer& server,
|
||||
const std::string& template_str,
|
||||
kainjow::mustache::data data,
|
||||
const std::string& mimetype,
|
||||
bool isHomePage)
|
||||
{
|
||||
auto content = render_template(template_str, data);
|
||||
return ContentResponse::build(server, content, mimetype, isHomePage);
|
||||
}
|
||||
@@ -366,14 +393,14 @@ ItemResponse::ItemResponse(bool verbose, const zim::Item& item, const std::strin
|
||||
add_header(MHD_HTTP_HEADER_CONTENT_TYPE, m_mimeType);
|
||||
}
|
||||
|
||||
std::unique_ptr<Response> ItemResponse::build(const InternalServer& server, const RequestContext& request, const zim::Item& item)
|
||||
std::unique_ptr<Response> ItemResponse::build(const InternalServer& server, const RequestContext& request, const zim::Item& item, bool raw)
|
||||
{
|
||||
const std::string mimetype = get_mime_type(item);
|
||||
auto byteRange = request.get_range().resolve(item.getSize());
|
||||
const bool noRange = byteRange.kind() == ByteRange::RESOLVED_FULL_CONTENT;
|
||||
if (noRange && is_compressible_mime_type(mimetype)) {
|
||||
// Return a contentResponse
|
||||
auto response = ContentResponse::build(server, item.getData(), mimetype);
|
||||
auto response = ContentResponse::build(server, item.getData(), mimetype, /*isHomePage=*/false, raw);
|
||||
response->set_cacheable();
|
||||
response->m_byteRange = byteRange;
|
||||
return std::move(response);
|
||||
|
||||
@@ -47,7 +47,7 @@ class Response {
|
||||
|
||||
static std::unique_ptr<Response> build(const InternalServer& server);
|
||||
static std::unique_ptr<Response> build_304(const InternalServer& server, const ETag& etag);
|
||||
static std::unique_ptr<Response> build_404(const InternalServer& server, const RequestContext& request, const std::string& bookName, const std::string& bookTitle, const std::string& details="");
|
||||
static std::unique_ptr<Response> build_404(const InternalServer& server, const std::string& url, const std::string& bookName, const std::string& bookTitle, const std::string& details="");
|
||||
static std::unique_ptr<Response> build_416(const InternalServer& server, size_t resourceLength);
|
||||
static std::unique_ptr<Response> build_500(const InternalServer& server, const std::string& msg);
|
||||
static std::unique_ptr<Response> build_redirect(const InternalServer& server, const std::string& redirectUrl);
|
||||
@@ -78,9 +78,27 @@ class Response {
|
||||
|
||||
class ContentResponse : public Response {
|
||||
public:
|
||||
ContentResponse(const std::string& root, bool verbose, bool withTaskbar, bool withLibraryButton, bool blockExternalLinks, const std::string& content, const std::string& mimetype);
|
||||
static std::unique_ptr<ContentResponse> build(const InternalServer& server, const std::string& content, const std::string& mimetype, bool isHomePage = false);
|
||||
static std::unique_ptr<ContentResponse> build(const InternalServer& server, const std::string& template_str, kainjow::mustache::data data, const std::string& mimetype, bool isHomePage = false);
|
||||
ContentResponse(
|
||||
const std::string& root,
|
||||
bool verbose,
|
||||
bool raw,
|
||||
bool withTaskbar,
|
||||
bool withLibraryButton,
|
||||
bool blockExternalLinks,
|
||||
const std::string& content,
|
||||
const std::string& mimetype);
|
||||
static std::unique_ptr<ContentResponse> build(
|
||||
const InternalServer& server,
|
||||
const std::string& content,
|
||||
const std::string& mimetype,
|
||||
bool isHomePage = false,
|
||||
bool raw = false);
|
||||
static std::unique_ptr<ContentResponse> build(
|
||||
const InternalServer& server,
|
||||
const std::string& template_str,
|
||||
kainjow::mustache::data data,
|
||||
const std::string& mimetype,
|
||||
bool isHomePage = false);
|
||||
|
||||
void set_taskbar(const std::string& bookName, const std::string& bookTitle);
|
||||
|
||||
@@ -98,6 +116,7 @@ class ContentResponse : public Response {
|
||||
std::string m_root;
|
||||
std::string m_content;
|
||||
std::string m_mimeType;
|
||||
bool m_raw;
|
||||
bool m_withTaskbar;
|
||||
bool m_withLibraryButton;
|
||||
bool m_blockExternalLinks;
|
||||
@@ -108,7 +127,7 @@ class ContentResponse : public Response {
|
||||
class ItemResponse : public Response {
|
||||
public:
|
||||
ItemResponse(bool verbose, const zim::Item& item, const std::string& mimetype, const ByteRange& byterange);
|
||||
static std::unique_ptr<Response> build(const InternalServer& server, const RequestContext& request, const zim::Item& item);
|
||||
static std::unique_ptr<Response> build(const InternalServer& server, const RequestContext& request, const zim::Item& item, bool raw = false);
|
||||
|
||||
private:
|
||||
MHD_Response* create_mhd_response(const RequestContext& request);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright 2012 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
* Copyright 2021 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
|
||||
@@ -17,6 +18,7 @@
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "tools.h"
|
||||
#include <tools/networkTools.h>
|
||||
|
||||
#include <stdio.h>
|
||||
@@ -29,6 +31,17 @@
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <iostream>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <net/if.h>
|
||||
#include <netdb.h>
|
||||
#endif
|
||||
|
||||
size_t write_callback_to_iss(char* ptr, size_t size, size_t nmemb, void* userdata)
|
||||
{
|
||||
@@ -57,3 +70,104 @@ std::string kiwix::download(const std::string& url) {
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> kiwix::getNetworkInterfaces() {
|
||||
std::map<std::string, std::string> interfaces;
|
||||
|
||||
#ifdef _WIN32
|
||||
SOCKET sd = WSASocket(AF_INET, SOCK_DGRAM, 0, 0, 0, 0);
|
||||
if (sd == INVALID_SOCKET) {
|
||||
std::cerr << "Failed to get a socket. Error " << WSAGetLastError() << std::endl;
|
||||
return interfaces;
|
||||
}
|
||||
|
||||
INTERFACE_INFO InterfaceList[20];
|
||||
unsigned long nBytesReturned;
|
||||
if (WSAIoctl(sd, SIO_GET_INTERFACE_LIST, 0, 0, &InterfaceList,
|
||||
sizeof(InterfaceList), &nBytesReturned, 0, 0) == SOCKET_ERROR) {
|
||||
std::cerr << "Failed calling WSAIoctl: error " << WSAGetLastError() << std::endl;
|
||||
return interfaces;
|
||||
}
|
||||
|
||||
int nNumInterfaces = nBytesReturned / sizeof(INTERFACE_INFO);
|
||||
for (int i = 0; i < nNumInterfaces; ++i) {
|
||||
sockaddr_in *pAddress;
|
||||
pAddress = (sockaddr_in *) & (InterfaceList[i].iiAddress.AddressIn);
|
||||
if(pAddress->sin_family == AF_INET) {
|
||||
/* Add to the map */
|
||||
std::string interfaceName = std::string(inet_ntoa(pAddress->sin_addr));
|
||||
interfaces[interfaceName] = interfaceName;
|
||||
}
|
||||
}
|
||||
#else
|
||||
/* Get Network interfaces information */
|
||||
char buf[16384];
|
||||
struct ifconf ifconf;
|
||||
int fd = socket(PF_INET, SOCK_DGRAM, 0); /* Only IPV4 */
|
||||
ifconf.ifc_len = sizeof(buf);
|
||||
ifconf.ifc_buf=buf;
|
||||
if(ioctl(fd, SIOCGIFCONF, &ifconf)!=0) {
|
||||
perror("ioctl(SIOCGIFCONF)");
|
||||
}
|
||||
|
||||
/* Go through each interface */
|
||||
struct ifreq *ifreq;
|
||||
ifreq = ifconf.ifc_req;
|
||||
for (int i = 0; i < ifconf.ifc_len; ) {
|
||||
if (ifreq->ifr_addr.sa_family == AF_INET) {
|
||||
/* Get the network interface ip */
|
||||
char host[128] = { 0 };
|
||||
const int error = getnameinfo(&(ifreq->ifr_addr), sizeof(ifreq->ifr_addr),
|
||||
host, sizeof(host),
|
||||
0, 0, NI_NUMERICHOST);
|
||||
if (!error) {
|
||||
std::string interfaceName = std::string(ifreq->ifr_name);
|
||||
std::string interfaceIp = std::string(host);
|
||||
/* Add to the map */
|
||||
interfaces[interfaceName] = interfaceIp;
|
||||
} else {
|
||||
perror("getnameinfo()");
|
||||
}
|
||||
}
|
||||
|
||||
/* some systems have ifr_addr.sa_len and adjust the length that
|
||||
* way, but not mine. weird */
|
||||
size_t len;
|
||||
#ifndef __linux__
|
||||
len = IFNAMSIZ + ifreq->ifr_addr.sa_len;
|
||||
#else
|
||||
len = sizeof(*ifreq);
|
||||
#endif
|
||||
ifreq = (struct ifreq*)((char*)ifreq+len);
|
||||
i += len;
|
||||
}
|
||||
#endif
|
||||
return interfaces;
|
||||
}
|
||||
|
||||
std::string kiwix::getBestPublicIp() {
|
||||
auto interfaces = getNetworkInterfaces();
|
||||
|
||||
#ifndef _WIN32
|
||||
const char* const prioritizedNames[] =
|
||||
{ "eth0", "eth1", "wlan0", "wlan1", "en0", "en1" };
|
||||
for(auto name: prioritizedNames) {
|
||||
auto it = interfaces.find(name);
|
||||
if(it != interfaces.end()) {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
const char* const prefixes[] = { "192.168", "172.16.", "10.0" };
|
||||
for(auto prefix : prefixes){
|
||||
for(auto& itr : interfaces) {
|
||||
auto interfaceIp = itr.second;
|
||||
if (interfaceIp.find(prefix) == 0) {
|
||||
return interfaceIp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "127.0.0.1";
|
||||
}
|
||||
|
||||
@@ -305,16 +305,18 @@ std::string kiwix::getFileContent(const std::string& path)
|
||||
bool kiwix::fileExists(const std::string& path)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return PathFileExistsW(Utf8ToWide(path).c_str());
|
||||
return (_waccess_s(Utf8ToWide(path).c_str(), 0) == 0);
|
||||
#else
|
||||
bool flag = false;
|
||||
std::fstream fin;
|
||||
fin.open(path.c_str(), std::ios::in);
|
||||
if (fin.is_open()) {
|
||||
flag = true;
|
||||
}
|
||||
fin.close();
|
||||
return flag;
|
||||
return (access(path.c_str(), F_OK) == 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool kiwix::fileReadable(const std::string& path)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return (_waccess_s(Utf8ToWide(path).c_str(), 4) == 0);
|
||||
#else
|
||||
return (access(path.c_str(), R_OK) == 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
77
src/version.cpp
Normal file
77
src/version.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2021 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
*
|
||||
* 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 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 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 Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#include <version.h>
|
||||
#include <zim/zim.h>
|
||||
#include <kiwix_config.h>
|
||||
#include <unicode/uversion.h>
|
||||
#include <pugixml.hpp>
|
||||
#include <curl/curl.h>
|
||||
#include <microhttpd.h>
|
||||
#include <xapian.h>
|
||||
#include <mustache.hpp>
|
||||
#include <zlib.h>
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
LibVersions getVersions() {
|
||||
LibVersions versions = {
|
||||
{ "libkiwix", LIBKIWIX_VERSION },
|
||||
{ "libzim", LIBZIM_VERSION },
|
||||
{ "libxapian", XAPIAN_VERSION },
|
||||
{ "libcurl", LIBCURL_VERSION },
|
||||
{ "libmicrohttpd", MHD_get_version() },
|
||||
{ "libz", ZLIB_VERSION }
|
||||
};
|
||||
|
||||
// U_ICU_VERSION does not include the patch level if 0
|
||||
std::ostringstream libicu_version;
|
||||
libicu_version << U_ICU_VERSION_MAJOR_NUM << "." << U_ICU_VERSION_MINOR_NUM << "." << U_ICU_VERSION_PATCHLEVEL_NUM;
|
||||
versions.push_back({ "libicu", libicu_version.str() });
|
||||
|
||||
// No human readable version string for pugixml
|
||||
const unsigned pugixml_major = (PUGIXML_VERSION - PUGIXML_VERSION % 1000) / 1000;
|
||||
const unsigned pugixml_minor = (PUGIXML_VERSION - pugixml_major * 1000 - PUGIXML_VERSION % 10) / 10;
|
||||
const unsigned pugixml_patch = PUGIXML_VERSION - pugixml_major * 1000 - pugixml_minor * 10;
|
||||
std::ostringstream libpugixml_version;
|
||||
libpugixml_version << pugixml_major << "." << pugixml_minor << "." << pugixml_patch;
|
||||
versions.push_back({ "libpugixml", libpugixml_version.str() });
|
||||
|
||||
// Needs version 5.0 of Mustache
|
||||
#if defined(KAINJOW_MUSTACHE_VERSION_MAJOR)
|
||||
std::ostringstream libmustache_version;
|
||||
libmustache_version << KAINJOW_MUSTACHE_VERSION_MAJOR << "." <<
|
||||
KAINJOW_MUSTACHE_VERSION_MINOR << "." << KAINJOW_MUSTACHE_VERSION_PATCH;
|
||||
versions.push_back({ "libmustache", libmustache_version.str() });
|
||||
#endif
|
||||
|
||||
return versions;
|
||||
}
|
||||
|
||||
void printVersions(std::ostream& out) {
|
||||
LibVersions versions = getVersions();
|
||||
for (const auto& iter : versions) {
|
||||
out << (iter != versions.front() ? "+ " : "")
|
||||
<< iter.first << " " << iter.second << std::endl;
|
||||
}
|
||||
}
|
||||
} //namespace kiwix
|
||||
@@ -28,7 +28,7 @@ skin/iso6391To3.js
|
||||
skin/isotope.pkgd.min.js
|
||||
skin/index.js
|
||||
skin/taskbar.css
|
||||
skin/home.css
|
||||
skin/index.css
|
||||
skin/fonts/Poppins.ttf
|
||||
skin/fonts/Roboto.ttf
|
||||
skin/block_external.js
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -16,6 +16,7 @@
|
||||
let filters = getCookie(filterCookieName);
|
||||
let params = new URLSearchParams(window.location.search || filters || '');
|
||||
let timer;
|
||||
let languages = {};
|
||||
|
||||
function queryUrlBuilder() {
|
||||
let url = `${root}/catalog/search?`;
|
||||
@@ -51,10 +52,15 @@
|
||||
fileSize /= (1000 ** quotient);
|
||||
return `${+fileSize.toFixed(2)} ${units[quotient]}`;
|
||||
};
|
||||
|
||||
|
||||
const humanFriendlyTitle = (title) => {
|
||||
title = title.replace(/_/g, ' ');
|
||||
return htmlEncode(title[0].toUpperCase() + title.slice(1));
|
||||
if (typeof title === 'string' && title.length > 0) {
|
||||
title = title.replace(/_/g, ' ');
|
||||
if (title.length > 0) {
|
||||
return htmlEncode(title[0].toUpperCase() + title.slice(1));
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function htmlEncode(str) {
|
||||
@@ -67,7 +73,8 @@
|
||||
}
|
||||
|
||||
function getInnerHtml(node, query) {
|
||||
return node.querySelector(query).innerHTML;
|
||||
const queryNode = node.querySelector(query);
|
||||
return queryNode != null ? queryNode.innerHTML : "";
|
||||
}
|
||||
|
||||
function generateBookHtml(book, sort = false) {
|
||||
@@ -81,7 +88,8 @@
|
||||
const title = getInnerHtml(book, 'title');
|
||||
const description = getInnerHtml(book, 'summary');
|
||||
const id = getInnerHtml(book, 'id');
|
||||
const language = getInnerHtml(book, 'language');
|
||||
const langCode = getInnerHtml(book, 'language');
|
||||
const language = languages[langCode];
|
||||
const tags = getInnerHtml(book, 'tags');
|
||||
let tagHtml = tags.split(';').filter(tag => {return !(tag.split(':')[0].startsWith('_'))})
|
||||
.map((tag) => {return tag.charAt(0).toUpperCase() + tag.slice(1)})
|
||||
@@ -103,18 +111,28 @@
|
||||
if (sort) {
|
||||
divTag.setAttribute('data-idx', bookOrderMap.get(id));
|
||||
}
|
||||
divTag.innerHTML = `<div class="book__wrapper"><div class='book__icon' ><img class="book__icon--image" src='${root}${iconUrl}'></div>
|
||||
<div class='book__title' title='${title}'>
|
||||
<div id="bookTitle">${title}</div>
|
||||
${humanFriendlyZimSize ? `<div id='bookSize'>${humanFriendlyZimSize}</div>`: ''}
|
||||
const faviconAttr = iconUrl != undefined ? `style="background-image: url('${iconUrl}')"` : '';
|
||||
const languageAttr = langCode != '' ? `title="${language}" aria-label="${language}"` : 'style="background-color: transparent"';
|
||||
divTag.innerHTML = `<a class="book__link" href="${link}" data-hover="Preview">
|
||||
<div class="book__wrapper">
|
||||
<div class="book__icon" ${faviconAttr}></div>
|
||||
<div class="book__header">
|
||||
<div id="book__title">${title}</div>
|
||||
${downloadLink ? `<div class="book__download"><span data-link="${downloadLink}">Download ${humanFriendlyZimSize ? ` - ${humanFriendlyZimSize}</span></div>`: ''}` : ''}
|
||||
</div>
|
||||
<div class='book__description' title='${description}'>${description}</div>
|
||||
<div class='book__languageTag'>${language.substr(0, 2).toUpperCase()}</div>
|
||||
<div class='book__tags'><div class="book__tags--wrapper">${tagHtml}</div></div>
|
||||
<div class='book__links'> <a href="${root}${link}" data-hover="Preview">Preview</a>${downloadLink ? ` | <span class="download" data-link=${downloadLink} class="modal-button">Download</span>` : ''} </div></div>`;
|
||||
<div class="book__description" title="${description}">${description}</div>
|
||||
<div class="book__languageTag" ${languageAttr}>${getLanguageCodeToDisplay(langCode)}</div>
|
||||
<div class="book__tags"><div class="book__tags--wrapper">${tagHtml}</div></div>
|
||||
</div></div></a>`;
|
||||
return divTag;
|
||||
}
|
||||
|
||||
function getLanguageCodeToDisplay(langCode3Letter) {
|
||||
const langCode2Letter = (Object.keys(iso6391To3).find(key => iso6391To3[key] === langCode3Letter));
|
||||
const res = (langCode2Letter != undefined) ? langCode2Letter : langCode3Letter;
|
||||
return res.toUpperCase();
|
||||
}
|
||||
|
||||
function toggleFooter(show=false) {
|
||||
if (show) {
|
||||
footer.style.display = 'block';
|
||||
@@ -126,7 +144,8 @@
|
||||
|
||||
function insertModal(button) {
|
||||
const downloadLink = button.getAttribute('data-link');
|
||||
button.addEventListener('click', () => {
|
||||
button.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
document.body.insertAdjacentHTML('beforeend', `<div class="modal-wrapper">
|
||||
<div class="modal">
|
||||
<div class="modal-heading">
|
||||
@@ -226,7 +245,9 @@
|
||||
data.querySelectorAll('entry').forEach(entry => {
|
||||
const title = getInnerHtml(entry, 'title');
|
||||
const value = getInnerHtml(entry, valueEntryNode);
|
||||
optionStr += `<option value='${value}'>${humanFriendlyTitle(title)}</option>`;
|
||||
const hfTitle = humanFriendlyTitle(title);
|
||||
languages[value] = hfTitle;
|
||||
optionStr += (hfTitle != '') ? `<option value="${value}">${hfTitle}</option>` : '';
|
||||
});
|
||||
document.querySelector(nodeQuery).innerHTML += optionStr;
|
||||
});
|
||||
@@ -304,7 +325,7 @@
|
||||
booksToDelete.forEach(book => {iso.remove(book);});
|
||||
books.forEach((book) => {
|
||||
iso.insert(generateBookHtml(book, sort))
|
||||
const downloadButton = document.querySelector(`[data-id="${getInnerHtml(book, 'id')}"] .download`);
|
||||
const downloadButton = document.querySelector(`[data-id="${getInnerHtml(book, 'id')}"] .book__download span`);
|
||||
if (downloadButton) {
|
||||
insertModal(downloadButton);
|
||||
}
|
||||
@@ -339,7 +360,7 @@
|
||||
});
|
||||
|
||||
async function loadSubset() {
|
||||
if (window.innerHeight + window.scrollY >= document.body.offsetHeight) {
|
||||
if (window.innerHeight + window.scrollY >= (document.body.offsetHeight * 0.98)) {
|
||||
if (incrementalLoadingParams.count) {
|
||||
loadAndDisplayBooks();
|
||||
}
|
||||
@@ -378,9 +399,9 @@
|
||||
footer = document.getElementById('kiwixfooter');
|
||||
fadeOutDiv = document.getElementById('fadeOut');
|
||||
loader = document.querySelector('.loader');
|
||||
await loadAndDisplayBooks();
|
||||
await loadAndDisplayOptions('#languageFilter', `${root}/catalog/v2/languages`, 'language');
|
||||
await loadAndDisplayOptions('#categoryFilter', `${root}/catalog/v2/categories`, 'title');
|
||||
await loadAndDisplayBooks();
|
||||
document.querySelectorAll('.filter').forEach(filter => {
|
||||
filter.addEventListener('change', () => {resetAndFilter(filter.name, filter.value)});
|
||||
});
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
// Source: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const iso6391To3 = {
|
||||
"aa": "aar",
|
||||
"ab": "abk",
|
||||
"ae": "ave",
|
||||
"af": "afr",
|
||||
"ak": "aka",
|
||||
"am": "amh",
|
||||
"an": "arg",
|
||||
"ar": "ara",
|
||||
"as": "asm",
|
||||
"av": "ava",
|
||||
"ay": "aym",
|
||||
"az": "aze",
|
||||
"ba": "bak",
|
||||
"be": "bel",
|
||||
"bg": "bul",
|
||||
"bi": "bis",
|
||||
"bm": "bam",
|
||||
"bn": "ben",
|
||||
"bo": "bod",
|
||||
@@ -17,24 +24,33 @@ const iso6391To3 = {
|
||||
"bs": "bos",
|
||||
"ca": "cat",
|
||||
"ce": "che",
|
||||
"ch": "cha",
|
||||
"co": "cos",
|
||||
"cr": "cre",
|
||||
"cs": "ces",
|
||||
"cu": "chu",
|
||||
"cv": "chv",
|
||||
"cy": "cym",
|
||||
"da": "dan",
|
||||
"de": "deu",
|
||||
"dv": "div",
|
||||
"dz": "dzo",
|
||||
"el": "ell",
|
||||
"ee": "ewe",
|
||||
"en": "eng",
|
||||
"eo": "epo",
|
||||
"es": "spa",
|
||||
"et": "est",
|
||||
"eu": "eus",
|
||||
"fa": "fas",
|
||||
"ff": "ful",
|
||||
"fi": "fin",
|
||||
"fj": "fij",
|
||||
"fo": "fao",
|
||||
"fr": "fra",
|
||||
"fy": "fry",
|
||||
"ga": "gle",
|
||||
"gd": "gla",
|
||||
"gl": "glg",
|
||||
"gn": "grn",
|
||||
"gu": "guj",
|
||||
@@ -42,44 +58,78 @@ const iso6391To3 = {
|
||||
"ha": "hau",
|
||||
"he": "heb",
|
||||
"hi": "hin",
|
||||
"ho": "hmo",
|
||||
"hr": "hrv",
|
||||
"ht": "hat",
|
||||
"hu": "hun",
|
||||
"hy": "hye",
|
||||
"hz": "her",
|
||||
"ia": "ina",
|
||||
"id": "ind",
|
||||
"ie": "ile",
|
||||
"ig": "ibo",
|
||||
"ii": "iii",
|
||||
"ik": "ipk",
|
||||
"io": "ido",
|
||||
"is": "isl",
|
||||
"it": "ita",
|
||||
"iu": "iku",
|
||||
"ja": "jpn",
|
||||
"jv": "jav",
|
||||
"ka": "kat",
|
||||
"kg": "kon",
|
||||
"ki": "kik",
|
||||
"kj": "kua",
|
||||
"kl": "kal",
|
||||
"kk": "kaz",
|
||||
"km": "khm",
|
||||
"kn": "kan",
|
||||
"ko": "kor",
|
||||
"kr": "kau",
|
||||
"ks": "kas",
|
||||
"ku": "kur",
|
||||
"kv": "kom",
|
||||
"kw": "cor",
|
||||
"ky": "kir",
|
||||
"la": "lat",
|
||||
"lb": "ltz",
|
||||
"lg": "lug",
|
||||
"li": "lim",
|
||||
"ln": "lin",
|
||||
"lo": "lao",
|
||||
"lt": "lit",
|
||||
"lu": "lub",
|
||||
"lv": "lav",
|
||||
"mg": "mlg",
|
||||
"mh": "mah",
|
||||
"mi": "mri",
|
||||
"mk": "mkd",
|
||||
"ml": "mal",
|
||||
"mn": "mon",
|
||||
"mr": "mar",
|
||||
"ms": "msa",
|
||||
"mt": "mlt",
|
||||
"my": "mya",
|
||||
"na": "nau",
|
||||
"nb": "nob",
|
||||
"nd": "nde",
|
||||
"ne": "nep",
|
||||
"ng": "ndo",
|
||||
"nl": "nld",
|
||||
"nn": "nno",
|
||||
"no": "nor",
|
||||
"nr": "nbl",
|
||||
"nv": "nav",
|
||||
"ny": "nya",
|
||||
"oc": "oci",
|
||||
"oj": "oji",
|
||||
"om": "orm",
|
||||
"or": "ori",
|
||||
"os": "oss",
|
||||
"pa": "pan",
|
||||
"pi": "pli",
|
||||
"pl": "pol",
|
||||
"ps": "pus",
|
||||
"pt": "por",
|
||||
"qu": "que",
|
||||
"rm": "roh",
|
||||
@@ -88,7 +138,10 @@ const iso6391To3 = {
|
||||
"ru": "rus",
|
||||
"rw": "kin",
|
||||
"sa": "san",
|
||||
"sc": "srd",
|
||||
"sd": "snd",
|
||||
"se": "sme",
|
||||
"sm": "smo",
|
||||
"sg": "sag",
|
||||
"si": "sin",
|
||||
"sk": "slk",
|
||||
@@ -98,27 +151,37 @@ const iso6391To3 = {
|
||||
"sq": "sqi",
|
||||
"sr": "srp",
|
||||
"ss": "ssw",
|
||||
"st": "sot",
|
||||
"su": "sun",
|
||||
"sv": "swe",
|
||||
"sw": "swa",
|
||||
"ta": "tam",
|
||||
"te": "tel",
|
||||
"tg": "tgk",
|
||||
"th": "tha",
|
||||
"ti": "tir",
|
||||
"tk": "tuk",
|
||||
"tl": "tgl",
|
||||
"tn": "tsn",
|
||||
"to": "ton",
|
||||
"tr": "tur",
|
||||
"ts": "tso",
|
||||
"tt": "tat",
|
||||
"tw": "twi",
|
||||
"ty": "tah",
|
||||
"ug": "uig",
|
||||
"uk": "ukr",
|
||||
"ur": "urd",
|
||||
"uz": "uzb",
|
||||
"ve": "ven",
|
||||
"vi": "vie",
|
||||
"vo": "vol",
|
||||
"wa": "wln",
|
||||
"wo": "wol",
|
||||
"xh": "xho",
|
||||
"yi": "yid",
|
||||
"yo": "yor",
|
||||
"za": "zha",
|
||||
"zh": "zho",
|
||||
"zu": "zul"
|
||||
}
|
||||
3563
static/skin/isotope.pkgd.js
Normal file
3563
static/skin/isotope.pkgd.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -6,9 +6,11 @@
|
||||
</head>
|
||||
<body>
|
||||
<h1>Not Found</h1>
|
||||
{{#url}}
|
||||
<p>
|
||||
The requested URL "{{url}}" was not found on this server.
|
||||
</p>
|
||||
{{/url}}
|
||||
{{#details}}
|
||||
<p>
|
||||
{{{details}}}
|
||||
|
||||
@@ -15,12 +15,10 @@
|
||||
<tags>{{tags}}</tags>
|
||||
<articleCount>{{article_count}}</articleCount>
|
||||
<mediaCount>{{media_count}}</mediaCount>
|
||||
{{#icons}}
|
||||
<link rel="http://opds-spec.org/image/thumbnail"
|
||||
href="/meta?name=Illustration_{{icon_width}}x{{icon_height}}@{{icon_scale}}&content={{{content_id}}}"
|
||||
type="image/png;width={{icon_width}};height={{icon_height}};scale={{icon_scale}}"/>
|
||||
{{/icons}}
|
||||
<link type="text/html" href="/{{{content_id}}}" />
|
||||
{{#icons}}<link rel="http://opds-spec.org/image/thumbnail"
|
||||
href="{{root}}/catalog/v2/illustration/{{{content_id}}}/?size={{icon_size}}"
|
||||
type="{{icon_mimetype}};width={{icon_size}};height={{icon_size}};scale=1"/>
|
||||
{{/icons}}<link type="text/html" href="{{root}}/{{{content_id}}}" />
|
||||
<author>
|
||||
<name>{{author_name}}</name>
|
||||
</author>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
/>
|
||||
<link
|
||||
type="text/css"
|
||||
href="{{root}}/skin/home.css"
|
||||
href="{{root}}/skin/index.css"
|
||||
rel="Stylesheet"
|
||||
/>
|
||||
<style>
|
||||
|
||||
@@ -5,18 +5,18 @@
|
||||
<form class="kiwixsearch" method="GET" action="{{root}}/search" id="kiwixsearchform">
|
||||
{{#hascontent}}<input type="hidden" name="content" value="{{content}}" />{{/hascontent}}
|
||||
<label for="kiwixsearchbox">🔍</label>
|
||||
<input autocomplete="off" class="ui-autocomplete-input" id="kiwixsearchbox" name="pattern" type="text">
|
||||
<input autocomplete="off" class="ui-autocomplete-input" id="kiwixsearchbox" name="pattern" type="text" title="Search '{{title}}'" aria-label="Search '{{title}}'">
|
||||
</form>
|
||||
</div>
|
||||
<input type="checkbox" id="kiwix_button_show_toggle">
|
||||
<label for="kiwix_button_show_toggle"><img src="{{root}}/skin/caret.png" alt=""></label>
|
||||
<div class="kiwix_button_cont">
|
||||
{{#withlibrarybutton}}
|
||||
<a id="kiwix_serve_taskbar_library_button" href="{{root}}/"><button>🏠</button></a>
|
||||
<a id="kiwix_serve_taskbar_library_button" title="Go to welcome page" aria-label="Go to welcome page" href="{{root}}/"><button>🏠</button></a>
|
||||
{{/withlibrarybutton}}
|
||||
{{#hascontent}}
|
||||
<a id="kiwix_serve_taskbar_home_button" href="{{root}}/{{content}}/"><button>{{title}}</button></a>
|
||||
<a id="kiwix_serve_taskbar_random_button"
|
||||
<a id="kiwix_serve_taskbar_home_button" title="Go to the main page of '{{title}}'" aria-label="Go to the main page of '{{title}}'" href="{{root}}/{{content}}/"><button>{{title}}</button></a>
|
||||
<a id="kiwix_serve_taskbar_random_button" title="Go to a randomly selected page" aria-label="Go to a randomly selected page"
|
||||
href="{{root}}/random?content={{#urlencoded}}{{{content}}}{{/urlencoded}}"><button>🎲</button></a>
|
||||
{{/hascontent}}
|
||||
</div>
|
||||
|
||||
116
test/book.cpp
116
test/book.cpp
@@ -2,42 +2,6 @@
|
||||
#include "../include/book.h"
|
||||
#include <pugixml.hpp>
|
||||
|
||||
TEST(BookTest, updateTest)
|
||||
{
|
||||
kiwix::Book book;
|
||||
|
||||
book.setId("xyz");
|
||||
book.setReadOnly(false);
|
||||
book.setPath("/home/user/Downloads/skin-of-color-society_en_all_2019-11.zim");
|
||||
book.setPathValid(true);
|
||||
book.setUrl("book-url");
|
||||
book.setTags("youtube;_videos:yes;_ftindex:yes;_ftindex:yes;_pictures:yes;_details:yes");
|
||||
book.setName("skin-of-color-society_en_all");
|
||||
book.setFavicon("book-favicon");
|
||||
book.setFaviconMimeType("book-favicon-mimetype");
|
||||
|
||||
kiwix::Book newBook;
|
||||
|
||||
newBook.setReadOnly(true);
|
||||
EXPECT_FALSE(newBook.update(book));
|
||||
|
||||
newBook.setReadOnly(false);
|
||||
EXPECT_FALSE(newBook.update(book));
|
||||
|
||||
newBook.setId("xyz");
|
||||
EXPECT_TRUE(newBook.update(book));
|
||||
|
||||
EXPECT_EQ(newBook.readOnly(), book.readOnly());
|
||||
EXPECT_EQ(newBook.getPath(), book.getPath());
|
||||
EXPECT_EQ(newBook.isPathValid(), book.isPathValid());
|
||||
EXPECT_EQ(newBook.getUrl(), book.getUrl());
|
||||
EXPECT_EQ(newBook.getTags(), book.getTags());
|
||||
EXPECT_EQ(newBook.getCategory(), book.getCategory());
|
||||
EXPECT_EQ(newBook.getName(), book.getName());
|
||||
EXPECT_EQ(newBook.getFavicon(), book.getFavicon());
|
||||
EXPECT_EQ(newBook.getFaviconMimeType(), book.getFaviconMimeType());
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
@@ -68,6 +32,9 @@ TEST(BookTest, updateFromXMLTest)
|
||||
articleCount="123456"
|
||||
mediaCount="234567"
|
||||
size="345678"
|
||||
favicon="ZmFrZS1ib29rLWZhdmljb24tZGF0YQ=="
|
||||
faviconMimeType="text/plain"
|
||||
faviconUrl="http://who.org/zara.fav"
|
||||
>
|
||||
</book>
|
||||
)");
|
||||
@@ -85,6 +52,10 @@ TEST(BookTest, updateFromXMLTest)
|
||||
EXPECT_EQ(book.getArticleCount(), 123456U);
|
||||
EXPECT_EQ(book.getMediaCount(), 234567U);
|
||||
EXPECT_EQ(book.getSize(), 345678U*1024U);
|
||||
auto defaultIllustration = book.getIllustration(48);
|
||||
EXPECT_EQ(defaultIllustration->getData(), "fake-book-favicon-data");
|
||||
EXPECT_EQ(defaultIllustration->mimeType, "text/plain");
|
||||
EXPECT_EQ(defaultIllustration->url, "http://who.org/zara.fav");
|
||||
}
|
||||
|
||||
TEST(BookTest, updateFromXMLCategoryHandlingTest)
|
||||
@@ -166,3 +137,76 @@ TEST(BookTest, updateCopiesCategory)
|
||||
newBook.update(book);
|
||||
EXPECT_EQ(newBook.getCategory(), "ted");
|
||||
}
|
||||
|
||||
TEST(BookTest, updateTest)
|
||||
{
|
||||
const XMLDoc xml(R"(
|
||||
<book id="xyz"
|
||||
path="/home/user/Downloads/skin-of-color-society_en_all_2019-11.zim"
|
||||
url="book-url"
|
||||
name="skin-of-color-society_en_all"
|
||||
tags="youtube;_videos:yes;_ftindex:yes;_ftindex:yes;_pictures:yes;_details:yes"
|
||||
favicon="Ym9vay1mYXZpY29u"
|
||||
faviconMimeType="book-favicon-mimetype"
|
||||
>
|
||||
</book>
|
||||
)");
|
||||
|
||||
kiwix::Book book;
|
||||
book.updateFromXml(xml.child("book"), "/data/zim");
|
||||
|
||||
book.setReadOnly(false);
|
||||
book.setPathValid(true);
|
||||
|
||||
kiwix::Book newBook;
|
||||
|
||||
newBook.setReadOnly(true);
|
||||
EXPECT_FALSE(newBook.update(book));
|
||||
|
||||
newBook.setReadOnly(false);
|
||||
EXPECT_FALSE(newBook.update(book));
|
||||
|
||||
newBook.setId("xyz");
|
||||
EXPECT_TRUE(newBook.update(book));
|
||||
|
||||
EXPECT_EQ(newBook.readOnly(), book.readOnly());
|
||||
EXPECT_EQ(newBook.getPath(), book.getPath());
|
||||
EXPECT_EQ(newBook.isPathValid(), book.isPathValid());
|
||||
EXPECT_EQ(newBook.getUrl(), book.getUrl());
|
||||
EXPECT_EQ(newBook.getTags(), book.getTags());
|
||||
EXPECT_EQ(newBook.getCategory(), book.getCategory());
|
||||
EXPECT_EQ(newBook.getName(), book.getName());
|
||||
auto defaultIllustration = book.getIllustration(48);
|
||||
auto newDefaultIllustration = newBook.getIllustration(48);
|
||||
EXPECT_EQ(newDefaultIllustration->getData(), defaultIllustration->getData());
|
||||
EXPECT_EQ(newDefaultIllustration->mimeType, defaultIllustration->mimeType);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
std::string path2HumanReadableId(const std::string& path)
|
||||
{
|
||||
const XMLDoc xml("<book id=\"xyz\" path=\"" + path + "\"></book>");
|
||||
|
||||
kiwix::Book book;
|
||||
book.updateFromXml(xml.child("book"), "/data/zim");
|
||||
return book.getHumanReadableIdFromPath();
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
TEST(BookTest, getHumanReadableIdFromPath)
|
||||
{
|
||||
EXPECT_EQ("abc", path2HumanReadableId("abc.zim"));
|
||||
EXPECT_EQ("abc", path2HumanReadableId("ABC.zim"));
|
||||
EXPECT_EQ("abc", path2HumanReadableId("âbç.zim"));
|
||||
EXPECT_EQ("ancient", path2HumanReadableId("ancient.zimbabwe"));
|
||||
EXPECT_EQ("ab_cd", path2HumanReadableId("ab cd.zim"));
|
||||
#ifdef _WIN32
|
||||
EXPECT_EQ("abc", path2HumanReadableId("C:\\Data\\ZIM\\abc.zim"));
|
||||
#else
|
||||
EXPECT_EQ("abc", path2HumanReadableId("/Data/ZIM/abc.zim"));
|
||||
#endif
|
||||
EXPECT_EQ("3plus2", path2HumanReadableId("3+2.zim"));
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
articleCount="284"
|
||||
mediaCount="2"
|
||||
size="556"
|
||||
faviconMimeType="image/png"
|
||||
favicon="SOME DATA"
|
||||
></book>
|
||||
<book
|
||||
id="raycharles_uncategorized"
|
||||
@@ -33,8 +35,8 @@
|
||||
></book>
|
||||
<book
|
||||
id="charlesray"
|
||||
path="./zimfile.zim"
|
||||
url="https://github.com/kiwix/libkiwix/raw/master/test/data/zimfile.zim"
|
||||
path="./zimfile&other.zim"
|
||||
url="https://github.com/kiwix/libkiwix/raw/master/test/data/zimfile%26other.zim"
|
||||
title="Charles, Ray"
|
||||
description="Wikipedia articles about Ray Charles"
|
||||
language="fra"
|
||||
|
||||
1
test/data/zimfile&other.zim
Symbolic link
1
test/data/zimfile&other.zim
Symbolic link
@@ -0,0 +1 @@
|
||||
zimfile.zim
|
||||
@@ -2889,7 +2889,7 @@ inline ssize_t Stream::write(const std::string &s) {
|
||||
|
||||
template <typename... Args>
|
||||
inline ssize_t Stream::write_format(const char *fmt, const Args &... args) {
|
||||
std::array<char, 2048> buf;
|
||||
std::array<char, 2048> buf{};
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER < 1900
|
||||
auto sn = _snprintf_s(buf, bufsiz, buf.size() - 1, fmt, args...);
|
||||
@@ -3756,7 +3756,7 @@ inline socket_t Client::create_client_socket() const {
|
||||
}
|
||||
|
||||
inline bool Client::read_response_line(Stream &strm, Response &res) {
|
||||
std::array<char, 2048> buf;
|
||||
std::array<char, 2048> buf{};
|
||||
|
||||
detail::stream_line_reader line_reader(strm, buf.data(), buf.size());
|
||||
|
||||
|
||||
@@ -232,7 +232,7 @@ class LibraryTest : public ::testing::Test {
|
||||
void SetUp() override {
|
||||
kiwix::Manager manager(&lib);
|
||||
manager.readOpds(sampleOpdsStream, "foo.urlHost");
|
||||
manager.readXml(sampleLibraryXML, true, "./test/library.xml", true);
|
||||
manager.readXml(sampleLibraryXML, false, "./test/library.xml", true);
|
||||
}
|
||||
|
||||
kiwix::Bookmark createBookmark(const std::string &id) {
|
||||
@@ -660,13 +660,14 @@ TEST_F(LibraryTest, filterByMultipleCriteria)
|
||||
|
||||
TEST_F(LibraryTest, getBookByPath)
|
||||
{
|
||||
auto& 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);
|
||||
}
|
||||
@@ -683,9 +684,9 @@ TEST_F(LibraryTest, removeBookByIdRemovesTheBook)
|
||||
|
||||
TEST_F(LibraryTest, removeBookByIdDropsTheReader)
|
||||
{
|
||||
EXPECT_NE(nullptr, lib.getReaderById("raycharles"));
|
||||
EXPECT_NE(nullptr, lib.getArchiveById("raycharles"));
|
||||
lib.removeBookById("raycharles");
|
||||
EXPECT_THROW(lib.getReaderById("raycharles"), std::out_of_range);
|
||||
EXPECT_THROW(lib.getArchiveById("raycharles"), std::out_of_range);
|
||||
};
|
||||
|
||||
TEST_F(LibraryTest, removeBookByIdUpdatesTheSearchDB)
|
||||
@@ -706,4 +707,35 @@ TEST_F(LibraryTest, removeBookByIdUpdatesTheSearchDB)
|
||||
EXPECT_THROW(lib.getBookById("raycharles"), std::out_of_range);
|
||||
};
|
||||
|
||||
TEST_F(LibraryTest, removeBooksNotUpdatedSince)
|
||||
{
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter(),
|
||||
"An example ZIM archive",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Granblue Fantasy Wiki",
|
||||
"Géographie par Wikipédia",
|
||||
"Islam Stack Exchange",
|
||||
"Mathématiques",
|
||||
"Movies & TV Stack Exchange",
|
||||
"Mythology & Folklore Stack Exchange",
|
||||
"Ray Charles",
|
||||
"TED talks - Business",
|
||||
"Tania Louis",
|
||||
"Wikiquote"
|
||||
);
|
||||
|
||||
const uint64_t rev = lib.getRevision();
|
||||
for ( const auto& id : lib.filter(kiwix::Filter().query("exchange")) ) {
|
||||
lib.addBook(lib.getBookByIdThreadSafe(id));
|
||||
}
|
||||
|
||||
EXPECT_EQ(9u, lib.removeBooksNotUpdatedSince(rev));
|
||||
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter(),
|
||||
"Islam Stack Exchange",
|
||||
"Movies & TV Stack Exchange",
|
||||
"Mythology & Folklore Stack Exchange",
|
||||
);
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
@@ -67,3 +67,29 @@ TEST(ManagerTest, readXml)
|
||||
EXPECT_EQ(45U, book.getMediaCount());
|
||||
EXPECT_EQ(678U*1024, book.getSize());
|
||||
}
|
||||
|
||||
TEST(Manager, reload)
|
||||
{
|
||||
kiwix::Library lib;
|
||||
kiwix::Manager manager(&lib);
|
||||
|
||||
manager.reload({ "./test/library.xml" });
|
||||
EXPECT_EQ(lib.getBooksIds(), (kiwix::Library::BookIdCollection{
|
||||
"charlesray",
|
||||
"raycharles",
|
||||
"raycharles_uncategorized"
|
||||
}));
|
||||
|
||||
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({
|
||||
"charlesray",
|
||||
"raycharles",
|
||||
"raycharles_uncategorized"
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -8,9 +8,8 @@ tests = [
|
||||
'kiwixserve',
|
||||
'book',
|
||||
'manager',
|
||||
'opds_catalog',
|
||||
'reader',
|
||||
'searcher'
|
||||
'name_mapper',
|
||||
'opds_catalog'
|
||||
]
|
||||
|
||||
if build_machine.system() != 'windows'
|
||||
@@ -28,6 +27,7 @@ if gtest_dep.found() and not meson.is_cross_build()
|
||||
data_files = [
|
||||
'example.zim',
|
||||
'zimfile.zim',
|
||||
'zimfile&other.zim',
|
||||
'corner_cases.zim',
|
||||
'library.xml'
|
||||
]
|
||||
|
||||
147
test/name_mapper.cpp
Normal file
147
test/name_mapper.cpp
Normal file
@@ -0,0 +1,147 @@
|
||||
#include "../include/name_mapper.h"
|
||||
|
||||
#include "../include/library.h"
|
||||
#include "../include/manager.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
const char libraryXML[] = R"(
|
||||
<library version="1.0">
|
||||
<book id="01" path="/data/zero_one.zim"> </book>
|
||||
<book id="02" path="/data/zero two.zim"> </book>
|
||||
<book id="03" path="/data/ZERO thrêë.zim"> </book>
|
||||
<book id="04-2021-10" path="/data/zero_four_2021-10.zim"></book>
|
||||
<book id="04-2021-11" path="/data/zero_four_2021-11.zim"></book>
|
||||
</library>
|
||||
)";
|
||||
|
||||
class NameMapperTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
kiwix::Manager manager(&lib);
|
||||
manager.readXml(libraryXML, false, "./library.xml", true);
|
||||
for ( const std::string& id : lib.getBooksIds() ) {
|
||||
kiwix::Book bookCopy = lib.getBookById(id);
|
||||
bookCopy.setPathValid(true);
|
||||
lib.addBook(bookCopy);
|
||||
}
|
||||
}
|
||||
|
||||
kiwix::Library lib;
|
||||
};
|
||||
|
||||
class CapturedStderr
|
||||
{
|
||||
std::ostringstream buffer;
|
||||
std::streambuf* const sbuf;
|
||||
public:
|
||||
CapturedStderr()
|
||||
: sbuf(std::cerr.rdbuf())
|
||||
{
|
||||
std::cerr.rdbuf(buffer.rdbuf());
|
||||
}
|
||||
|
||||
CapturedStderr(const CapturedStderr&) = delete;
|
||||
|
||||
~CapturedStderr()
|
||||
{
|
||||
std::cerr.rdbuf(sbuf);
|
||||
}
|
||||
|
||||
operator std::string() const { return buffer.str(); }
|
||||
};
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
void checkUnaliasedEntriesInNameMapper(const kiwix::NameMapper& nm)
|
||||
{
|
||||
EXPECT_EQ("zero_one", nm.getNameForId("01"));
|
||||
EXPECT_EQ("zero_two", nm.getNameForId("02"));
|
||||
EXPECT_EQ("zero_three", nm.getNameForId("03"));
|
||||
EXPECT_EQ("zero_four_2021-10", nm.getNameForId("04-2021-10"));
|
||||
EXPECT_EQ("zero_four_2021-11", nm.getNameForId("04-2021-11"));
|
||||
|
||||
EXPECT_EQ("01", nm.getIdForName("zero_one"));
|
||||
EXPECT_EQ("02", nm.getIdForName("zero_two"));
|
||||
EXPECT_EQ("03", nm.getIdForName("zero_three"));
|
||||
EXPECT_EQ("04-2021-10", nm.getIdForName("zero_four_2021-10"));
|
||||
EXPECT_EQ("04-2021-11", nm.getIdForName("zero_four_2021-11"));
|
||||
}
|
||||
|
||||
TEST_F(NameMapperTest, HumanReadableNameMapperWithoutAliases)
|
||||
{
|
||||
CapturedStderr stderror;
|
||||
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");
|
||||
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);
|
||||
}
|
||||
|
||||
TEST_F(NameMapperTest, HumanReadableNameMapperWithAliases)
|
||||
{
|
||||
CapturedStderr stderror;
|
||||
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'."
|
||||
" Therefore, only /data/zero_four_2021-10.zim will be served.\n"
|
||||
, std::string(stderror)
|
||||
);
|
||||
|
||||
checkUnaliasedEntriesInNameMapper(nm);
|
||||
EXPECT_EQ("04-2021-10", nm.getIdForName("zero_four"));
|
||||
|
||||
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"));
|
||||
}
|
||||
|
||||
TEST_F(NameMapperTest, UpdatableNameMapperWithoutAliases)
|
||||
{
|
||||
CapturedStderr stderror;
|
||||
kiwix::UpdatableNameMapper 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");
|
||||
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);
|
||||
EXPECT_THROW(nm.getIdForName("zero_four"), std::out_of_range);
|
||||
}
|
||||
|
||||
TEST_F(NameMapperTest, UpdatableNameMapperWithAliases)
|
||||
{
|
||||
CapturedStderr stderror;
|
||||
kiwix::UpdatableNameMapper 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'."
|
||||
" Therefore, only /data/zero_four_2021-10.zim will be served.\n"
|
||||
, std::string(stderror)
|
||||
);
|
||||
|
||||
checkUnaliasedEntriesInNameMapper(nm);
|
||||
EXPECT_EQ("04-2021-10", nm.getIdForName("zero_four"));
|
||||
|
||||
{
|
||||
CapturedStderr nmUpdateStderror;
|
||||
lib.removeBookById("04-2021-10");
|
||||
nm.update();
|
||||
EXPECT_EQ("", std::string(nmUpdateStderror));
|
||||
}
|
||||
EXPECT_EQ("04-2021-11", nm.getIdForName("zero_four"));
|
||||
EXPECT_THROW(nm.getNameForId("04-2021-10"), std::out_of_range);
|
||||
EXPECT_THROW(nm.getIdForName("zero_four_2021-10"), std::out_of_range);
|
||||
}
|
||||
@@ -20,6 +20,10 @@
|
||||
#include "gtest/gtest.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#ifndef _WIN32
|
||||
# include <unistd.h>
|
||||
# include <fcntl.h>
|
||||
#endif
|
||||
#include "../include/tools.h"
|
||||
#include "../src/tools/pathTools.h"
|
||||
|
||||
@@ -190,6 +194,34 @@ TEST(pathTools, appendToDirectory)
|
||||
P5("a","b","c",".","foo.xml"));
|
||||
}
|
||||
|
||||
TEST(pathTools, fileExists)
|
||||
{
|
||||
ASSERT_TRUE(kiwix::fileExists(P3(".","test","example.zim")));
|
||||
ASSERT_FALSE(kiwix::fileExists(P3(".","test","noFile.zim")));
|
||||
}
|
||||
|
||||
TEST(pathTools, fileReadable)
|
||||
{
|
||||
ASSERT_TRUE(kiwix::fileReadable(P3(".","test","example.zim")));
|
||||
ASSERT_FALSE(kiwix::fileReadable(P3(".","test","noFile.zim")));
|
||||
#ifdef _POSIX_SOURCE
|
||||
std::string path = P3(".","test","example.zim");
|
||||
auto myprivs = geteuid();
|
||||
if (myprivs != 0) {
|
||||
int fd = open(path.c_str(), O_RDONLY);
|
||||
if (fd != -1) {
|
||||
int wResp = fchmod(fd, ~(S_IRWXU | S_IRWXG | S_IRWXO)); // remove all permissions
|
||||
if (wResp == 0) {
|
||||
EXPECT_FALSE(kiwix::fileReadable(P3(".","test","example.zim")));
|
||||
}
|
||||
int resetResp = fchmod(fd, S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP); // reset back permissions to -rw-rw-r--
|
||||
if (resetResp == 0) {
|
||||
EXPECT_TRUE(kiwix::fileReadable(P3(".","test","example.zim")));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(pathTools, goUp)
|
||||
{
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "../include/reader.h"
|
||||
#include "zim/archive.h"
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
/**
|
||||
* This test file is written primarily to demonstrate how Reader is simply a
|
||||
* wrapper over an archive. We will be dropping this wrapper soon.
|
||||
**/
|
||||
TEST (Reader, archiveWrapper) {
|
||||
Reader reader("./test/zimfile.zim");
|
||||
zim::Archive archive = *reader.getZimArchive();
|
||||
|
||||
std::ostringstream s;
|
||||
s << archive.getUuid();
|
||||
|
||||
ASSERT_EQ(reader.getId(), s.str());
|
||||
ASSERT_EQ(reader.getGlobalCount(), archive.getEntryCount());
|
||||
ASSERT_EQ(reader.getMainPage().getTitle(), archive.getMainEntry().getTitle());
|
||||
ASSERT_EQ(reader.hasFulltextIndex(), archive.hasFulltextIndex());
|
||||
ASSERT_NO_THROW(reader.getRandomPage());
|
||||
}
|
||||
|
||||
TEST (Reader, getFunctions) {
|
||||
zim::Archive archive("./test/zimfile.zim");
|
||||
Reader reader("./test/zimfile.zim");
|
||||
|
||||
auto archiveEntry = archive.getRandomEntry();
|
||||
ASSERT_TRUE(reader.pathExists(archiveEntry.getPath()));
|
||||
auto readerEntry = reader.getEntryFromPath(archiveEntry.getPath());
|
||||
ASSERT_EQ(readerEntry.getTitle(), archiveEntry.getTitle());
|
||||
|
||||
ASSERT_FALSE(reader.pathExists("invalidEntryPath"));
|
||||
ASSERT_THROW(reader.getEntryFromPath("invalidEntryPath"), NoEntry);
|
||||
|
||||
readerEntry = reader.getEntryFromTitle(archiveEntry.getTitle());
|
||||
ASSERT_EQ(readerEntry.getTitle(), archiveEntry.getTitle());
|
||||
}
|
||||
|
||||
TEST (Reader, suggestions) {
|
||||
Reader reader("./test/zimfile.zim");
|
||||
SuggestionsList_t suggestions;
|
||||
reader.searchSuggestionsSmart("The Genius", 4, suggestions);
|
||||
|
||||
std::vector<std::string> suggestionResult, expectedResult;
|
||||
std::string suggestionTitle;
|
||||
for (auto it = suggestions.begin(); it != suggestions.end(); it++) {
|
||||
suggestionResult.push_back(it->getTitle());
|
||||
}
|
||||
|
||||
expectedResult = {
|
||||
"The Genius After Hours",
|
||||
"The Genius Hits the Road",
|
||||
"The Genius Sings the Blues",
|
||||
"The Genius of Ray Charles"
|
||||
};
|
||||
|
||||
ASSERT_EQ(suggestionResult, expectedResult);
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
#include "gtest/gtest.h"
|
||||
#include "../include/searcher.h"
|
||||
#include "../include/reader.h"
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
TEST(Searcher, search) {
|
||||
Reader reader("./test/example.zim");
|
||||
|
||||
Searcher searcher;
|
||||
searcher.add_reader(&reader);
|
||||
ASSERT_EQ(searcher.get_reader(0)->getTitle(), reader.getTitle());
|
||||
|
||||
searcher.search("wiki", 0, 2);
|
||||
searcher.restart_search();
|
||||
ASSERT_EQ(searcher.getEstimatedResultCount(), (unsigned int)2);
|
||||
|
||||
auto result = searcher.getNextResult();
|
||||
ASSERT_EQ(result->get_title(), "FreedomBox for Communities/Offline Wikipedia - Wikibooks, open books for an open world");
|
||||
result = searcher.getNextResult();
|
||||
ASSERT_EQ(result->get_title(), "Wikibooks");
|
||||
}
|
||||
|
||||
TEST(Searcher, suggestion) {
|
||||
Reader reader("./test/zimfile.zim");
|
||||
|
||||
Searcher searcher;
|
||||
searcher.add_reader(&reader);
|
||||
ASSERT_EQ(searcher.get_reader(0)->getTitle(), reader.getTitle());
|
||||
|
||||
std::string query = "ray";
|
||||
searcher.suggestions(query, true);
|
||||
searcher.restart_search();
|
||||
|
||||
auto result = searcher.getNextResult();
|
||||
ASSERT_EQ(result->get_title(), "Charles, Ray");
|
||||
ASSERT_EQ(result->get_url(), "A/Charles,_Ray");
|
||||
ASSERT_EQ(result->get_snippet(), "Charles, <b>Ray</b>");
|
||||
ASSERT_EQ(result->get_score(), 0);
|
||||
ASSERT_EQ(result->get_content(), "");
|
||||
ASSERT_EQ(result->get_size(), 0);
|
||||
ASSERT_EQ(result->get_wordCount(), 0);
|
||||
ASSERT_EQ(result->get_zimId(), "");
|
||||
|
||||
result = searcher.getNextResult();
|
||||
ASSERT_EQ(result->get_title(), "Ray (film)");
|
||||
}
|
||||
|
||||
TEST(Searcher, incrementalRange) {
|
||||
// Attempt to get 50 results in steps of 5
|
||||
zim::Archive archive("./test/zimfile.zim");
|
||||
zim::Searcher ftsearcher(archive);
|
||||
zim::Query query;
|
||||
query.setQuery("ray");
|
||||
auto search = ftsearcher.search(query);
|
||||
|
||||
int suggCount = 0;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
auto srs = search.getResults(i*5, 5); // get 5 results
|
||||
ASSERT_EQ(srs.size(), 5);
|
||||
suggCount += srs.size();
|
||||
}
|
||||
ASSERT_EQ(suggCount, 50);
|
||||
}
|
||||
|
||||
}
|
||||
312
test/server.cpp
312
test/server.cpp
@@ -84,7 +84,6 @@ ZimFileServer::ZimFileServer(int serverPort, std::string libraryFilePath)
|
||||
if ( kiwix::isRelativePath(libraryFilePath) )
|
||||
libraryFilePath = kiwix::computeAbsolutePath(kiwix::getCurrentDirectory(), libraryFilePath);
|
||||
manager.readFile(libraryFilePath, true, true);
|
||||
|
||||
run(serverPort);
|
||||
}
|
||||
|
||||
@@ -95,7 +94,6 @@ ZimFileServer::ZimFileServer(int serverPort, const FilePathCollection& zimpaths,
|
||||
if (!manager.addBookFromPath(zimpath, zimpath, "", false))
|
||||
throw std::runtime_error("Unable to add the ZIM file '" + zimpath + "'");
|
||||
}
|
||||
|
||||
run(serverPort, indexTemplateString);
|
||||
}
|
||||
|
||||
@@ -104,6 +102,7 @@ void ZimFileServer::run(int serverPort, std::string indexTemplateString)
|
||||
const std::string address = "127.0.0.1";
|
||||
nameMapper.reset(new kiwix::HumanReadableNameMapper(library, false));
|
||||
server.reset(new kiwix::Server(&library, nameMapper.get()));
|
||||
server->setRoot("ROOT");
|
||||
server->setAddress(address);
|
||||
server->setPort(serverPort);
|
||||
server->setNbThreads(2);
|
||||
@@ -162,51 +161,59 @@ std::ostream& operator<<(std::ostream& out, const Resource& r)
|
||||
typedef std::vector<Resource> ResourceCollection;
|
||||
|
||||
const ResourceCollection resources200Compressible{
|
||||
{ WITH_ETAG, "/" },
|
||||
{ WITH_ETAG, "/ROOT/" },
|
||||
|
||||
{ WITH_ETAG, "/skin/jquery-ui/jquery-ui.structure.min.css" },
|
||||
{ WITH_ETAG, "/skin/jquery-ui/jquery-ui.min.js" },
|
||||
{ WITH_ETAG, "/skin/jquery-ui/external/jquery/jquery.js" },
|
||||
{ WITH_ETAG, "/skin/jquery-ui/jquery-ui.theme.min.css" },
|
||||
{ WITH_ETAG, "/skin/jquery-ui/jquery-ui.min.css" },
|
||||
{ WITH_ETAG, "/skin/taskbar.js" },
|
||||
{ WITH_ETAG, "/skin/taskbar.css" },
|
||||
{ WITH_ETAG, "/skin/block_external.js" },
|
||||
{ WITH_ETAG, "/ROOT/skin/jquery-ui/jquery-ui.structure.min.css" },
|
||||
{ WITH_ETAG, "/ROOT/skin/jquery-ui/jquery-ui.min.js" },
|
||||
{ WITH_ETAG, "/ROOT/skin/jquery-ui/external/jquery/jquery.js" },
|
||||
{ WITH_ETAG, "/ROOT/skin/jquery-ui/jquery-ui.theme.min.css" },
|
||||
{ WITH_ETAG, "/ROOT/skin/jquery-ui/jquery-ui.min.css" },
|
||||
{ WITH_ETAG, "/ROOT/skin/taskbar.js" },
|
||||
{ WITH_ETAG, "/ROOT/skin/taskbar.css" },
|
||||
{ WITH_ETAG, "/ROOT/skin/block_external.js" },
|
||||
|
||||
{ NO_ETAG, "/catalog/root.xml" },
|
||||
{ NO_ETAG, "/catalog/searchdescription.xml" },
|
||||
{ NO_ETAG, "/catalog/search" },
|
||||
{ NO_ETAG, "/ROOT/catalog/root.xml" },
|
||||
{ NO_ETAG, "/ROOT/catalog/searchdescription.xml" },
|
||||
{ NO_ETAG, "/ROOT/catalog/search" },
|
||||
|
||||
{ NO_ETAG, "/search?content=zimfile&pattern=a" },
|
||||
{ NO_ETAG, "/ROOT/search?content=zimfile&pattern=a" },
|
||||
|
||||
{ NO_ETAG, "/suggest?content=zimfile&term=ray" },
|
||||
{ NO_ETAG, "/ROOT/suggest?content=zimfile" },
|
||||
{ NO_ETAG, "/ROOT/suggest?content=zimfile&term=ray" },
|
||||
|
||||
{ NO_ETAG, "/catch/external?source=www.example.com" },
|
||||
{ NO_ETAG, "/ROOT/catch/external?source=www.example.com" },
|
||||
|
||||
{ WITH_ETAG, "/zimfile/A/index" },
|
||||
{ WITH_ETAG, "/zimfile/A/Ray_Charles" },
|
||||
{ WITH_ETAG, "/ROOT/zimfile/A/index" },
|
||||
{ WITH_ETAG, "/ROOT/zimfile/A/Ray_Charles" },
|
||||
|
||||
{ WITH_ETAG, "/ROOT/raw/zimfile/content/A/index" },
|
||||
{ WITH_ETAG, "/ROOT/raw/zimfile/content/A/Ray_Charles" },
|
||||
};
|
||||
|
||||
const ResourceCollection resources200Uncompressible{
|
||||
{ WITH_ETAG, "/skin/jquery-ui/images/animated-overlay.gif" },
|
||||
{ WITH_ETAG, "/skin/caret.png" },
|
||||
{ WITH_ETAG, "/ROOT/skin/jquery-ui/images/animated-overlay.gif" },
|
||||
{ WITH_ETAG, "/ROOT/skin/caret.png" },
|
||||
|
||||
{ WITH_ETAG, "/meta?content=zimfile&name=title" },
|
||||
{ WITH_ETAG, "/meta?content=zimfile&name=description" },
|
||||
{ WITH_ETAG, "/meta?content=zimfile&name=language" },
|
||||
{ WITH_ETAG, "/meta?content=zimfile&name=name" },
|
||||
{ WITH_ETAG, "/meta?content=zimfile&name=tags" },
|
||||
{ WITH_ETAG, "/meta?content=zimfile&name=date" },
|
||||
{ WITH_ETAG, "/meta?content=zimfile&name=creator" },
|
||||
{ WITH_ETAG, "/meta?content=zimfile&name=publisher" },
|
||||
{ WITH_ETAG, "/meta?content=zimfile&name=favicon" },
|
||||
{ WITH_ETAG, "/meta?content=zimfile&name=Illustration_48x48@1" },
|
||||
{ WITH_ETAG, "/ROOT/raw/zimfile/meta/Title" },
|
||||
{ WITH_ETAG, "/ROOT/raw/zimfile/meta/Description" },
|
||||
{ WITH_ETAG, "/ROOT/raw/zimfile/meta/Language" },
|
||||
{ WITH_ETAG, "/ROOT/raw/zimfile/meta/Name" },
|
||||
{ WITH_ETAG, "/ROOT/raw/zimfile/meta/Tags" },
|
||||
{ WITH_ETAG, "/ROOT/raw/zimfile/meta/Date" },
|
||||
{ WITH_ETAG, "/ROOT/raw/zimfile/meta/Creator" },
|
||||
{ WITH_ETAG, "/ROOT/raw/zimfile/meta/Publisher" },
|
||||
|
||||
{ WITH_ETAG, "/zimfile/I/m/Ray_Charles_classic_piano_pose.jpg" },
|
||||
{ NO_ETAG, "/ROOT/catalog/v2/illustration/zimfile?size=48" },
|
||||
|
||||
{ WITH_ETAG, "/corner_cases/A/empty.html" },
|
||||
{ WITH_ETAG, "/corner_cases/-/empty.css" },
|
||||
{ WITH_ETAG, "/corner_cases/-/empty.js" },
|
||||
{ WITH_ETAG, "/ROOT/zimfile/I/m/Ray_Charles_classic_piano_pose.jpg" },
|
||||
|
||||
{ WITH_ETAG, "/ROOT/corner_cases/A/empty.html" },
|
||||
{ WITH_ETAG, "/ROOT/corner_cases/-/empty.css" },
|
||||
{ WITH_ETAG, "/ROOT/corner_cases/-/empty.js" },
|
||||
|
||||
// The title and creator are too small to be compressed
|
||||
{ WITH_ETAG, "/ROOT/raw/zimfile/meta/Creator" },
|
||||
{ WITH_ETAG, "/ROOT/raw/zimfile/meta/Title" },
|
||||
};
|
||||
|
||||
ResourceCollection all200Resources()
|
||||
@@ -222,7 +229,7 @@ TEST(indexTemplateStringTest, emptyIndexTemplate) {
|
||||
};
|
||||
|
||||
ZimFileServer zfs(PORT, ZIMFILES, "");
|
||||
EXPECT_EQ(200, zfs.GET("/")->status);
|
||||
EXPECT_EQ(200, zfs.GET("/ROOT/")->status);
|
||||
}
|
||||
|
||||
TEST(indexTemplateStringTest, indexTemplateCheck) {
|
||||
@@ -238,9 +245,9 @@ TEST(indexTemplateStringTest, indexTemplateCheck) {
|
||||
"</html>");
|
||||
EXPECT_EQ("<!DOCTYPE html><head>"
|
||||
"<title>Welcome to kiwix library</title>"
|
||||
"<link type=\"root\" href=\"\">"
|
||||
"<link type=\"root\" href=\"/ROOT\">"
|
||||
"</head>"
|
||||
"</html>", zfs.GET("/")->body);
|
||||
"</html>", zfs.GET("/ROOT/")->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, 200)
|
||||
@@ -269,23 +276,32 @@ TEST_F(ServerTest, UncompressibleContentIsNotCompressed)
|
||||
}
|
||||
|
||||
const char* urls404[] = {
|
||||
"/non-existent-item",
|
||||
"/skin/non-existent-skin-resource",
|
||||
"/catalog",
|
||||
"/catalog/non-existent-item",
|
||||
"/catalogBLABLABLA/root.xml",
|
||||
"/meta",
|
||||
"/meta?content=zimfile",
|
||||
"/meta?content=zimfile&name=non-existent-item",
|
||||
"/meta?content=non-existent-book&name=title",
|
||||
"/random",
|
||||
"/random?content=non-existent-book",
|
||||
"/search",
|
||||
"/suggest",
|
||||
"/suggest?content=zimfile",
|
||||
"/suggest?content=non-existent-book&term=abcd",
|
||||
"/catch/external",
|
||||
"/zimfile/A/non-existent-article",
|
||||
"/",
|
||||
"/zimfile",
|
||||
"/ROOT/non-existent-item",
|
||||
"/ROOT/skin/non-existent-skin-resource",
|
||||
"/ROOT/catalog",
|
||||
"/ROOT/catalog/non-existent-item",
|
||||
"/ROOT/catalogBLABLABLA/root.xml",
|
||||
"/ROOT/catalog/v2/illustration/zimfile?size=96",
|
||||
"/ROOT/meta",
|
||||
"/ROOT/meta?content=zimfile",
|
||||
"/ROOT/meta?content=zimfile&name=non-existent-item",
|
||||
"/ROOT/meta?content=non-existent-book&name=title",
|
||||
"/ROOT/random",
|
||||
"/ROOT/random?content=non-existent-book",
|
||||
"/ROOT/search",
|
||||
"/ROOT/suggest",
|
||||
"/ROOT/suggest?content=non-existent-book&term=abcd",
|
||||
"/ROOT/catch/external",
|
||||
"/ROOT/zimfile/A/non-existent-article",
|
||||
|
||||
"/ROOT/raw/non-existent-book/meta/Title",
|
||||
"/ROOT/raw/zimfile/wrong-kind/Foo",
|
||||
|
||||
// zimfile has no Favicon nor Illustration_48x48@1 meta item
|
||||
"/ROOT/raw/zimfile/meta/Favicon",
|
||||
"/ROOT/raw/zimfile/meta/Illustration_48x48@1",
|
||||
};
|
||||
|
||||
TEST_F(ServerTest, 404)
|
||||
@@ -296,7 +312,7 @@ TEST_F(ServerTest, 404)
|
||||
|
||||
TEST_F(ServerTest, RandomPageRedirectsToAnExistingArticle)
|
||||
{
|
||||
auto g = zfs1_->GET("/random?content=zimfile");
|
||||
auto g = zfs1_->GET("/ROOT/random?content=zimfile");
|
||||
ASSERT_EQ(302, g->status);
|
||||
ASSERT_TRUE(g->has_header("Location"));
|
||||
ASSERT_TRUE(g->get_header_value("Location").find("/zimfile/A/") != std::string::npos);
|
||||
@@ -304,10 +320,36 @@ TEST_F(ServerTest, RandomPageRedirectsToAnExistingArticle)
|
||||
|
||||
TEST_F(ServerTest, BookMainPageIsRedirectedToArticleIndex)
|
||||
{
|
||||
auto g = zfs1_->GET("/zimfile");
|
||||
auto g = zfs1_->GET("/ROOT/zimfile");
|
||||
ASSERT_EQ(302, g->status);
|
||||
ASSERT_TRUE(g->has_header("Location"));
|
||||
ASSERT_EQ("/zimfile/A/index", g->get_header_value("Location"));
|
||||
ASSERT_EQ("/ROOT/zimfile/A/index", g->get_header_value("Location"));
|
||||
}
|
||||
|
||||
|
||||
TEST_F(ServerTest, RawEntry)
|
||||
{
|
||||
auto p = zfs1_->GET("/ROOT/raw/zimfile/meta/Title");
|
||||
EXPECT_EQ(200, p->status);
|
||||
EXPECT_EQ(p->body, std::string("Ray Charles"));
|
||||
|
||||
p = zfs1_->GET("/ROOT/raw/zimfile/meta/Creator");
|
||||
EXPECT_EQ(200, p->status);
|
||||
EXPECT_EQ(p->body, std::string("Wikipedia"));
|
||||
|
||||
// The raw content of Ray_Charles returned by the server is
|
||||
// the same as the one in the zim file.
|
||||
auto archive = zim::Archive("./test/zimfile.zim");
|
||||
auto entry = archive.getEntryByPath("A/Ray_Charles");
|
||||
p = zfs1_->GET("/ROOT/raw/zimfile/content/A/Ray_Charles");
|
||||
EXPECT_EQ(200, p->status);
|
||||
EXPECT_EQ(std::string(p->body), std::string(entry.getItem(true).getData()));
|
||||
|
||||
// ... but the "normal" content is not
|
||||
p = zfs1_->GET("/ROOT/zimfile/A/Ray_Charles");
|
||||
EXPECT_EQ(200, p->status);
|
||||
EXPECT_NE(std::string(p->body), std::string(entry.getItem(true).getData()));
|
||||
EXPECT_TRUE(p->body.find("taskbar") != std::string::npos);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, HeadMethodIsSupported)
|
||||
@@ -466,7 +508,7 @@ TEST_F(ServerTest, IfNoneMatchRequestsWithMismatchingETagResultIn200Responses)
|
||||
|
||||
TEST_F(ServerTest, ValidSingleRangeByteRangeRequestsAreHandledProperly)
|
||||
{
|
||||
const char url[] = "/zimfile/I/m/Ray_Charles_classic_piano_pose.jpg";
|
||||
const char url[] = "/ROOT/zimfile/I/m/Ray_Charles_classic_piano_pose.jpg";
|
||||
const auto full = zfs1_->GET(url);
|
||||
EXPECT_FALSE(full->has_header("Content-Range"));
|
||||
EXPECT_EQ("bytes", full->get_header_value("Accept-Ranges"));
|
||||
@@ -516,7 +558,7 @@ TEST_F(ServerTest, ValidSingleRangeByteRangeRequestsAreHandledProperly)
|
||||
|
||||
TEST_F(ServerTest, InvalidAndMultiRangeByteRangeRequestsResultIn416Responses)
|
||||
{
|
||||
const char url[] = "/zimfile/I/m/Ray_Charles_classic_piano_pose.jpg";
|
||||
const char url[] = "/ROOT/zimfile/I/m/Ray_Charles_classic_piano_pose.jpg";
|
||||
|
||||
const char* invalidRanges[] = {
|
||||
"0-10", "bytes=", "bytes=123", "bytes=-10-20", "bytes=10-20xxx",
|
||||
@@ -537,7 +579,7 @@ TEST_F(ServerTest, InvalidAndMultiRangeByteRangeRequestsResultIn416Responses)
|
||||
|
||||
TEST_F(ServerTest, ValidByteRangeRequestsOfZeroSizedEntriesResultIn416Responses)
|
||||
{
|
||||
const char url[] = "/corner_cases/-/empty.js";
|
||||
const char url[] = "/ROOT/corner_cases/-/empty.js";
|
||||
|
||||
const char* ranges[] = {
|
||||
"bytes=0-",
|
||||
@@ -556,7 +598,7 @@ TEST_F(ServerTest, ValidByteRangeRequestsOfZeroSizedEntriesResultIn416Responses)
|
||||
|
||||
TEST_F(ServerTest, RangeHasPrecedenceOverCompression)
|
||||
{
|
||||
const char url[] = "/zimfile/I/m/Ray_Charles_classic_piano_pose.jpg";
|
||||
const char url[] = "/ROOT/zimfile/I/m/Ray_Charles_classic_piano_pose.jpg";
|
||||
|
||||
const Headers onlyRange{ {"Range", "bytes=123-456"} };
|
||||
Headers rangeAndCompression(onlyRange);
|
||||
@@ -571,7 +613,7 @@ TEST_F(ServerTest, RangeHasPrecedenceOverCompression)
|
||||
|
||||
TEST_F(ServerTest, RangeHeaderIsCaseInsensitive)
|
||||
{
|
||||
const char url[] = "/zimfile/I/m/Ray_Charles_classic_piano_pose.jpg";
|
||||
const char url[] = "/ROOT/zimfile/I/m/Ray_Charles_classic_piano_pose.jpg";
|
||||
const auto r0 = zfs1_->GET(url, { {"Range", "bytes=100-200"} } );
|
||||
|
||||
const char* header_variations[] = { "RANGE", "range", "rAnGe", "RaNgE" };
|
||||
@@ -644,7 +686,7 @@ std::string maskVariableOPDSFeedData(std::string s)
|
||||
" <link rel=\"self\" href=\"\" type=\"application/atom+xml\" />\n" \
|
||||
" <link rel=\"search\"" \
|
||||
" type=\"application/opensearchdescription+xml\"" \
|
||||
" href=\"/catalog/searchdescription.xml\" />\n"
|
||||
" href=\"/ROOT/catalog/searchdescription.xml\" />\n"
|
||||
|
||||
#define CHARLES_RAY_CATALOG_ENTRY \
|
||||
" <entry>\n" \
|
||||
@@ -659,17 +701,14 @@ std::string maskVariableOPDSFeedData(std::string s)
|
||||
" <tags>unittest;wikipedia;_category:jazz;_pictures:no;_videos:no;_details:no;_ftindex:yes</tags>\n" \
|
||||
" <articleCount>284</articleCount>\n" \
|
||||
" <mediaCount>2</mediaCount>\n" \
|
||||
" <link rel=\"http://opds-spec.org/image/thumbnail\"\n" \
|
||||
" href=\"/meta?name=Illustration_48x48@1&content=zimfile\"\n" \
|
||||
" type=\"image/png;width=48;height=48;scale=1\"/>\n" \
|
||||
" <link type=\"text/html\" href=\"/zimfile\" />\n" \
|
||||
" <link type=\"text/html\" href=\"/ROOT/zimfile%26other\" />\n" \
|
||||
" <author>\n" \
|
||||
" <name>Wikipedia</name>\n" \
|
||||
" </author>\n" \
|
||||
" <publisher>\n" \
|
||||
" <name>Kiwix</name>\n" \
|
||||
" </publisher>\n" \
|
||||
" <link rel=\"http://opds-spec.org/acquisition/open-access\" type=\"application/x-zim\" href=\"https://github.com/kiwix/libkiwix/raw/master/test/data/zimfile.zim\" length=\"569344\" />\n" \
|
||||
" <link rel=\"http://opds-spec.org/acquisition/open-access\" type=\"application/x-zim\" href=\"https://github.com/kiwix/libkiwix/raw/master/test/data/zimfile%26other.zim\" length=\"569344\" />\n" \
|
||||
" </entry>\n"
|
||||
|
||||
#define RAY_CHARLES_CATALOG_ENTRY \
|
||||
@@ -686,9 +725,9 @@ std::string maskVariableOPDSFeedData(std::string s)
|
||||
" <articleCount>284</articleCount>\n" \
|
||||
" <mediaCount>2</mediaCount>\n" \
|
||||
" <link rel=\"http://opds-spec.org/image/thumbnail\"\n" \
|
||||
" href=\"/meta?name=Illustration_48x48@1&content=zimfile\"\n" \
|
||||
" href=\"/ROOT/catalog/v2/illustration/zimfile/?size=48\"\n" \
|
||||
" type=\"image/png;width=48;height=48;scale=1\"/>\n" \
|
||||
" <link type=\"text/html\" href=\"/zimfile\" />\n" \
|
||||
" <link type=\"text/html\" href=\"/ROOT/zimfile\" />\n" \
|
||||
" <author>\n" \
|
||||
" <name>Wikipedia</name>\n" \
|
||||
" </author>\n" \
|
||||
@@ -711,10 +750,7 @@ std::string maskVariableOPDSFeedData(std::string s)
|
||||
" <tags>unittest;wikipedia;_pictures:no;_videos:no;_details:no</tags>\n" \
|
||||
" <articleCount>284</articleCount>\n" \
|
||||
" <mediaCount>2</mediaCount>\n" \
|
||||
" <link rel=\"http://opds-spec.org/image/thumbnail\"\n" \
|
||||
" href=\"/meta?name=Illustration_48x48@1&content=zimfile\"\n" \
|
||||
" type=\"image/png;width=48;height=48;scale=1\"/>\n" \
|
||||
" <link type=\"text/html\" href=\"/zimfile\" />\n" \
|
||||
" <link type=\"text/html\" href=\"/ROOT/zimfile\" />\n" \
|
||||
" <author>\n" \
|
||||
" <name>Wikipedia</name>\n" \
|
||||
" </author>\n" \
|
||||
@@ -726,7 +762,7 @@ std::string maskVariableOPDSFeedData(std::string s)
|
||||
|
||||
TEST_F(LibraryServerTest, catalog_root_xml)
|
||||
{
|
||||
const auto r = zfs1_->GET("/catalog/root.xml");
|
||||
const auto r = zfs1_->GET("/ROOT/catalog/root.xml");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||
OPDS_FEED_TAG
|
||||
@@ -744,7 +780,7 @@ TEST_F(LibraryServerTest, catalog_root_xml)
|
||||
|
||||
TEST_F(LibraryServerTest, catalog_searchdescription_xml)
|
||||
{
|
||||
const auto r = zfs1_->GET("/catalog/searchdescription.xml");
|
||||
const auto r = zfs1_->GET("/ROOT/catalog/searchdescription.xml");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
EXPECT_EQ(r->body,
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||
@@ -755,14 +791,14 @@ TEST_F(LibraryServerTest, catalog_searchdescription_xml)
|
||||
" xmlns:atom=\"http://www.w3.org/2005/Atom\"\n"
|
||||
" xmlns:k=\"http://kiwix.org/opensearchextension/1.0\"\n"
|
||||
" indexOffset=\"0\"\n"
|
||||
" template=\"/catalog/search?q={searchTerms?}&lang={language?}&name={k:name?}&tag={k:tag?}¬ag={k:notag?}&maxsize={k:maxsize?}&count={count?}&start={startIndex?}\"/>\n"
|
||||
" template=\"/ROOT/catalog/search?q={searchTerms?}&lang={language?}&name={k:name?}&tag={k:tag?}¬ag={k:notag?}&maxsize={k:maxsize?}&count={count?}&start={startIndex?}\"/>\n"
|
||||
"</OpenSearchDescription>\n"
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(LibraryServerTest, catalog_search_by_phrase)
|
||||
{
|
||||
const auto r = zfs1_->GET("/catalog/search?q=\"ray%20charles\"");
|
||||
const auto r = zfs1_->GET("/ROOT/catalog/search?q=\"ray%20charles\"");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||
OPDS_FEED_TAG
|
||||
@@ -781,7 +817,7 @@ TEST_F(LibraryServerTest, catalog_search_by_phrase)
|
||||
|
||||
TEST_F(LibraryServerTest, catalog_search_by_words)
|
||||
{
|
||||
const auto r = zfs1_->GET("/catalog/search?q=ray%20charles");
|
||||
const auto r = zfs1_->GET("/ROOT/catalog/search?q=ray%20charles");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||
OPDS_FEED_TAG
|
||||
@@ -802,7 +838,7 @@ TEST_F(LibraryServerTest, catalog_search_by_words)
|
||||
TEST_F(LibraryServerTest, catalog_prefix_search)
|
||||
{
|
||||
{
|
||||
const auto r = zfs1_->GET("/catalog/search?q=description:ray%20description:charles");
|
||||
const auto r = zfs1_->GET("/ROOT/catalog/search?q=description:ray%20description:charles");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||
OPDS_FEED_TAG
|
||||
@@ -819,7 +855,7 @@ TEST_F(LibraryServerTest, catalog_prefix_search)
|
||||
);
|
||||
}
|
||||
{
|
||||
const auto r = zfs1_->GET("/catalog/search?q=title:\"ray%20charles\"");
|
||||
const auto r = zfs1_->GET("/ROOT/catalog/search?q=title:\"ray%20charles\"");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||
OPDS_FEED_TAG
|
||||
@@ -838,7 +874,7 @@ TEST_F(LibraryServerTest, catalog_prefix_search)
|
||||
|
||||
TEST_F(LibraryServerTest, catalog_search_with_word_exclusion)
|
||||
{
|
||||
const auto r = zfs1_->GET("/catalog/search?q=ray%20-uncategorized");
|
||||
const auto r = zfs1_->GET("/ROOT/catalog/search?q=ray%20-uncategorized");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||
OPDS_FEED_TAG
|
||||
@@ -857,7 +893,7 @@ TEST_F(LibraryServerTest, catalog_search_with_word_exclusion)
|
||||
|
||||
TEST_F(LibraryServerTest, catalog_search_by_tag)
|
||||
{
|
||||
const auto r = zfs1_->GET("/catalog/search?tag=_category:jazz");
|
||||
const auto r = zfs1_->GET("/ROOT/catalog/search?tag=_category:jazz");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||
OPDS_FEED_TAG
|
||||
@@ -875,7 +911,7 @@ TEST_F(LibraryServerTest, catalog_search_by_tag)
|
||||
|
||||
TEST_F(LibraryServerTest, catalog_search_by_category)
|
||||
{
|
||||
const auto r = zfs1_->GET("/catalog/search?category=jazz");
|
||||
const auto r = zfs1_->GET("/ROOT/catalog/search?category=jazz");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||
OPDS_FEED_TAG
|
||||
@@ -894,7 +930,25 @@ TEST_F(LibraryServerTest, catalog_search_by_category)
|
||||
TEST_F(LibraryServerTest, catalog_search_results_pagination)
|
||||
{
|
||||
{
|
||||
const auto r = zfs1_->GET("/catalog/search?count=1");
|
||||
const auto r = zfs1_->GET("/ROOT/catalog/search?count=0");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||
OPDS_FEED_TAG
|
||||
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
|
||||
" <title>Filtered zims (count=0)</title>\n"
|
||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||
" <totalResults>3</totalResults>\n"
|
||||
" <startIndex>0</startIndex>\n"
|
||||
" <itemsPerPage>3</itemsPerPage>\n"
|
||||
CATALOG_LINK_TAGS
|
||||
CHARLES_RAY_CATALOG_ENTRY
|
||||
RAY_CHARLES_CATALOG_ENTRY
|
||||
UNCATEGORIZED_RAY_CHARLES_CATALOG_ENTRY
|
||||
"</feed>\n"
|
||||
);
|
||||
}
|
||||
{
|
||||
const auto r = zfs1_->GET("/ROOT/catalog/search?count=1");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||
OPDS_FEED_TAG
|
||||
@@ -910,7 +964,7 @@ TEST_F(LibraryServerTest, catalog_search_results_pagination)
|
||||
);
|
||||
}
|
||||
{
|
||||
const auto r = zfs1_->GET("/catalog/search?start=1&count=1");
|
||||
const auto r = zfs1_->GET("/ROOT/catalog/search?start=1&count=1");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||
OPDS_FEED_TAG
|
||||
@@ -926,7 +980,7 @@ TEST_F(LibraryServerTest, catalog_search_results_pagination)
|
||||
);
|
||||
}
|
||||
{
|
||||
const auto r = zfs1_->GET("/catalog/search?start=100&count=10");
|
||||
const auto r = zfs1_->GET("/ROOT/catalog/search?start=100&count=10");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||
OPDS_FEED_TAG
|
||||
@@ -944,20 +998,20 @@ TEST_F(LibraryServerTest, catalog_search_results_pagination)
|
||||
|
||||
TEST_F(LibraryServerTest, catalog_v2_root)
|
||||
{
|
||||
const auto r = zfs1_->GET("/catalog/v2/root.xml");
|
||||
const auto r = zfs1_->GET("/ROOT/catalog/v2/root.xml");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
const char expected_output[] = 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>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||
<link rel="self"
|
||||
href="/catalog/v2/root.xml"
|
||||
href="/ROOT/catalog/v2/root.xml"
|
||||
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||
<link rel="start"
|
||||
href="/catalog/v2/root.xml"
|
||||
href="/ROOT/catalog/v2/root.xml"
|
||||
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||
<link rel="search"
|
||||
href="/catalog/v2/searchdescription.xml"
|
||||
href="/ROOT/catalog/v2/searchdescription.xml"
|
||||
type="application/opensearchdescription+xml"/>
|
||||
<title>OPDS Catalog Root</title>
|
||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||
@@ -965,7 +1019,7 @@ TEST_F(LibraryServerTest, catalog_v2_root)
|
||||
<entry>
|
||||
<title>All entries</title>
|
||||
<link rel="subsection"
|
||||
href="/catalog/v2/entries"
|
||||
href="/ROOT/catalog/v2/entries"
|
||||
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||
@@ -974,7 +1028,7 @@ TEST_F(LibraryServerTest, catalog_v2_root)
|
||||
<entry>
|
||||
<title>All entries (partial)</title>
|
||||
<link rel="subsection"
|
||||
href="/catalog/v2/partial_entries"
|
||||
href="/ROOT/catalog/v2/partial_entries"
|
||||
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||
@@ -983,7 +1037,7 @@ TEST_F(LibraryServerTest, catalog_v2_root)
|
||||
<entry>
|
||||
<title>List of categories</title>
|
||||
<link rel="subsection"
|
||||
href="/catalog/v2/categories"
|
||||
href="/ROOT/catalog/v2/categories"
|
||||
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||
@@ -992,7 +1046,7 @@ TEST_F(LibraryServerTest, catalog_v2_root)
|
||||
<entry>
|
||||
<title>List of languages</title>
|
||||
<link rel="subsection"
|
||||
href="/catalog/v2/languages"
|
||||
href="/ROOT/catalog/v2/languages"
|
||||
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||
@@ -1005,7 +1059,7 @@ TEST_F(LibraryServerTest, catalog_v2_root)
|
||||
|
||||
TEST_F(LibraryServerTest, catalog_v2_searchdescription_xml)
|
||||
{
|
||||
const auto r = zfs1_->GET("/catalog/v2/searchdescription.xml");
|
||||
const auto r = zfs1_->GET("/ROOT/catalog/v2/searchdescription.xml");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
EXPECT_EQ(r->body,
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||
@@ -1016,24 +1070,24 @@ TEST_F(LibraryServerTest, catalog_v2_searchdescription_xml)
|
||||
" xmlns:atom=\"http://www.w3.org/2005/Atom\"\n"
|
||||
" xmlns:k=\"http://kiwix.org/opensearchextension/1.0\"\n"
|
||||
" indexOffset=\"0\"\n"
|
||||
" template=\"/catalog/v2/entries?q={searchTerms?}&lang={language?}&name={k:name?}&tag={k:tag?}&maxsize={k:maxsize?}&count={count?}&start={startIndex?}\"/>\n"
|
||||
" template=\"/ROOT/catalog/v2/entries?q={searchTerms?}&lang={language?}&name={k:name?}&tag={k:tag?}&maxsize={k:maxsize?}&count={count?}&start={startIndex?}\"/>\n"
|
||||
"</OpenSearchDescription>\n"
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(LibraryServerTest, catalog_v2_categories)
|
||||
{
|
||||
const auto r = zfs1_->GET("/catalog/v2/categories");
|
||||
const auto r = zfs1_->GET("/ROOT/catalog/v2/categories");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
const char expected_output[] = 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>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||
<link rel="self"
|
||||
href="/catalog/v2/categories"
|
||||
href="/ROOT/catalog/v2/categories"
|
||||
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||
<link rel="start"
|
||||
href="/catalog/v2/root.xml"
|
||||
href="/ROOT/catalog/v2/root.xml"
|
||||
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||
<title>List of categories</title>
|
||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||
@@ -1041,7 +1095,7 @@ TEST_F(LibraryServerTest, catalog_v2_categories)
|
||||
<entry>
|
||||
<title>jazz</title>
|
||||
<link rel="subsection"
|
||||
href="/catalog/v2/entries?category=jazz"
|
||||
href="/ROOT/catalog/v2/entries?category=jazz"
|
||||
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||
@@ -1050,7 +1104,7 @@ TEST_F(LibraryServerTest, catalog_v2_categories)
|
||||
<entry>
|
||||
<title>wikipedia</title>
|
||||
<link rel="subsection"
|
||||
href="/catalog/v2/entries?category=wikipedia"
|
||||
href="/ROOT/catalog/v2/entries?category=wikipedia"
|
||||
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||
@@ -1063,7 +1117,7 @@ TEST_F(LibraryServerTest, catalog_v2_categories)
|
||||
|
||||
TEST_F(LibraryServerTest, catalog_v2_languages)
|
||||
{
|
||||
const auto r = zfs1_->GET("/catalog/v2/languages");
|
||||
const auto r = zfs1_->GET("/ROOT/catalog/v2/languages");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
const char expected_output[] = R"(<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom"
|
||||
@@ -1072,10 +1126,10 @@ TEST_F(LibraryServerTest, catalog_v2_languages)
|
||||
xmlns:thr="http://purl.org/syndication/thread/1.0">
|
||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||
<link rel="self"
|
||||
href="/catalog/v2/languages"
|
||||
href="/ROOT/catalog/v2/languages"
|
||||
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||
<link rel="start"
|
||||
href="/catalog/v2/root.xml"
|
||||
href="/ROOT/catalog/v2/root.xml"
|
||||
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||
<title>List of languages</title>
|
||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||
@@ -1085,7 +1139,7 @@ TEST_F(LibraryServerTest, catalog_v2_languages)
|
||||
<dc:language>eng</dc:language>
|
||||
<thr:count>1</thr:count>
|
||||
<link rel="subsection"
|
||||
href="/catalog/v2/entries?lang=eng"
|
||||
href="/ROOT/catalog/v2/entries?lang=eng"
|
||||
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||
@@ -1095,7 +1149,7 @@ TEST_F(LibraryServerTest, catalog_v2_languages)
|
||||
<dc:language>fra</dc:language>
|
||||
<thr:count>1</thr:count>
|
||||
<link rel="subsection"
|
||||
href="/catalog/v2/entries?lang=fra"
|
||||
href="/ROOT/catalog/v2/entries?lang=fra"
|
||||
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||
@@ -1105,7 +1159,7 @@ TEST_F(LibraryServerTest, catalog_v2_languages)
|
||||
<dc:language>rus</dc:language>
|
||||
<thr:count>1</thr:count>
|
||||
<link rel="subsection"
|
||||
href="/catalog/v2/entries?lang=rus"
|
||||
href="/ROOT/catalog/v2/entries?lang=rus"
|
||||
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||
@@ -1123,13 +1177,13 @@ TEST_F(LibraryServerTest, catalog_v2_languages)
|
||||
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n" \
|
||||
"\n" \
|
||||
" <link rel=\"self\"\n" \
|
||||
" href=\"/catalog/v2/" x "\"\n" \
|
||||
" href=\"/ROOT/catalog/v2/" x "\"\n" \
|
||||
" type=\"application/atom+xml;profile=opds-catalog;kind=acquisition\"/>\n" \
|
||||
" <link rel=\"start\"\n" \
|
||||
" href=\"/catalog/v2/root.xml\"\n" \
|
||||
" href=\"/ROOT/catalog/v2/root.xml\"\n" \
|
||||
" type=\"application/atom+xml;profile=opds-catalog;kind=navigation\"/>\n" \
|
||||
" <link rel=\"up\"\n" \
|
||||
" href=\"/catalog/v2/root.xml\"\n" \
|
||||
" href=\"/ROOT/catalog/v2/root.xml\"\n" \
|
||||
" type=\"application/atom+xml;profile=opds-catalog;kind=navigation\"/>\n" \
|
||||
"\n" \
|
||||
|
||||
@@ -1141,7 +1195,7 @@ TEST_F(LibraryServerTest, catalog_v2_languages)
|
||||
|
||||
TEST_F(LibraryServerTest, catalog_v2_entries)
|
||||
{
|
||||
const auto r = zfs1_->GET("/catalog/v2/entries");
|
||||
const auto r = zfs1_->GET("/ROOT/catalog/v2/entries");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||
CATALOG_V2_ENTRIES_PREAMBLE("")
|
||||
@@ -1158,7 +1212,7 @@ TEST_F(LibraryServerTest, catalog_v2_entries)
|
||||
TEST_F(LibraryServerTest, catalog_v2_entries_filtered_by_range)
|
||||
{
|
||||
{
|
||||
const auto r = zfs1_->GET("/catalog/v2/entries?start=1");
|
||||
const auto r = zfs1_->GET("/ROOT/catalog/v2/entries?start=1");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||
CATALOG_V2_ENTRIES_PREAMBLE("?start=1")
|
||||
@@ -1174,7 +1228,7 @@ TEST_F(LibraryServerTest, catalog_v2_entries_filtered_by_range)
|
||||
}
|
||||
|
||||
{
|
||||
const auto r = zfs1_->GET("/catalog/v2/entries?count=2");
|
||||
const auto r = zfs1_->GET("/ROOT/catalog/v2/entries?count=2");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||
CATALOG_V2_ENTRIES_PREAMBLE("?count=2")
|
||||
@@ -1190,7 +1244,7 @@ TEST_F(LibraryServerTest, catalog_v2_entries_filtered_by_range)
|
||||
}
|
||||
|
||||
{
|
||||
const auto r = zfs1_->GET("/catalog/v2/entries?start=1&count=1");
|
||||
const auto r = zfs1_->GET("/ROOT/catalog/v2/entries?start=1&count=1");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||
CATALOG_V2_ENTRIES_PREAMBLE("?count=1&start=1")
|
||||
@@ -1207,7 +1261,7 @@ TEST_F(LibraryServerTest, catalog_v2_entries_filtered_by_range)
|
||||
|
||||
TEST_F(LibraryServerTest, catalog_v2_entries_filtered_by_search_terms)
|
||||
{
|
||||
const auto r = zfs1_->GET("/catalog/v2/entries?q=\"ray%20charles\"");
|
||||
const auto r = zfs1_->GET("/ROOT/catalog/v2/entries?q=\"ray%20charles\"");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||
CATALOG_V2_ENTRIES_PREAMBLE("?q=%22ray%20charles%22")
|
||||
@@ -1233,7 +1287,7 @@ TEST_F(LibraryServerTest, suggestions_in_range)
|
||||
{
|
||||
int suggCount = 0;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
std::string url = "/suggest?content=zimfile&term=ray&start=" + std::to_string(i*5) + "&count=5";
|
||||
std::string url = "/ROOT/suggest?content=zimfile&term=ray&start=" + std::to_string(i*5) + "&count=5";
|
||||
const auto r = zfs1_->GET(url.c_str());
|
||||
std::string body = r->body;
|
||||
int currCount = std::count(body.begin(), body.end(), '{') - 1;
|
||||
@@ -1245,13 +1299,13 @@ TEST_F(LibraryServerTest, suggestions_in_range)
|
||||
|
||||
// Attempt to get 10 suggestions in steps of 5 even though there are only 8
|
||||
{
|
||||
std::string url = "/suggest?content=zimfile&term=song+for+you&start=0&count=5";
|
||||
std::string url = "/ROOT/suggest?content=zimfile&term=song+for+you&start=0&count=5";
|
||||
const auto r1 = zfs1_->GET(url.c_str());
|
||||
std::string body = r1->body;
|
||||
int currCount = std::count(body.begin(), body.end(), '{') - 1;
|
||||
ASSERT_EQ(currCount, 5);
|
||||
|
||||
url = "/suggest?content=zimfile&term=song+for+you&start=5&count=5";
|
||||
url = "/ROOT/suggest?content=zimfile&term=song+for+you&start=5&count=5";
|
||||
const auto r2 = zfs1_->GET(url.c_str());
|
||||
body = r2->body;
|
||||
currCount = std::count(body.begin(), body.end(), '{') - 1;
|
||||
@@ -1260,7 +1314,7 @@ TEST_F(LibraryServerTest, suggestions_in_range)
|
||||
|
||||
// Attempt to get 10 suggestions even though there is only 1
|
||||
{
|
||||
std::string url = "/suggest?content=zimfile&term=strong&start=0&count=5";
|
||||
std::string url = "/ROOT/suggest?content=zimfile&term=strong&start=0&count=5";
|
||||
const auto r = zfs1_->GET(url.c_str());
|
||||
std::string body = r->body;
|
||||
int currCount = std::count(body.begin(), body.end(), '{') - 1;
|
||||
@@ -1269,7 +1323,7 @@ TEST_F(LibraryServerTest, suggestions_in_range)
|
||||
|
||||
// No Suggestion
|
||||
{
|
||||
std::string url = "/suggest?content=zimfile&term=oops&start=0&count=5";
|
||||
std::string url = "/ROOT/suggest?content=zimfile&term=oops&start=0&count=5";
|
||||
const auto r = zfs1_->GET(url.c_str());
|
||||
std::string body = r->body;
|
||||
int currCount = std::count(body.begin(), body.end(), '{') - 1;
|
||||
@@ -1278,7 +1332,7 @@ TEST_F(LibraryServerTest, suggestions_in_range)
|
||||
|
||||
// Out of bound value
|
||||
{
|
||||
std::string url = "/suggest?content=zimfile&term=ray&start=-2&count=-1";
|
||||
std::string url = "/ROOT/suggest?content=zimfile&term=ray&start=-2&count=-1";
|
||||
const auto r = zfs1_->GET(url.c_str());
|
||||
std::string body = r->body;
|
||||
int currCount = std::count(body.begin(), body.end(), '{') - 1;
|
||||
@@ -1288,20 +1342,20 @@ TEST_F(LibraryServerTest, suggestions_in_range)
|
||||
|
||||
TEST_F(LibraryServerTest, catalog_v2_individual_entry_access)
|
||||
{
|
||||
const auto r = zfs1_->GET("/catalog/v2/entry/raycharles");
|
||||
const auto r = zfs1_->GET("/ROOT/catalog/v2/entry/raycharles");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||
RAY_CHARLES_CATALOG_ENTRY
|
||||
);
|
||||
|
||||
const auto r1 = zfs1_->GET("/catalog/v2/entry/non-existent-entry");
|
||||
const auto r1 = zfs1_->GET("/ROOT/catalog/v2/entry/non-existent-entry");
|
||||
EXPECT_EQ(r1->status, 404);
|
||||
}
|
||||
|
||||
TEST_F(LibraryServerTest, catalog_v2_partial_entries)
|
||||
{
|
||||
const auto r = zfs1_->GET("/catalog/v2/partial_entries");
|
||||
const auto r = zfs1_->GET("/ROOT/catalog/v2/partial_entries");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||
CATALOG_V2_PARTIAL_ENTRIES_PREAMBLE("")
|
||||
@@ -1313,7 +1367,7 @@ TEST_F(LibraryServerTest, catalog_v2_partial_entries)
|
||||
" <title>Charles, Ray</title>\n"
|
||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||
" <link rel=\"alternate\"\n"
|
||||
" href=\"/catalog/v2/entry/charlesray\"\n"
|
||||
" href=\"/ROOT/catalog/v2/entry/charlesray\"\n"
|
||||
" type=\"application/atom+xml;type=entry;profile=opds-catalog\"/>\n"
|
||||
" </entry>\n"
|
||||
" <entry>\n"
|
||||
@@ -1321,7 +1375,7 @@ TEST_F(LibraryServerTest, catalog_v2_partial_entries)
|
||||
" <title>Ray Charles</title>\n"
|
||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||
" <link rel=\"alternate\"\n"
|
||||
" href=\"/catalog/v2/entry/raycharles\"\n"
|
||||
" href=\"/ROOT/catalog/v2/entry/raycharles\"\n"
|
||||
" type=\"application/atom+xml;type=entry;profile=opds-catalog\"/>\n"
|
||||
" </entry>\n"
|
||||
" <entry>\n"
|
||||
@@ -1329,7 +1383,7 @@ TEST_F(LibraryServerTest, catalog_v2_partial_entries)
|
||||
" <title>Ray (uncategorized) Charles</title>\n"
|
||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||
" <link rel=\"alternate\"\n"
|
||||
" href=\"/catalog/v2/entry/raycharles_uncategorized\"\n"
|
||||
" href=\"/ROOT/catalog/v2/entry/raycharles_uncategorized\"\n"
|
||||
" type=\"application/atom+xml;type=entry;profile=opds-catalog\"/>\n"
|
||||
" </entry>\n"
|
||||
"</feed>\n"
|
||||
|
||||
Reference in New Issue
Block a user