mirror of
https://github.com/kiwix/libkiwix.git
synced 2026-02-24 02:09:00 -05:00
Compare commits
1 Commits
feature/av
...
more_predi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ebf0fe8b8f |
147
.github/workflows/ci.yml
vendored
147
.github/workflows/ci.yml
vendored
@@ -14,19 +14,9 @@ jobs:
|
||||
os:
|
||||
- macos-13
|
||||
target:
|
||||
- macos-x86_64-dyn
|
||||
- ios-arm64-dyn
|
||||
- ios-x86_64-dyn
|
||||
include:
|
||||
- target: macos-x86_64-dyn
|
||||
arch_name: x86_64-apple-darwin
|
||||
run_test: true
|
||||
- target: ios-arm64-dyn
|
||||
arch_name: aarch64-apple-ios
|
||||
run_test: true
|
||||
- target: ios-x86_64-dyn
|
||||
arch_name: x86-apple-ios-simulator
|
||||
run_test: true
|
||||
- native_dyn
|
||||
- iOS_arm64
|
||||
- iOS_x86_64
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
env:
|
||||
@@ -39,8 +29,8 @@ jobs:
|
||||
run: |
|
||||
brew update
|
||||
brew unlink python3
|
||||
# upgrade from python@3.12 to python@3.12.2 fails to overwrite those
|
||||
rm -f /usr/local/bin/2to3 /usr/local/bin/2to3-3.12 /usr/local/bin/idle3 /usr/local/bin/idle3.12 /usr/local/bin/pydoc3 /usr/local/bin/pydoc3.12 /usr/local/bin/python3 /usr/local/bin/python3-config /usr/local/bin/python3.12 /usr/local/bin/python3.12-config
|
||||
# upgrade from python@3.11.2_1 to python@3.11.3 fails to overwrite those
|
||||
rm -f /usr/local/bin/2to3 /usr/local/bin/2to3-3.11 /usr/local/bin/idle3 /usr/local/bin/idle3.11 /usr/local/bin/pydoc3 /usr/local/bin/pydoc3.11 /usr/local/bin/python3 /usr/local/bin/python3-config /usr/local/bin/python3.11 /usr/local/bin/python3.11-config
|
||||
brew install pkg-config ninja meson
|
||||
env:
|
||||
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1
|
||||
@@ -48,109 +38,66 @@ jobs:
|
||||
- name: Install dependencies
|
||||
uses: kiwix/kiwix-build/actions/dl_deps_archive@main
|
||||
with:
|
||||
os_name: macos
|
||||
target_platform: ${{ matrix.target }}
|
||||
|
||||
- name: Compile
|
||||
env:
|
||||
PKG_CONFIG_PATH: ${{env.HOME}}/BUILD_${{matrix.arch_name}}/INSTALL/lib/pkgconfig
|
||||
CPPFLAGS: -I${{env.HOME}}/BUILD_${{matrix.arch_name}}/INSTALL/include
|
||||
PKG_CONFIG_PATH: ${{env.HOME}}/BUILD_${{matrix.target}}/INSTALL/lib/pkgconfig
|
||||
CPPFLAGS: -I${{env.HOME}}/BUILD_native_dyn/INSTALL/include
|
||||
MESON_OPTION: --default-library=shared -Db_coverage=true
|
||||
MESON_CROSSFILE: ${{env.HOME}}/BUILD_${{matrix.arch_name}}/meson_cross_file.txt
|
||||
MESON_CROSSFILE: ${{env.HOME}}/BUILD_${{matrix.target}}/meson_cross_file.txt
|
||||
shell: bash
|
||||
run: |
|
||||
if [ -e $MESON_CROSSFILE ]; then
|
||||
if [[ ! "${{matrix.target}}" =~ native_.* ]]; then
|
||||
MESON_OPTION="$MESON_OPTION --cross-file $MESON_CROSSFILE -Dstatic-linkage=true"
|
||||
fi
|
||||
meson . build ${MESON_OPTION}
|
||||
ninja -C build
|
||||
|
||||
- name: Test libkiwix
|
||||
if: matrix.run_test
|
||||
if: startsWith(matrix.target, 'native_')
|
||||
env:
|
||||
SKIP_BIG_MEMORY_TEST: 1
|
||||
LD_LIBRARY_PATH: ${{env.HOME}}/BUILD_${{matrix.arch_name}}/INSTALL/lib:${{env.HOME}}/BUILD_${{matrix.arch_name}}/INSTALL/lib64
|
||||
LD_LIBRARY_PATH: ${{env.HOME}}/BUILD_native_dyn/INSTALL/lib:${{env.HOME}}/BUILD_native_dyn/INSTALL/lib64
|
||||
run: meson test -C build --verbose
|
||||
|
||||
Windows:
|
||||
runs-on: windows-2022
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup python 3.10
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Install packages
|
||||
run:
|
||||
choco install pkgconfiglite ninja
|
||||
|
||||
- name: Install python modules
|
||||
run: pip3 install meson
|
||||
|
||||
- name: Setup MSVC compiler
|
||||
uses: bus1/cabuild/action/msdevshell@v1
|
||||
with:
|
||||
architecture: x64
|
||||
|
||||
- name: Install dependencies
|
||||
uses: kiwix/kiwix-build/actions/dl_deps_archive@main
|
||||
with:
|
||||
target_platform: win-x86_64-static
|
||||
|
||||
- name: Compile
|
||||
shell: cmd
|
||||
run: |
|
||||
set PKG_CONFIG_PATH=%cd%\BUILD_win-amd64\INSTALL\lib\pkgconfig
|
||||
set CPPFLAGS=-I%cd%\BUILD_win-amd64\INSTALL\include
|
||||
meson.exe setup . build -Dwerror=false --default-library=static --buildtype=debug
|
||||
cd build
|
||||
ninja.exe
|
||||
|
||||
- name: Test
|
||||
shell: cmd
|
||||
run: |
|
||||
cd build
|
||||
meson.exe test --verbose
|
||||
env:
|
||||
WAIT_TIME_FACTOR_TEST: 10
|
||||
|
||||
Linux:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
- linux-x86_64-static
|
||||
- linux-x86_64-dyn
|
||||
- android-arm
|
||||
- android-arm64
|
||||
name:
|
||||
- native_static
|
||||
- native_dyn
|
||||
- android_arm
|
||||
- android_arm64
|
||||
- win32_static
|
||||
- win32_dyn
|
||||
include:
|
||||
- target: linux-x86_64-static
|
||||
- name: native_static
|
||||
target: native_static
|
||||
image_variant: focal
|
||||
lib_postfix: '/x86_64-linux-gnu'
|
||||
arch_name: linux-x86_64
|
||||
run_test: true
|
||||
coverage: true
|
||||
- target: linux-x86_64-dyn
|
||||
- name: native_dyn
|
||||
target: native_dyn
|
||||
image_variant: focal
|
||||
lib_postfix: '/x86_64-linux-gnu'
|
||||
arch_name: linux-x86_64
|
||||
run_test: true
|
||||
coverage: true
|
||||
- target: android-arm
|
||||
- name: android_arm
|
||||
target: android_arm
|
||||
image_variant: focal
|
||||
lib_postfix: '/arm-linux-androideabi'
|
||||
arch_name: arm-linux-androideabi
|
||||
run_test: false
|
||||
coverage: false
|
||||
- target: android-arm64
|
||||
- name: android_arm64
|
||||
target: android_arm64
|
||||
image_variant: focal
|
||||
lib_postfix: '/aarch64-linux-android'
|
||||
arch_name: aarch64-linux-android
|
||||
run_test: false
|
||||
coverage: false
|
||||
- name: win32_static
|
||||
target: win32_static
|
||||
image_variant: f35
|
||||
lib_postfix: '64'
|
||||
- name: win32_dyn
|
||||
target: win32_dyn
|
||||
image_variant: f35
|
||||
lib_postfix: '64'
|
||||
env:
|
||||
HOME: /home/runner
|
||||
runs-on: ubuntu-20.04
|
||||
@@ -167,40 +114,38 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
meson --version
|
||||
if [[ "${{matrix.target}}" =~ .*-dyn ]]; then
|
||||
if [[ "${{matrix.target}}" =~ .*_dyn ]]; then
|
||||
MESON_OPTION="--default-library=shared"
|
||||
else
|
||||
MESON_OPTION="--default-library=static"
|
||||
fi
|
||||
if [ -e "${{env.HOME}}/BUILD_${{matrix.arch_name}}/meson_cross_file.txt" ]; then
|
||||
MESON_OPTION="$MESON_OPTION --cross-file ${{env.HOME}}/BUILD_${{matrix.arch_name}}/meson_cross_file.txt"
|
||||
else
|
||||
if [[ "${{matrix.target}}" =~ native_.* ]]; then
|
||||
MESON_OPTION="$MESON_OPTION -Db_coverage=true"
|
||||
else
|
||||
MESON_OPTION="$MESON_OPTION --cross-file $HOME/BUILD_${{matrix.target}}/meson_cross_file.txt"
|
||||
fi
|
||||
if [[ "${{matrix.target}}" =~ android-.* ]]; then
|
||||
if [[ "${{matrix.target}}" =~ android_.* ]]; then
|
||||
MESON_OPTION="$MESON_OPTION -Dstatic-linkage=true"
|
||||
fi
|
||||
meson . build ${MESON_OPTION}
|
||||
cd build
|
||||
ninja
|
||||
env:
|
||||
PKG_CONFIG_PATH: "/home/runner/BUILD_${{matrix.arch_name}}/INSTALL/lib/pkgconfig:/home/runner/BUILD_${{matrix.arch_name}}/INSTALL/lib${{matrix.lib_postfix}}/pkgconfig"
|
||||
CPPFLAGS: "-I/home/runner/BUILD_${{matrix.arch_name}}/INSTALL/include"
|
||||
PKG_CONFIG_PATH: "/home/runner/BUILD_${{matrix.target}}/INSTALL/lib/pkgconfig:/home/runner/BUILD_${{matrix.target}}/INSTALL/lib${{matrix.lib_postfix}}/pkgconfig"
|
||||
CPPFLAGS: "-I/home/runner/BUILD_${{matrix.target}}/INSTALL/include"
|
||||
- name: Test
|
||||
if: matrix.run_test
|
||||
if: startsWith(matrix.target, 'native_')
|
||||
shell: bash
|
||||
run: |
|
||||
cd build
|
||||
meson test --verbose
|
||||
if [[ "${{matrix.coverage}}" = "true" ]]; then
|
||||
ninja coverage
|
||||
fi
|
||||
ninja coverage
|
||||
env:
|
||||
LD_LIBRARY_PATH: "/home/runner/BUILD_${{matrix.arch_name}}/INSTALL/lib:/home/runner/BUILD_${{matrix.arch_name}}/INSTALL/lib${{matrix.lib_postfix}}"
|
||||
LD_LIBRARY_PATH: "/home/runner/BUILD_${{matrix.target}}/INSTALL/lib:/home/runner/BUILD_${{matrix.target}}/INSTALL/lib${{matrix.lib_postfix}}"
|
||||
SKIP_BIG_MEMORY_TEST: 1
|
||||
|
||||
- name: Publish coverage
|
||||
if: matrix.coverage
|
||||
if: startsWith(matrix.target, 'native_')
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
52
.github/workflows/package.yml
vendored
52
.github/workflows/package.yml
vendored
@@ -15,16 +15,11 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
distro:
|
||||
# - debian-unstable
|
||||
# - debian-trixie
|
||||
# - debian-bookworm
|
||||
# - debian-bullseye
|
||||
- ubuntu-noble
|
||||
- debian-unstable
|
||||
- ubuntu-jammy
|
||||
- ubuntu-focal
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
# Determine which PPA we should upload to
|
||||
- name: PPA
|
||||
@@ -39,47 +34,18 @@ jobs:
|
||||
env:
|
||||
REF: ${{ github.ref }}
|
||||
|
||||
- uses: legoktm/gh-action-auto-dch@main
|
||||
- uses: legoktm/gh-action-auto-dch@master
|
||||
with:
|
||||
fullname: Kiwix builder
|
||||
email: release+launchpad@kiwix.org
|
||||
distro: ${{ matrix.distro }}
|
||||
|
||||
# - uses: legoktm/gh-action-build-deb@debian-unstable
|
||||
# if: matrix.distro == 'debian-unstable'
|
||||
# name: Build package for debian-unstable
|
||||
# id: build-debian-unstable
|
||||
# with:
|
||||
# args: --no-sign
|
||||
#
|
||||
# - uses: legoktm/gh-action-build-deb@b47978ba8498dc8b8153cc3b5f99a5fc1afa5de1 # pin@debian-trixie
|
||||
# if: matrix.distro == 'debian-trixie'
|
||||
# name: Build package for debian-trixie
|
||||
# id: build-debian-trixie
|
||||
# with:
|
||||
# args: --no-sign
|
||||
#
|
||||
# - uses: legoktm/gh-action-build-deb@1f4e86a6bb34aaad388167eaf5eb85d553935336 # pin@debian-bookworm
|
||||
# if: matrix.distro == 'debian-bookworm'
|
||||
# name: Build package for debian-bookworm
|
||||
# id: build-debian-bookworm
|
||||
# with:
|
||||
# args: --no-sign
|
||||
#
|
||||
# - uses: legoktm/gh-action-build-deb@084b4263209252ec80a75d2c78a586192c17f18d # pin@debian-bullseye
|
||||
# if: matrix.distro == 'debian-bullseye'
|
||||
# name: Build package for debian-bullseye
|
||||
# id: build-debian-bullseye
|
||||
# with:
|
||||
# args: --no-sign
|
||||
|
||||
- uses: legoktm/gh-action-build-deb@9114a536498b65c40b932209b9833aa942bf108d # pin@ubuntu-noble
|
||||
if: matrix.distro == 'ubuntu-noble'
|
||||
name: Build package for ubuntu-noble
|
||||
id: build-ubuntu-noble
|
||||
- uses: legoktm/gh-action-build-deb@debian-unstable
|
||||
if: matrix.distro == 'debian-unstable'
|
||||
name: Build package for debian-unstable
|
||||
id: build-debian-unstable
|
||||
with:
|
||||
args: --no-sign
|
||||
ppa: ${{ steps.ppa.outputs.ppa }}
|
||||
|
||||
- uses: legoktm/gh-action-build-deb@ubuntu-jammy
|
||||
if: matrix.distro == 'ubuntu-jammy'
|
||||
@@ -102,7 +68,7 @@ jobs:
|
||||
name: Packages for ${{ matrix.distro }}
|
||||
path: output
|
||||
|
||||
- uses: legoktm/gh-action-dput@main
|
||||
- uses: legoktm/gh-action-dput@master
|
||||
name: Upload dev package
|
||||
# Only upload on pushes to main
|
||||
if: github.event_name == 'push' && github.event.ref == 'refs/heads/main' && startswith(matrix.distro, 'ubuntu-')
|
||||
@@ -111,7 +77,7 @@ jobs:
|
||||
repository: ppa:kiwixteam/dev
|
||||
packages: output/*_source.changes
|
||||
|
||||
- uses: legoktm/gh-action-dput@main
|
||||
- uses: legoktm/gh-action-dput@master
|
||||
name: Upload release package
|
||||
if: github.event_name == 'release' && startswith(matrix.distro, 'ubuntu-')
|
||||
with:
|
||||
|
||||
14
ChangeLog
14
ChangeLog
@@ -1,14 +1,3 @@
|
||||
libkiwix 13.1.0
|
||||
===============
|
||||
|
||||
* Server:
|
||||
- Properly translated error pages (@veloman-yunkan #1032)
|
||||
- Properly translated search result page (@veloman-yunkan #1046)
|
||||
- Default UI language is resolved in frontend (@veloman-yunkan #1044)
|
||||
- Better support of older Web browsers by polyfilling replaceAll() (@veloman-yunkan #1054)
|
||||
* New API to migrate bookmarks between books (@mgautierfr #1043)
|
||||
* Fixed compilation on Haiku OS (@Begasus #1048)
|
||||
|
||||
libkiwix 13.0.0
|
||||
===============
|
||||
|
||||
@@ -64,6 +53,8 @@ libkiwix 12.1.0
|
||||
* Remove libkiwix android publisher from the repository (@kelson42 #884)
|
||||
* Various fixes of meson and CI. (@mgautierfr @kelson42)
|
||||
|
||||
|
||||
|
||||
libkiwix 12.0.0
|
||||
===============
|
||||
|
||||
@@ -103,6 +94,7 @@ libkiwix 12.0.0
|
||||
* Fix documentation (@kelson42 #816)
|
||||
* Udpate translation (#787 #839 #847)
|
||||
|
||||
|
||||
libkiwix 11.0.0
|
||||
===============
|
||||
|
||||
|
||||
17
debian/control
vendored
17
debian/control
vendored
@@ -3,12 +3,13 @@ Priority: optional
|
||||
Maintainer: Kiwix team <kiwix@kiwix.org>
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
meson,
|
||||
pkgconf,
|
||||
libzim-dev (>= 9.0.0), libzim-dev (<< 10.0.0),
|
||||
pkg-config,
|
||||
libzim-dev (>= 7.2.0~),
|
||||
libcurl4-gnutls-dev,
|
||||
libicu-dev,
|
||||
libgtest-dev,
|
||||
libkainjow-mustache-dev,
|
||||
liblzma-dev,
|
||||
libmicrohttpd-dev,
|
||||
libpugixml-dev,
|
||||
zlib1g-dev
|
||||
@@ -21,13 +22,12 @@ Package: libkiwix-dev
|
||||
Section: libdevel
|
||||
Architecture: any
|
||||
Multi-Arch: same
|
||||
Depends: libkiwix14 (= ${binary:Version}), ${misc:Depends}, python3,
|
||||
libzim-dev (>= 9.0.0), libzim-dev (<< 10.0.0),
|
||||
Depends: libkiwix10 (= ${binary:Version}), ${misc:Depends}, python3,
|
||||
libzim-dev (>= 7.2.0~),
|
||||
libicu-dev,
|
||||
libpugixml-dev,
|
||||
libcurl4-gnutls-dev,
|
||||
libmicrohttpd-dev,
|
||||
zlib1g-dev
|
||||
libmicrohttpd-dev
|
||||
Description: library of common code for Kiwix (development)
|
||||
Kiwix is an offline Wikipedia reader. libkiwix provides the
|
||||
software core for Kiwix, and contains the code shared by all
|
||||
@@ -35,12 +35,11 @@ Description: library of common code for Kiwix (development)
|
||||
.
|
||||
This package contains development files.
|
||||
|
||||
Package: libkiwix14
|
||||
Package: libkiwix10
|
||||
Architecture: any
|
||||
Multi-Arch: same
|
||||
Depends: ${shlibs:Depends}, ${misc:Depends}, aria2
|
||||
Conflicts: libkiwix0, libkiwix3, libkiwix9, libkiwix10, libkiwix11, libkiwix12, libkiwix13
|
||||
Replaces: libkiwix0, libkiwix3, libkiwix9, libkiwix10, libkiwix11, libkiwix12, libkiwix13
|
||||
Conflicts: libkiwix0, libkiwix3, libkiwix9
|
||||
Description: library of common code for Kiwix
|
||||
Kiwix is an offline Wikipedia reader. libkiwix provides the
|
||||
software core for Kiwix, and contains the code shared by all
|
||||
|
||||
@@ -29,33 +29,19 @@ class xml_node;
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
class Book;
|
||||
/**
|
||||
* A class to store information about a bookmark (an article in a book)
|
||||
*/
|
||||
class Bookmark
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Create an empty bookmark.
|
||||
*
|
||||
* Bookmark must be populated with `set*` methods
|
||||
*/
|
||||
Bookmark();
|
||||
|
||||
/**
|
||||
* Create a bookmark given a Book, a path and a title.
|
||||
*/
|
||||
Bookmark(const Book& book, const std::string& path, const std::string& title);
|
||||
|
||||
~Bookmark();
|
||||
|
||||
void updateFromXml(const pugi::xml_node& node);
|
||||
|
||||
const std::string& getBookId() const { return m_bookId; }
|
||||
const std::string& getBookTitle() const { return m_bookTitle; }
|
||||
const std::string& getBookName() const { return m_bookName; }
|
||||
const std::string& getBookFlavour() const { return m_bookFlavour; }
|
||||
const std::string& getUrl() const { return m_url; }
|
||||
const std::string& getTitle() const { return m_title; }
|
||||
const std::string& getLanguage() const { return m_language; }
|
||||
@@ -63,8 +49,6 @@ class Bookmark
|
||||
|
||||
void setBookId(const std::string& bookId) { m_bookId = bookId; }
|
||||
void setBookTitle(const std::string& bookTitle) { m_bookTitle = bookTitle; }
|
||||
void setBookName(const std::string& bookName) { m_bookName = bookName; }
|
||||
void setBookFlavour(const std::string& bookFlavour) { m_bookFlavour = bookFlavour; }
|
||||
void setUrl(const std::string& url) { m_url = url; }
|
||||
void setTitle(const std::string& title) { m_title = title; }
|
||||
void setLanguage(const std::string& language) { m_language = language; }
|
||||
@@ -73,8 +57,6 @@ class Bookmark
|
||||
protected:
|
||||
std::string m_bookId;
|
||||
std::string m_bookTitle;
|
||||
std::string m_bookName;
|
||||
std::string m_bookFlavour;
|
||||
std::string m_url;
|
||||
std::string m_title;
|
||||
std::string m_language;
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
namespace kiwix {
|
||||
|
||||
enum class IpMode { ipv4, ipv6, all };
|
||||
typedef zim::size_type size_type;
|
||||
typedef zim::offset_type offset_type;
|
||||
|
||||
|
||||
@@ -168,16 +168,8 @@ class Download {
|
||||
*/
|
||||
class Downloader
|
||||
{
|
||||
public: // types
|
||||
typedef std::vector<std::pair<std::string, std::string>> Options;
|
||||
|
||||
public: // functions
|
||||
/*
|
||||
* Create a new Downloader object.
|
||||
*
|
||||
* @param sessionFileDir: The directory where aria2 will store its session file.
|
||||
*/
|
||||
explicit Downloader(std::string sessionFileDir);
|
||||
public:
|
||||
Downloader();
|
||||
virtual ~Downloader();
|
||||
|
||||
void close();
|
||||
@@ -185,22 +177,14 @@ class Downloader
|
||||
/**
|
||||
* Start a new download.
|
||||
*
|
||||
* This method is thread safe and returns a pointer to a newly created
|
||||
* `Download` or an existing one with a matching URI. In the latter case
|
||||
* the options parameter is ignored, which can lead to surprising results.
|
||||
* For example, if the old and new download requests (sharing the same URI)
|
||||
* have different values for the download directory or output file name
|
||||
* options, after the download is reported to be complete the downloaded file
|
||||
* will be present only at the location specified for the first request.
|
||||
*
|
||||
* This method is thread safe and return a pointer to a newly created `Download`.
|
||||
* User should call `update` on the returned `Download` to have an accurate status.
|
||||
*
|
||||
* @param uri: The uri of the thing to download.
|
||||
* @param downloadDir: The download directory where the thing should be stored (takes precedence over any "dir" in `options`).
|
||||
* @param options: A series of pair <option_name, option_value> to pass to aria.
|
||||
* @return: The newly created Download.
|
||||
*/
|
||||
std::shared_ptr<Download> startDownload(const std::string& uri, const std::string& downloadDir, Options options = {});
|
||||
std::shared_ptr<Download> startDownload(const std::string& uri, const std::vector<std::pair<std::string, std::string>>& options = {});
|
||||
|
||||
/**
|
||||
* Get a download corrsponding to a download id (did)
|
||||
@@ -222,7 +206,7 @@ class Downloader
|
||||
*/
|
||||
std::vector<std::string> getDownloadIds() const;
|
||||
|
||||
private: // data
|
||||
private:
|
||||
mutable std::mutex m_lock;
|
||||
std::map<std::string, std::shared_ptr<Download>> m_knownDownloads;
|
||||
std::shared_ptr<Aria2> mp_aria;
|
||||
|
||||
@@ -55,22 +55,6 @@ enum supportedListMode {
|
||||
NOVALID = 1 << 5
|
||||
};
|
||||
|
||||
enum MigrationMode {
|
||||
/** When migrating bookmarks, do not allow to migrate to an older book than the currently pointed one
|
||||
* (or date stored in the bookmark if book is invalid)
|
||||
*
|
||||
* If no newer books are found, no upgrade is made.
|
||||
*/
|
||||
UPGRADE_ONLY = 0,
|
||||
|
||||
/** Try hard to do a migration. This mostly does:
|
||||
* - Try to find a newer book.
|
||||
* - If book is invalid: find a best book, potentially older.
|
||||
* Older book will never be returned if current book is a valid one.
|
||||
*/
|
||||
ALLOW_DOWNGRADE = 1,
|
||||
};
|
||||
|
||||
class Filter {
|
||||
public: // types
|
||||
using Tags = std::vector<std::string>;
|
||||
@@ -87,7 +71,6 @@ class Filter {
|
||||
std::string _query;
|
||||
bool _queryIsPartial;
|
||||
std::string _name;
|
||||
std::string _flavour;
|
||||
|
||||
public: // functions
|
||||
Filter();
|
||||
@@ -147,7 +130,6 @@ class Filter {
|
||||
Filter& maxSize(size_t size);
|
||||
Filter& query(std::string query, bool partial=true);
|
||||
Filter& name(std::string name);
|
||||
Filter& flavour(std::string flavour);
|
||||
Filter& clearLang();
|
||||
Filter& clearCategory();
|
||||
|
||||
@@ -170,9 +152,6 @@ class Filter {
|
||||
bool hasCreator() const;
|
||||
const std::string& getCreator() const { return _creator; }
|
||||
|
||||
bool hasFlavour() const;
|
||||
const std::string& getFlavour() const { return _flavour; }
|
||||
|
||||
const Tags& getAcceptTags() const { return _acceptTags; }
|
||||
const Tags& getRejectTags() const { return _rejectTags; }
|
||||
|
||||
@@ -271,7 +250,7 @@ class Library: public std::enable_shared_from_this<Library>
|
||||
void addBookmark(const Bookmark& bookmark);
|
||||
|
||||
/**
|
||||
* Remove a bookmark
|
||||
* Remove a bookmarkk
|
||||
*
|
||||
* @param zimId The zimId of the bookmark.
|
||||
* @param url The url of the bookmark.
|
||||
@@ -279,66 +258,6 @@ class Library: public std::enable_shared_from_this<Library>
|
||||
*/
|
||||
bool removeBookmark(const std::string& zimId, const std::string& url);
|
||||
|
||||
/**
|
||||
* Migrate all invalid bookmarks.
|
||||
*
|
||||
* All invalid bookmarks (ie pointing to unknown books, no check is made on bookmark pointing to
|
||||
* invalid articles of valid book) will be migrated (if possible) to a better book.
|
||||
* "Better book", will be determined using method `getBestTargetBookId`.
|
||||
*
|
||||
* @return A tuple<int, int>: <The number of bookmarks updated>, <Number of invalid bookmarks before migration was performed>.
|
||||
*/
|
||||
std::tuple<int, int> migrateBookmarks(MigrationMode migrationMode = ALLOW_DOWNGRADE);
|
||||
|
||||
/**
|
||||
* Migrate all bookmarks associated to a specific book.
|
||||
*
|
||||
* All bookmarks associated to `sourceBookId` book will be migrated to a better book.
|
||||
* "Better book", will be determined using method `getBestTargetBookId`.
|
||||
*
|
||||
* @param sourceBookId the source bookId of the bookmarks to migrate.
|
||||
* @param migrationMode how we will find the best book.
|
||||
* @return The number of bookmarks updated.
|
||||
*/
|
||||
int migrateBookmarks(const std::string& sourceBookId, MigrationMode migrationMode = UPGRADE_ONLY);
|
||||
|
||||
/**
|
||||
* Migrate bookmarks
|
||||
*
|
||||
* Migrate all bookmarks pointing to `source` to `destination`.
|
||||
*
|
||||
* @param sourceBookId the source bookId of the bookmarks to migrate.
|
||||
* @param targetBookId the destination bookId to migrate the bookmarks to.
|
||||
* @return The number of bookmarks updated.
|
||||
*/
|
||||
int migrateBookmarks(const std::string& sourceBookId, const std::string& targetBookId);
|
||||
|
||||
/**
|
||||
* Get the best available bookId for a bookmark.
|
||||
*
|
||||
* Given a bookmark, return the best available bookId.
|
||||
* "best available bookId" is determined using heuristitcs based on book name, flavour and date.
|
||||
*
|
||||
* @param bookmark The bookmark to search the bookId for.
|
||||
* @param migrationMode The migration mode to use.
|
||||
* @return A bookId. Potentially empty string if no suitable book found.
|
||||
*/
|
||||
std::string getBestTargetBookId(const Bookmark& bookmark, MigrationMode migrationMode) const;
|
||||
|
||||
/**
|
||||
* Get the best bookId for a combination of book's name, flavour and date.
|
||||
*
|
||||
* Given a bookName (mandatory), try to find the best book.
|
||||
* If preferedFlavour is given, will try to find a book with the same flavour. If not found, return a book with a different flavour.
|
||||
* If minDate is given, return a book newer than minDate. If not found, return a empty bookId.
|
||||
*
|
||||
* @param bookName The name of the book
|
||||
* @param preferedFlavour The prefered flavour.
|
||||
* @param minDate the minimal book date acceptable. Must be a string in the format "YYYY-MM-DD".
|
||||
* @return A bookId corresponding to the query, or empty string if not found.
|
||||
*/
|
||||
std::string getBestTargetBookId(const std::string& bookName, const std::string& preferedFlavour="", const std::string& minDate="") const;
|
||||
|
||||
// XXX: This is a non-thread-safe operation
|
||||
const Book& getBookById(const std::string& id) const;
|
||||
// XXX: This is a non-thread-safe operation
|
||||
@@ -484,13 +403,12 @@ private: // functions
|
||||
AttributeCounts getBookAttributeCounts(BookStrPropMemFn p) const;
|
||||
std::vector<std::string> getBookPropValueSet(BookStrPropMemFn p) const;
|
||||
BookIdCollection filterViaBookDB(const Filter& filter) const;
|
||||
std::string getBestFromBookCollection(BookIdCollection books, const Bookmark& bookmark, MigrationMode migrationMode) const;
|
||||
unsigned int getBookCount_not_protected(const bool localBooks, const bool remoteBooks) const;
|
||||
void updateBookDB(const Book& book);
|
||||
void dropCache(const std::string& bookId);
|
||||
|
||||
private: //data
|
||||
mutable std::recursive_mutex m_mutex;
|
||||
mutable std::mutex m_mutex;
|
||||
Library::Revision m_revision;
|
||||
std::map<std::string, Entry> m_books;
|
||||
using ArchiveCache = ConcurrentCache<std::string, std::shared_ptr<zim::Archive>>;
|
||||
|
||||
@@ -10,8 +10,7 @@ headers = [
|
||||
'kiwixserve.h',
|
||||
'name_mapper.h',
|
||||
'tools.h',
|
||||
'version.h',
|
||||
'i18n.h'
|
||||
'version.h'
|
||||
]
|
||||
|
||||
install_headers(headers, subdir:'kiwix')
|
||||
|
||||
@@ -54,9 +54,6 @@ class HumanReadableNameMapper : public NameMapper {
|
||||
virtual ~HumanReadableNameMapper() = default;
|
||||
virtual std::string getNameForId(const std::string& id) const;
|
||||
virtual std::string getIdForName(const std::string& name) const;
|
||||
|
||||
private:
|
||||
void mapName(const kiwix::Library& lib, std::string name, std::string id);
|
||||
};
|
||||
|
||||
class UpdatableNameMapper : public NameMapper {
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include "tools.h"
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
@@ -52,7 +51,7 @@ namespace kiwix
|
||||
void stop();
|
||||
|
||||
void setRoot(const std::string& root);
|
||||
void setAddress(const std::string& addr);
|
||||
void setAddress(const std::string& addr) { m_addr = addr; }
|
||||
void setPort(int port) { m_port = port; }
|
||||
void setNbThreads(int threads) { m_nbThreads = threads; }
|
||||
void setMultiZimSearchLimit(unsigned int limit) { m_multizimSearchLimit = limit; }
|
||||
@@ -63,16 +62,14 @@ namespace kiwix
|
||||
{ m_withTaskbar = withTaskbar; m_withLibraryButton = withLibraryButton; }
|
||||
void setBlockExternalLinks(bool blockExternalLinks)
|
||||
{ m_blockExternalLinks = blockExternalLinks; }
|
||||
void setIpMode(IpMode mode) { m_ipMode = mode; }
|
||||
int getPort() const;
|
||||
IpAddress getAddress() const;
|
||||
IpMode getIpMode() const;
|
||||
int getPort();
|
||||
std::string getAddress();
|
||||
|
||||
protected:
|
||||
std::shared_ptr<Library> mp_library;
|
||||
std::shared_ptr<NameMapper> mp_nameMapper;
|
||||
std::string m_root = "";
|
||||
IpAddress m_addr;
|
||||
std::string m_addr = "";
|
||||
std::string m_indexTemplateString = "";
|
||||
int m_port = 80;
|
||||
int m_nbThreads = 1;
|
||||
@@ -81,7 +78,6 @@ namespace kiwix
|
||||
bool m_withTaskbar = true;
|
||||
bool m_withLibraryButton = true;
|
||||
bool m_blockExternalLinks = false;
|
||||
IpMode m_ipMode = IpMode::ipv4;
|
||||
int m_ipConnectionLimit = 0;
|
||||
std::unique_ptr<InternalServer> mp_server;
|
||||
};
|
||||
|
||||
@@ -24,23 +24,8 @@
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <cstdint>
|
||||
#include "common.h"
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
struct IpAddress
|
||||
{
|
||||
std::string addr; // IPv4 address
|
||||
std::string addr6; // IPv6 address
|
||||
|
||||
bool empty() const { return addr.empty() && addr6.empty(); }
|
||||
bool empty4() const { return addr.empty(); }
|
||||
bool empty6() const { return addr6.empty(); }
|
||||
bool valid4(IpMode mode) const { return (mode == IpMode::ipv4 || mode == IpMode::all) && !this->empty4(); }
|
||||
bool valid6(IpMode mode) const { return (mode == IpMode::ipv6 || mode == IpMode::all) && !this->empty6(); }
|
||||
};
|
||||
|
||||
namespace kiwix {
|
||||
typedef std::pair<std::string, std::string> LangNameCodePair;
|
||||
typedef std::vector<LangNameCodePair> FeedLanguages;
|
||||
typedef std::vector<std::string> FeedCategories;
|
||||
@@ -52,6 +37,26 @@ typedef std::vector<std::string> FeedCategories;
|
||||
*/
|
||||
std::string getCurrentDirectory();
|
||||
|
||||
/**
|
||||
* Return the data directory.
|
||||
*
|
||||
* The data directory is a directory where to put data (zim files, ...)
|
||||
* It depends of the platform and it may be changed by user using environment variable.
|
||||
*
|
||||
* The resolution order is :
|
||||
* - `KIWIX_DATA_DIR` env variable (if set).
|
||||
* - On Windows :
|
||||
* . `$APPDATA/kiwix` if $APPDATA is set
|
||||
* . `$USERPROFILE/kiwix` if $USERPROFILE is set
|
||||
* - Else :
|
||||
* . `$XDG_DATA_HOME/kiwix`if $XDG_DATA_HOME is set
|
||||
* . `$HOME/.local/share/kiwx` if $HOWE is set
|
||||
* - current directory
|
||||
*
|
||||
* @return the path of the data directory (utf8 encoded)
|
||||
*/
|
||||
std::string getDataDirectory();
|
||||
|
||||
/** Return the path of the executable
|
||||
*
|
||||
* Some application may be packaged in auto extractible archive (Appimage) and the
|
||||
@@ -205,41 +210,16 @@ bool fileReadable(const std::string& path);
|
||||
std::string getMimeTypeForFile(const std::string& filename);
|
||||
|
||||
/** Provides all available network interfaces
|
||||
*
|
||||
* This function provides the available IPv4 and IPv6 network interfaces
|
||||
* as a map from the interface name to its IPv4 and/or IPv6 address(es).
|
||||
*/
|
||||
std::map<std::string, IpAddress> getNetworkInterfacesIPv4Or6();
|
||||
|
||||
/** Provides all available IPv4 network interfaces
|
||||
*
|
||||
* This function provides the available IPv4 network interfaces
|
||||
* as a map from the interface name to its IPv4 address.
|
||||
*
|
||||
* Provided for backward compatibility with libkiwix v13.1.0.
|
||||
*/
|
||||
std::map<std::string, std::string> getNetworkInterfaces();
|
||||
|
||||
/** Provides the best IP address
|
||||
* This function provides the best IP address from the list given by getNetworkInterfacesIPv4Or6()
|
||||
*/
|
||||
IpAddress getBestPublicIps(IpMode mode);
|
||||
|
||||
/** Provides the best IPv4 adddress
|
||||
* Equivalent to getBestPublicIp(false). Provided for backward compatibility
|
||||
* with libkiwix v13.1.0.
|
||||
* This function provides the best IP address from the list given by getNetworkInterfaces
|
||||
*/
|
||||
std::string getBestPublicIp();
|
||||
|
||||
/** Checks if IP address is available
|
||||
*
|
||||
* Check if the given IP address is configured on any network interface of the system
|
||||
*
|
||||
* @param addr the IP address to check.
|
||||
* @return true if the IP address is available on the system.
|
||||
*/
|
||||
bool ipAvailable(const IpAddress& addr);
|
||||
|
||||
/** Converts file size to human readable format.
|
||||
*
|
||||
* This function will convert a number to its equivalent size using units.
|
||||
@@ -251,15 +231,15 @@ std::string beautifyFileSize(uint64_t number);
|
||||
|
||||
/**
|
||||
* Load languages stored in an OPDS stream.
|
||||
*
|
||||
*
|
||||
* @param content the OPDS stream.
|
||||
* @return vector containing pairs of language code and their corresponding full language name.
|
||||
* @return vector containing pairs of language code and their corresponding full language name.
|
||||
*/
|
||||
FeedLanguages readLanguagesFromFeed(const std::string& content);
|
||||
|
||||
/**
|
||||
* Load categories stored in an OPDS stream .
|
||||
*
|
||||
*
|
||||
* @param content the OPDS stream.
|
||||
* @return vector containing category strings.
|
||||
*/
|
||||
@@ -267,19 +247,10 @@ FeedCategories readCategoriesFromFeed(const std::string& content);
|
||||
|
||||
/**
|
||||
* Retrieve the full language name associated with a given ISO 639-3 language code.
|
||||
*
|
||||
*
|
||||
* @param lang ISO 639-3 language code.
|
||||
* @return full language name.
|
||||
*/
|
||||
std::string getLanguageSelfName(const std::string& lang);
|
||||
|
||||
/**
|
||||
* Slugifies the filename by converting any characters reserved by the operating
|
||||
* system to '_'. Note filename is only the file name and not a path.
|
||||
*
|
||||
* @param filename Valid UTF-8 encoded file name string.
|
||||
* @return slugified string.
|
||||
*/
|
||||
std::string getSlugifiedFileName(const std::string& filename);
|
||||
}
|
||||
#endif // KIWIX_TOOLS_H
|
||||
|
||||
10
kiwix.pc.in
Normal file
10
kiwix.pc.in
Normal file
@@ -0,0 +1,10 @@
|
||||
prefix=@prefix@
|
||||
libdir=${prefix}/lib64
|
||||
includedir=${prefix}/include
|
||||
|
||||
Name: libkiwix
|
||||
Description: A library that contains a lot of things used by used by other kiwix programs
|
||||
Version: @version@
|
||||
Requires: @requires@
|
||||
Libs: -L${libdir} -lkiwix @extra_libs@
|
||||
Cflags: -I${includedir}/ @extra_cflags@
|
||||
40
meson.build
40
meson.build
@@ -1,5 +1,5 @@
|
||||
project('libkiwix', 'cpp',
|
||||
version : '14.0.0',
|
||||
version : '13.0.0',
|
||||
license : 'GPLv3+',
|
||||
default_options : ['c_std=c11', 'cpp_std=c++17', 'werror=true'])
|
||||
|
||||
@@ -35,10 +35,9 @@ else
|
||||
error('Cannot found header mustache.hpp')
|
||||
endif
|
||||
|
||||
libzim_dep = dependency('libzim', version:['>=9.0.0', '<10.0.0'], static:static_deps)
|
||||
|
||||
libzim_dep = dependency('libzim', version : '>=8.1.0', static:static_deps)
|
||||
if not compiler.has_header_symbol('zim/zim.h', 'LIBZIM_WITH_XAPIAN', dependencies: libzim_dep)
|
||||
error('Libzim seems to be compiled without Xapian. Xapian support is mandatory.')
|
||||
error('Libzim seems to be compiled without xapian. Xapian support is mandatory.')
|
||||
endif
|
||||
|
||||
|
||||
@@ -50,14 +49,8 @@ endif
|
||||
|
||||
if host_machine.system() == 'windows'
|
||||
add_project_arguments('-DNOMINMAX', language: 'cpp')
|
||||
extra_libs += ['-liphlpapi']
|
||||
endif
|
||||
|
||||
if build_machine.system() == 'windows'
|
||||
extra_libs += ['-lshlwapi', '-lwinmm']
|
||||
endif
|
||||
|
||||
|
||||
all_deps = [thread_dep, libicu_dep, libzim_dep, pugixml_dep, libcurl_dep, microhttpd_dep, zlib_dep, xapian_dep]
|
||||
|
||||
inc = include_directories('include', extra_include)
|
||||
@@ -65,6 +58,12 @@ inc = include_directories('include', extra_include)
|
||||
conf = configuration_data()
|
||||
conf.set('LIBKIWIX_VERSION', '"@0@"'.format(meson.project_version()))
|
||||
|
||||
if build_machine.system() == 'windows'
|
||||
extra_link_args = ['-lshlwapi', '-lwinmm']
|
||||
else
|
||||
extra_link_args = []
|
||||
endif
|
||||
|
||||
subdir('include')
|
||||
subdir('scripts')
|
||||
subdir('static')
|
||||
@@ -74,10 +73,17 @@ if get_option('doc')
|
||||
subdir('docs')
|
||||
endif
|
||||
|
||||
pkg_mod = import('pkgconfig')
|
||||
pkg_mod.generate(libraries : [libkiwix] + extra_libs,
|
||||
version : meson.project_version(),
|
||||
name : 'libkiwix',
|
||||
filebase : 'libkiwix',
|
||||
description : 'A library that contains useful primitives that Kiwix readers have in common',
|
||||
extra_cflags: extra_cflags)
|
||||
pkg_requires = ['libzim', 'icu-i18n', 'pugixml', 'libcurl', 'libmicrohttpd', 'xapian-core']
|
||||
|
||||
pkg_conf = configuration_data()
|
||||
pkg_conf.set('prefix', get_option('prefix'))
|
||||
pkg_conf.set('requires', ' '.join(pkg_requires))
|
||||
pkg_conf.set('extra_libs', ' '.join(extra_libs))
|
||||
pkg_conf.set('extra_cflags', extra_cflags)
|
||||
pkg_conf.set('version', meson.project_version())
|
||||
configure_file(output : 'kiwix.pc',
|
||||
configuration : pkg_conf,
|
||||
input : 'kiwix.pc.in',
|
||||
install_dir: get_option('libdir')+'/pkgconfig'
|
||||
)
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ lang_table_entry_cxx_template = '''
|
||||
|
||||
cxxfile_template = '''// This file is automatically generated. Do not modify it.
|
||||
|
||||
#include "server/i18n_utils.h"
|
||||
#include "server/i18n.h"
|
||||
|
||||
namespace kiwix {
|
||||
namespace i18n {
|
||||
|
||||
@@ -61,32 +61,6 @@ resource_decl_template = """{namespaces_open}
|
||||
extern const std::string {identifier};
|
||||
{namespaces_close}"""
|
||||
|
||||
BINARY_RESOURCE_EXTENSIONS = {'.ico', '.png', '.ttf'}
|
||||
|
||||
TEXT_RESOURCE_EXTENSIONS = {
|
||||
'.css',
|
||||
'.html',
|
||||
'.js',
|
||||
'.json',
|
||||
'.svg',
|
||||
'.tmpl',
|
||||
'.webmanifest',
|
||||
'.xml',
|
||||
}
|
||||
|
||||
if not BINARY_RESOURCE_EXTENSIONS.isdisjoint(TEXT_RESOURCE_EXTENSIONS):
|
||||
raise RuntimeError(f"The following file type extensions are declared to be both binary and text: {BINARY_RESOURCE_EXTENSIONS.intersection(TEXT_RESOURCE_EXTENSIONS)}")
|
||||
|
||||
def is_binary_resource(filename):
|
||||
_, extension = os.path.splitext(filename)
|
||||
is_binary = extension in BINARY_RESOURCE_EXTENSIONS
|
||||
is_text = extension in TEXT_RESOURCE_EXTENSIONS
|
||||
if not is_binary and not is_text:
|
||||
# all file type extensions of static resources must be listed
|
||||
# in either BINARY_RESOURCE_EXTENSIONS or TEXT_RESOURCE_EXTENSIONS
|
||||
raise RuntimeError(f"Unknown file type extension: {extension}")
|
||||
return is_binary
|
||||
|
||||
class Resource:
|
||||
def __init__(self, base_dirs, filename, cacheid=None):
|
||||
filename = filename
|
||||
@@ -98,8 +72,6 @@ class Resource:
|
||||
try:
|
||||
with open(os.path.join(base_dir, filename), 'rb') as f:
|
||||
self.data = f.read()
|
||||
if not is_binary_resource(filename):
|
||||
self.data = self.data.replace(b"\r\n", b"\n")
|
||||
found = True
|
||||
break
|
||||
except FileNotFoundError:
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include "xmlrpc.h"
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
@@ -30,41 +29,18 @@
|
||||
|
||||
namespace kiwix {
|
||||
|
||||
namespace {
|
||||
|
||||
void pauseAnyActiveDownloads(const std::string& ariaSessionFilePath)
|
||||
{
|
||||
std::ifstream inputFile(ariaSessionFilePath);
|
||||
if ( !inputFile )
|
||||
return;
|
||||
|
||||
std::ostringstream ss;
|
||||
std::string line;
|
||||
while ( std::getline(inputFile, line) ) {
|
||||
if ( !startsWith(line, " pause=") ) {
|
||||
ss << line << "\n";
|
||||
}
|
||||
if ( !line.empty() && line[0] != ' ' && line[0] != '#' ) {
|
||||
ss << " pause=true\n";
|
||||
}
|
||||
}
|
||||
|
||||
std::ofstream outputFile(ariaSessionFilePath);
|
||||
outputFile << ss.str();
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
Aria2::Aria2(std::string sessionFileDir):
|
||||
Aria2::Aria2():
|
||||
mp_aria(nullptr),
|
||||
m_port(42042),
|
||||
m_secret(getNewRpcSecret())
|
||||
{
|
||||
m_downloadDir = getDataDirectory();
|
||||
makeDirectory(m_downloadDir);
|
||||
std::vector<const char*> callCmd;
|
||||
|
||||
std::string rpc_port = "--rpc-listen-port=" + to_string(m_port);
|
||||
std::string session_file = appendToDirectory(sessionFileDir, "kiwix.session");
|
||||
pauseAnyActiveDownloads(session_file);
|
||||
std::string download_dir = "--dir=" + getDataDirectory();
|
||||
std::string session_file = appendToDirectory(getDataDirectory(), "kiwix.session");
|
||||
std::string session = "--save-session=" + session_file;
|
||||
std::string inputFile = "--input-file=" + session_file;
|
||||
// std::string log_dir = "--log=\"" + logDir + "\"";
|
||||
@@ -91,6 +67,7 @@ Aria2::Aria2(std::string sessionFileDir):
|
||||
callCmd.push_back("--enable-rpc");
|
||||
callCmd.push_back(rpc_secret.c_str());
|
||||
callCmd.push_back(rpc_port.c_str());
|
||||
callCmd.push_back(download_dir.c_str());
|
||||
if (fileReadable(session_file)) {
|
||||
callCmd.push_back(inputFile.c_str());
|
||||
}
|
||||
@@ -120,30 +97,20 @@ Aria2::Aria2(std::string sessionFileDir):
|
||||
curl_easy_setopt(p_curl, CURLOPT_PORT, m_port);
|
||||
curl_easy_setopt(p_curl, CURLOPT_POST, 1L);
|
||||
curl_easy_setopt(p_curl, CURLOPT_ERRORBUFFER, curlErrorBuffer);
|
||||
curl_easy_setopt(p_curl, CURLOPT_TIMEOUT_MS, 100);
|
||||
|
||||
typedef std::chrono::duration<double> Seconds;
|
||||
|
||||
const double MAX_WAITING_TIME_SECONDS = 0.5;
|
||||
const auto t0 = std::chrono::steady_clock::now();
|
||||
bool maxWaitingTimeWasExceeded = false;
|
||||
|
||||
CURLcode res = CURLE_OK;
|
||||
while ( !maxWaitingTimeWasExceeded ) {
|
||||
int watchdog = 50;
|
||||
while(--watchdog) {
|
||||
sleep(10);
|
||||
curlErrorBuffer[0] = 0;
|
||||
res = curl_easy_perform(p_curl);
|
||||
auto res = curl_easy_perform(p_curl);
|
||||
if (res == CURLE_OK) {
|
||||
break;
|
||||
} else if (watchdog == 1) {
|
||||
LOG_ARIA_ERROR();
|
||||
}
|
||||
|
||||
const auto dt = std::chrono::steady_clock::now() - t0;
|
||||
const double elapsedTime = std::chrono::duration_cast<Seconds>(dt).count();
|
||||
maxWaitingTimeWasExceeded = elapsedTime > MAX_WAITING_TIME_SECONDS;
|
||||
}
|
||||
curl_easy_cleanup(p_curl);
|
||||
if ( maxWaitingTimeWasExceeded ) {
|
||||
LOG_ARIA_ERROR();
|
||||
if (!watchdog) {
|
||||
throw std::runtime_error("Cannot connect to aria2c rpc. Aria2c launch cmd : " + launchCmd);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,10 +22,11 @@ class Aria2
|
||||
std::unique_ptr<Subprocess> mp_aria;
|
||||
int m_port;
|
||||
std::string m_secret;
|
||||
std::string m_downloadDir;
|
||||
std::string doRequest(const MethodCall& methodCall);
|
||||
|
||||
public:
|
||||
explicit Aria2(std::string sessionFileDir);
|
||||
Aria2();
|
||||
virtual ~Aria2() = default;
|
||||
void close();
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
*/
|
||||
|
||||
#include "bookmark.h"
|
||||
#include "book.h"
|
||||
|
||||
#include <pugixml.hpp>
|
||||
|
||||
@@ -29,17 +28,6 @@ Bookmark::Bookmark()
|
||||
{
|
||||
}
|
||||
|
||||
Bookmark::Bookmark(const Book& book, const std::string& path, const std::string& title):
|
||||
m_bookId(book.getId()),
|
||||
m_bookTitle(book.getTitle()),
|
||||
m_bookName(book.getName()),
|
||||
m_bookFlavour(book.getFlavour()),
|
||||
m_url(path),
|
||||
m_title(title),
|
||||
m_language(book.getCommaSeparatedLanguages()),
|
||||
m_date(book.getDate())
|
||||
{}
|
||||
|
||||
/* Destructor */
|
||||
Bookmark::~Bookmark()
|
||||
{
|
||||
@@ -50,8 +38,6 @@ void Bookmark::updateFromXml(const pugi::xml_node& node)
|
||||
auto bookNode = node.child("book");
|
||||
m_bookId = bookNode.child("id").child_value();
|
||||
m_bookTitle = bookNode.child("title").child_value();
|
||||
m_bookName = bookNode.child("name").child_value();
|
||||
m_bookFlavour = bookNode.child("flavour").child_value();
|
||||
m_language = bookNode.child("language").child_value();
|
||||
m_date = bookNode.child("date").child_value();
|
||||
m_title = node.child("title").child_value();
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
*/
|
||||
|
||||
#include "downloader.h"
|
||||
#include "tools.h"
|
||||
#include "tools/pathTools.h"
|
||||
#include "tools/stringTools.h"
|
||||
|
||||
@@ -125,8 +124,8 @@ void Download::cancelDownload()
|
||||
}
|
||||
|
||||
/* Constructor */
|
||||
Downloader::Downloader(std::string sessionFileDir) :
|
||||
mp_aria(new Aria2(sessionFileDir))
|
||||
Downloader::Downloader() :
|
||||
mp_aria(new Aria2())
|
||||
{
|
||||
try {
|
||||
for (auto gid : mp_aria->tellWaiting()) {
|
||||
@@ -151,20 +150,11 @@ Downloader::Downloader(std::string sessionFileDir) :
|
||||
/* Destructor */
|
||||
Downloader::~Downloader()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
void Downloader::close()
|
||||
{
|
||||
if ( mp_aria ) {
|
||||
try {
|
||||
mp_aria->close();
|
||||
} catch (const std::exception& err) {
|
||||
std::cerr << "ERROR: Failed to save the downloader state: "
|
||||
<< err.what() << std::endl;
|
||||
}
|
||||
mp_aria.reset();
|
||||
}
|
||||
mp_aria->close();
|
||||
}
|
||||
|
||||
std::vector<std::string> Downloader::getDownloadIds() const {
|
||||
@@ -176,51 +166,9 @@ std::vector<std::string> Downloader::getDownloadIds() const {
|
||||
return ret;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
bool downloadCanBeReused(const Download& d,
|
||||
const std::string& uri,
|
||||
const Downloader::Options& /*options*/)
|
||||
{
|
||||
const auto& uris = d.getUris();
|
||||
const bool sameURI = std::find(uris.begin(), uris.end(), uri) != uris.end();
|
||||
|
||||
if ( !sameURI )
|
||||
return false;
|
||||
|
||||
switch ( d.getStatus() ) {
|
||||
case Download::K_ERROR:
|
||||
case Download::K_UNKNOWN:
|
||||
case Download::K_REMOVED:
|
||||
return false;
|
||||
|
||||
case Download::K_ACTIVE:
|
||||
case Download::K_WAITING:
|
||||
case Download::K_PAUSED:
|
||||
return true; // XXX: what if options are different?
|
||||
|
||||
case Download::K_COMPLETE:
|
||||
return fileExists(d.getPath()); // XXX: what if options are different?
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
std::shared_ptr<Download> Downloader::startDownload(const std::string& uri, const std::string& downloadDir, Options options)
|
||||
std::shared_ptr<Download> Downloader::startDownload(const std::string& uri, const std::vector<std::pair<std::string, std::string>>& options)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_lock);
|
||||
options.erase(std::remove_if(options.begin(), options.end(), [](const auto& option) {
|
||||
return option.first == "dir";
|
||||
}), options.end());
|
||||
options.push_back({"dir", downloadDir});
|
||||
for (auto& p: m_knownDownloads) {
|
||||
auto& d = p.second;
|
||||
if ( downloadCanBeReused(*d, uri, options) )
|
||||
return d;
|
||||
}
|
||||
std::vector<std::string> uris = {uri};
|
||||
auto gid = mp_aria->addUri(uris, options);
|
||||
m_knownDownloads[gid] = std::make_shared<Download>(mp_aria, gid);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "tools/otherTools.h"
|
||||
#include "tools.h"
|
||||
#include "tools/regexTools.h"
|
||||
#include "server/i18n_utils.h"
|
||||
#include "server/i18n.h"
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
@@ -77,7 +77,7 @@ std::string HTMLDumper::dumpPlainHTML(kiwix::Filter filter) const
|
||||
const auto tags = bookObj.getTags();
|
||||
const auto downloadAvailable = (bookObj.getUrl() != "");
|
||||
std::string faviconAttr = "style=background-image:url(" + bookIconUrl + ")";
|
||||
|
||||
|
||||
booksData.push_back(kainjow::mustache::object{
|
||||
{"id", contentId},
|
||||
{"title", bookTitle},
|
||||
|
||||
211
src/library.cpp
211
src/library.cpp
@@ -110,7 +110,7 @@ Library::~Library() = default;
|
||||
|
||||
bool Library::addBook(const Book& book)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
++m_revision;
|
||||
/* Try to find it */
|
||||
updateBookDB(book);
|
||||
@@ -141,13 +141,13 @@ bool Library::addBook(const Book& book)
|
||||
|
||||
void Library::addBookmark(const Bookmark& bookmark)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
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::recursive_mutex> lock(m_mutex);
|
||||
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);
|
||||
@@ -157,159 +157,6 @@ bool Library::removeBookmark(const std::string& zimId, const std::string& url)
|
||||
return false;
|
||||
}
|
||||
|
||||
std::tuple<int, int> Library::migrateBookmarks(MigrationMode migrationMode) {
|
||||
std::set<std::string> sourceBooks;
|
||||
int invalidBookmarks = 0;
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
for(auto& bookmark:m_bookmarks) {
|
||||
if (m_books.find(bookmark.getBookId()) == m_books.end()) {
|
||||
invalidBookmarks += 1;
|
||||
sourceBooks.insert(bookmark.getBookId());
|
||||
}
|
||||
}
|
||||
}
|
||||
int changed = 0;
|
||||
for(auto& sourceBook:sourceBooks) {
|
||||
changed += migrateBookmarks(sourceBook, migrationMode);
|
||||
}
|
||||
return std::make_tuple(changed, invalidBookmarks);
|
||||
}
|
||||
|
||||
std::string Library::getBestFromBookCollection(BookIdCollection books, const Bookmark& bookmark, MigrationMode migrationMode) const {
|
||||
// This function try to get the best book for a bookmark from a book collection.
|
||||
// It assumes that all books in the collection are "acceptable".
|
||||
// (this definiton is not clear but for now it is book's name is equal to bookmark's bookName)
|
||||
//
|
||||
// The algorithm first sort the colletion by "flavour equality" and date.
|
||||
// "flavour equality" is if book's flavour is same that bookmark's flavour (let's say "flavourA" here)
|
||||
// So we have the sorted collection:
|
||||
// - flavourA, date 5
|
||||
// - flavourA, date 4
|
||||
// - flavourB, date 6
|
||||
// - flavourC, date 5
|
||||
// - flavourB, date 3
|
||||
//
|
||||
// Then, depending of migrationMode:
|
||||
// - If ALLOW_DOWNGRADE => take the first one
|
||||
// - If UPGRADE_ONLY => loop on books until we find a book newer than bookmark.
|
||||
// So if bookmark date is 5 => flavourB, date 6
|
||||
// if bookmark date is 4 => flavourA, date 5
|
||||
// if bookmark date is 7 => No book
|
||||
|
||||
if (books.empty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
sort(books, DATE, false);
|
||||
stable_sort(books.begin(), books.end(), [&](const std::string& bookId1, const std::string& bookId2) {
|
||||
const auto& book1 = getBookById(bookId1);
|
||||
const auto& book2 = getBookById(bookId2);
|
||||
bool same_flavour1 = book1.getFlavour() == bookmark.getBookFlavour();
|
||||
bool same_flavour2 = book2.getFlavour() == bookmark.getBookFlavour();
|
||||
// return True if bookId1 is before bookId2, ie if same_flavour1 and not same_flavour2
|
||||
return same_flavour1 > same_flavour2;
|
||||
});
|
||||
|
||||
if (migrationMode == ALLOW_DOWNGRADE) {
|
||||
return books[0];
|
||||
} else {
|
||||
for (const auto& bookId: books) {
|
||||
const auto& book = getBookById(bookId);
|
||||
if (book.getDate() >= bookmark.getDate()) {
|
||||
return bookId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string remove_quote(std::string input) {
|
||||
std::replace(input.begin(), input.end(), '"', ' ');
|
||||
return input;
|
||||
}
|
||||
|
||||
std::string Library::getBestTargetBookId(const std::string& bookName, const std::string& preferedFlavour, const std::string& minDate) const {
|
||||
// Let's reuse our algorithm based on bookmark.
|
||||
MigrationMode migrationMode = UPGRADE_ONLY;
|
||||
auto bookmark = Bookmark();
|
||||
bookmark.setBookName(bookName);
|
||||
bookmark.setBookFlavour(preferedFlavour);
|
||||
|
||||
if (minDate.empty()) {
|
||||
migrationMode = ALLOW_DOWNGRADE;
|
||||
} else {
|
||||
bookmark.setDate(minDate);
|
||||
}
|
||||
|
||||
return getBestTargetBookId(bookmark, migrationMode);
|
||||
}
|
||||
|
||||
std::string Library::getBestTargetBookId(const Bookmark& bookmark, MigrationMode migrationMode) const {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
// Search for a existing book with the same name
|
||||
auto book_filter = Filter();
|
||||
if (!bookmark.getBookName().empty()) {
|
||||
book_filter.name(bookmark.getBookName());
|
||||
} else {
|
||||
// We don't have a name stored (older bookmarks)
|
||||
// Fallback on title (All bookmarks should have one, but let's be safe against wrongly filled bookmark)
|
||||
if (bookmark.getBookTitle().empty()) {
|
||||
// No bookName nor bookTitle, no way to find target book.
|
||||
return "";
|
||||
}
|
||||
book_filter.query("title:\"" + remove_quote(bookmark.getBookTitle()) + "\"");
|
||||
}
|
||||
auto targetBooks = filter(book_filter);
|
||||
auto bestBook = getBestFromBookCollection(targetBooks, bookmark, migrationMode);
|
||||
if (bestBook.empty()) {
|
||||
try {
|
||||
getBookById(bookmark.getBookId());
|
||||
return bookmark.getBookId();
|
||||
} catch (std::out_of_range&) {}
|
||||
}
|
||||
return bestBook;
|
||||
}
|
||||
|
||||
int Library::migrateBookmarks(const std::string& sourceBookId, MigrationMode migrationMode) {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
|
||||
Bookmark firstBookmarkToChange;
|
||||
for(auto& bookmark:m_bookmarks) {
|
||||
if (bookmark.getBookId() == sourceBookId) {
|
||||
firstBookmarkToChange = bookmark;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstBookmarkToChange.getBookId().empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string betterBook = getBestTargetBookId(firstBookmarkToChange, migrationMode);
|
||||
|
||||
if (betterBook.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return migrateBookmarks(sourceBookId, betterBook);
|
||||
}
|
||||
|
||||
int Library::migrateBookmarks(const std::string& sourceBookId, const std::string& targetBookId) {
|
||||
if (sourceBookId == targetBookId) {
|
||||
return 0;
|
||||
}
|
||||
int changed = 0;
|
||||
for (auto& bookmark:m_bookmarks) {
|
||||
if (bookmark.getBookId() == sourceBookId) {
|
||||
bookmark.setBookId(targetBookId);
|
||||
changed +=1;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
|
||||
void Library::dropCache(const std::string& id)
|
||||
{
|
||||
@@ -319,7 +166,7 @@ void Library::dropCache(const std::string& id)
|
||||
|
||||
bool Library::removeBookById(const std::string& id)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_bookDB->delete_document("Q" + id);
|
||||
dropCache(id);
|
||||
// We do not change the cache size here
|
||||
@@ -337,7 +184,7 @@ bool Library::removeBookById(const std::string& id)
|
||||
|
||||
Library::Revision Library::getRevision() const
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return m_revision;
|
||||
}
|
||||
|
||||
@@ -345,7 +192,7 @@ uint32_t Library::removeBooksNotUpdatedSince(Revision libraryRevision)
|
||||
{
|
||||
BookIdCollection booksToRemove;
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
for ( const auto& entry : m_books) {
|
||||
if ( entry.second.lastUpdatedRevision <= libraryRevision ) {
|
||||
booksToRemove.push_back(entry.first);
|
||||
@@ -370,7 +217,7 @@ const Book& Library::getBookById(const std::string& id) const
|
||||
|
||||
Book Library::getBookByIdThreadSafe(const std::string& id) const
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return getBookById(id);
|
||||
}
|
||||
|
||||
@@ -428,7 +275,7 @@ std::shared_ptr<ZimSearcher> Library::getSearcherByIds(const BookIdSet& ids)
|
||||
unsigned int Library::getBookCount(const bool localBooks,
|
||||
const bool remoteBooks) const
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return getBookCount_not_protected(localBooks, remoteBooks);
|
||||
}
|
||||
|
||||
@@ -441,7 +288,7 @@ bool Library::writeToFile(const std::string& path) const
|
||||
dumper.setBaseDir(baseDir);
|
||||
std::string xml;
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
xml = dumper.dumpLibXMLContent(allBookIds);
|
||||
};
|
||||
return writeTextFile(path, xml);
|
||||
@@ -457,7 +304,7 @@ bool Library::writeBookmarksToFile(const std::string& path) const
|
||||
|
||||
Library::AttributeCounts Library::getBookAttributeCounts(BookStrPropMemFn p) const
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
AttributeCounts propValueCounts;
|
||||
|
||||
for (const auto& pair: m_books) {
|
||||
@@ -489,7 +336,7 @@ std::vector<std::string> Library::getBooksLanguages() const
|
||||
|
||||
Library::AttributeCounts Library::getBooksLanguagesWithCounts() const
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
AttributeCounts langsWithCounts;
|
||||
|
||||
for (const auto& pair: m_books) {
|
||||
@@ -505,7 +352,7 @@ Library::AttributeCounts Library::getBooksLanguagesWithCounts() const
|
||||
|
||||
std::vector<std::string> Library::getBooksCategories() const
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
std::set<std::string> categories;
|
||||
|
||||
for (const auto& pair: m_books) {
|
||||
@@ -536,7 +383,7 @@ const std::vector<kiwix::Bookmark> Library::getBookmarks(bool onlyValidBookmarks
|
||||
}
|
||||
std::vector<kiwix::Bookmark> validBookmarks;
|
||||
auto booksId = getBooksIds();
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
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);
|
||||
@@ -547,7 +394,7 @@ const std::vector<kiwix::Bookmark> Library::getBookmarks(bool onlyValidBookmarks
|
||||
|
||||
Library::BookIdCollection Library::getBooksIds() const
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
BookIdCollection bookIds;
|
||||
|
||||
for (auto& pair: m_books) {
|
||||
@@ -590,7 +437,6 @@ void Library::updateBookDB(const Book& book)
|
||||
indexer.index_text(normalizeText(book.getCreator()), 1, "A");
|
||||
indexer.index_text(normalizeText(book.getPublisher()), 1, "XP");
|
||||
doc.add_term("XN"+normalizeText(book.getName()));
|
||||
indexer.index_text(normalizeText(book.getFlavour()), 1, "XF");
|
||||
indexer.index_text(normalizeText(book.getCategory()), 1, "XC");
|
||||
|
||||
for ( const auto& tag : split(normalizeText(book.getTags()), ";") ) {
|
||||
@@ -631,7 +477,6 @@ Xapian::Query buildXapianQueryFromFilterQuery(const Filter& filter)
|
||||
queryParser.add_prefix("title", "S");
|
||||
queryParser.add_prefix("description", "XD");
|
||||
queryParser.add_prefix("name", "XN");
|
||||
queryParser.add_prefix("flavour", "XF");
|
||||
queryParser.add_prefix("category", "XC");
|
||||
queryParser.add_prefix("lang", "L");
|
||||
queryParser.add_prefix("publisher", "XP");
|
||||
@@ -658,11 +503,6 @@ Xapian::Query nameQuery(const std::string& name)
|
||||
return Xapian::Query("XN" + normalizeText(name));
|
||||
}
|
||||
|
||||
Xapian::Query flavourQuery(const std::string& name)
|
||||
{
|
||||
return Xapian::Query("XF" + normalizeText(name));
|
||||
}
|
||||
|
||||
Xapian::Query multipleParamQuery(const std::string& commaSeparatedList, const std::string& prefix)
|
||||
{
|
||||
Xapian::Query q;
|
||||
@@ -730,9 +570,6 @@ Xapian::Query buildXapianQuery(const Filter& filter)
|
||||
if ( filter.hasName() ) {
|
||||
q = Xapian::Query(Xapian::Query::OP_AND, q, nameQuery(filter.getName()));
|
||||
}
|
||||
if ( filter.hasFlavour() ) {
|
||||
q = Xapian::Query(Xapian::Query::OP_AND, q, flavourQuery(filter.getFlavour()));
|
||||
}
|
||||
if ( filter.hasCategory() ) {
|
||||
q = Xapian::Query(Xapian::Query::OP_AND, q, categoryQuery(filter.getCategory()));
|
||||
}
|
||||
@@ -763,7 +600,7 @@ Library::BookIdCollection Library::filterViaBookDB(const Filter& filter) const
|
||||
|
||||
BookIdCollection bookIds;
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
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());
|
||||
@@ -778,7 +615,7 @@ Library::BookIdCollection Library::filter(const Filter& filter) const
|
||||
{
|
||||
BookIdCollection result;
|
||||
const auto preliminaryResult = filterViaBookDB(filter);
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
for(auto id : preliminaryResult) {
|
||||
if(filter.accept(m_books.at(id))) {
|
||||
result.push_back(id);
|
||||
@@ -852,7 +689,7 @@ void Library::sort(BookIdCollection& bookIds, supportedListSortBy sort, bool asc
|
||||
// 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::recursive_mutex> lock(m_mutex);
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
switch(sort) {
|
||||
case TITLE:
|
||||
std::sort(bookIds.begin(), bookIds.end(), Comparator<TITLE>(this, ascending));
|
||||
@@ -898,7 +735,6 @@ enum filterTypes {
|
||||
QUERY = FLAG(12),
|
||||
NAME = FLAG(13),
|
||||
CATEGORY = FLAG(14),
|
||||
FLAVOUR = FLAG(15),
|
||||
};
|
||||
|
||||
Filter& Filter::local(bool accept)
|
||||
@@ -1000,13 +836,6 @@ Filter& Filter::name(std::string name)
|
||||
activeFilters |= NAME;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Filter& Filter::flavour(std::string flavour)
|
||||
{
|
||||
_flavour = flavour;
|
||||
activeFilters |= FLAVOUR;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Filter& Filter::clearLang()
|
||||
{
|
||||
@@ -1052,12 +881,6 @@ bool Filter::hasCreator() const
|
||||
return ACTIVE(_CREATOR);
|
||||
}
|
||||
|
||||
bool Filter::hasFlavour() const
|
||||
{
|
||||
return ACTIVE(FLAVOUR);
|
||||
}
|
||||
|
||||
|
||||
bool Filter::accept(const Book& book) const
|
||||
{
|
||||
auto local = !book.getPath().empty();
|
||||
|
||||
@@ -97,15 +97,11 @@ void LibXMLDumper::handleBookmark(Bookmark bookmark, pugi::xml_node root_node) {
|
||||
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, "name", book.getName());
|
||||
ADD_TEXT_ENTRY(book_node, "flavour", book.getFlavour());
|
||||
ADD_TEXT_ENTRY(book_node, "language", book.getCommaSeparatedLanguages());
|
||||
ADD_TEXT_ENTRY(book_node, "date", book.getDate());
|
||||
} catch (...) {
|
||||
ADD_TEXT_ENTRY(book_node, "id", bookmark.getBookId());
|
||||
ADD_TEXT_ENTRY(book_node, "title", bookmark.getBookTitle());
|
||||
ADD_TEXT_ENTRY(book_node, "name", bookmark.getBookName());
|
||||
ADD_TEXT_ENTRY(book_node, "flavour", bookmark.getBookFlavour());
|
||||
ADD_TEXT_ENTRY(book_node, "language", bookmark.getLanguage());
|
||||
ADD_TEXT_ENTRY(book_node, "date", bookmark.getDate());
|
||||
}
|
||||
@@ -139,7 +135,7 @@ std::string LibXMLDumper::dumpLibXMLBookmark()
|
||||
pugi::xml_node bookmarksNode = doc.append_child("bookmarks");
|
||||
|
||||
if (library) {
|
||||
for (auto& bookmark: library->getBookmarks(false)) {
|
||||
for (auto& bookmark: library->getBookmarks()) {
|
||||
handleBookmark(bookmark, bookmarksNode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,29 +29,25 @@ HumanReadableNameMapper::HumanReadableNameMapper(kiwix::Library& library, bool w
|
||||
auto& currentBook = library.getBookById(bookId);
|
||||
auto bookName = currentBook.getHumanReadableIdFromPath();
|
||||
m_idToName[bookId] = bookName;
|
||||
mapName(library, bookName, bookId);
|
||||
m_nameToId[bookName] = bookId;
|
||||
|
||||
if (!withAlias)
|
||||
continue;
|
||||
|
||||
auto aliasName = replaceRegex(bookName, "", "_[[:digit:]]{4}-[[:digit:]]{2}$");
|
||||
if (aliasName != bookName) {
|
||||
mapName(library, aliasName, bookId);
|
||||
if (aliasName == bookName) {
|
||||
continue;
|
||||
}
|
||||
if (m_nameToId.find(aliasName) == m_nameToId.end()) {
|
||||
m_nameToId[aliasName] = bookId;
|
||||
} else {
|
||||
auto alreadyPresentPath = library.getBookById(m_nameToId[aliasName]).getPath();
|
||||
std::cerr << "Path collision: " << alreadyPresentPath
|
||||
<< " and " << currentBook.getPath()
|
||||
<< " can't share the same URL path '" << aliasName << "'."
|
||||
<< " Therefore, only " << alreadyPresentPath
|
||||
<< " will be served." << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HumanReadableNameMapper::mapName(const Library& library, std::string name, std::string bookId) {
|
||||
if (m_nameToId.find(name) == m_nameToId.end()) {
|
||||
m_nameToId[name] = bookId;
|
||||
} else {
|
||||
const auto& currentBook = library.getBookById(bookId);
|
||||
auto alreadyPresentPath = library.getBookById(m_nameToId[name]).getPath();
|
||||
std::cerr << "Path collision: '" << alreadyPresentPath
|
||||
<< "' and '" << currentBook.getPath()
|
||||
<< "' can't share the same URL path '" << name << "'."
|
||||
<< " Therefore, only '" << alreadyPresentPath
|
||||
<< "' will be served." << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
#include "libkiwix-resources.h"
|
||||
#include "tools/stringTools.h"
|
||||
|
||||
#include "server/i18n_utils.h"
|
||||
#include "server/i18n.h"
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
@@ -51,7 +51,6 @@ bool Server::start() {
|
||||
m_withTaskbar,
|
||||
m_withLibraryButton,
|
||||
m_blockExternalLinks,
|
||||
m_ipMode,
|
||||
m_indexTemplateString,
|
||||
m_ipConnectionLimit));
|
||||
return mp_server->start();
|
||||
@@ -75,32 +74,14 @@ void Server::setRoot(const std::string& root)
|
||||
}
|
||||
}
|
||||
|
||||
void Server::setAddress(const std::string& addr)
|
||||
{
|
||||
if (addr.empty())
|
||||
return;
|
||||
|
||||
if (addr.find(':') != std::string::npos) { // IPv6
|
||||
for (char ch : addr) if (ch == '[' or ch == ']') ch = '\0'; // Clean possible brackets
|
||||
m_addr.addr6 = addr;
|
||||
} else {
|
||||
m_addr.addr = addr;
|
||||
}
|
||||
}
|
||||
|
||||
int Server::getPort() const
|
||||
int Server::getPort()
|
||||
{
|
||||
return mp_server->getPort();
|
||||
}
|
||||
|
||||
IpAddress Server::getAddress() const
|
||||
std::string Server::getAddress()
|
||||
{
|
||||
return mp_server->getAddress();
|
||||
}
|
||||
|
||||
IpMode Server::getIpMode() const
|
||||
{
|
||||
return mp_server->getIpMode();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "i18n_utils.h"
|
||||
#include "i18n.h"
|
||||
|
||||
#include "tools/otherTools.h"
|
||||
|
||||
@@ -193,13 +193,4 @@ std::string selectMostSuitableLanguage(const UserLangPreferences& prefs)
|
||||
return bestLangSoFar;
|
||||
}
|
||||
|
||||
std::string translateBookCategory(const std::string& lang, const std::string& category)
|
||||
{
|
||||
try {
|
||||
return getTranslatedString(lang, "book-category." + category);
|
||||
} catch (...) {
|
||||
return category;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace kiwix
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 Veloman Yunkan <veloman.yunkan@gmail.com>
|
||||
* Copyright 2022 Veloman Yunkan <veloman.yunkan@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,15 +17,29 @@
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef KIWIX_I18N
|
||||
#define KIWIX_I18N
|
||||
#ifndef KIWIX_SERVER_I18N
|
||||
#define KIWIX_SERVER_I18N
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <mustache.hpp>
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
struct I18nString {
|
||||
const char* const key;
|
||||
const char* const value;
|
||||
};
|
||||
|
||||
struct I18nStringTable {
|
||||
const char* const lang;
|
||||
const size_t entryCount;
|
||||
const I18nString* const entries;
|
||||
|
||||
const char* get(const std::string& key) const;
|
||||
};
|
||||
|
||||
std::string getTranslatedString(const std::string& lang, const std::string& key);
|
||||
|
||||
namespace i18n
|
||||
@@ -56,6 +70,28 @@ private:
|
||||
const std::string m_lang;
|
||||
};
|
||||
|
||||
class GetTranslatedStringWithMsgId
|
||||
{
|
||||
typedef kainjow::mustache::basic_data<std::string> MustacheString;
|
||||
typedef std::pair<std::string, MustacheString> MsgIdAndTranslation;
|
||||
|
||||
public:
|
||||
explicit GetTranslatedStringWithMsgId(const std::string& lang) : m_lang(lang) {}
|
||||
|
||||
MsgIdAndTranslation operator()(const std::string& key) const
|
||||
{
|
||||
return {key, getTranslatedString(m_lang, key)};
|
||||
}
|
||||
|
||||
MsgIdAndTranslation operator()(const std::string& key, const Parameters& params) const
|
||||
{
|
||||
return {key, expandParameterizedString(m_lang, key, params)};
|
||||
}
|
||||
|
||||
private:
|
||||
const std::string m_lang;
|
||||
};
|
||||
|
||||
} // namespace i18n
|
||||
|
||||
class ParameterizedMessage
|
||||
@@ -85,8 +121,18 @@ inline ParameterizedMessage nonParameterizedMessage(const std::string& msgId)
|
||||
return ParameterizedMessage(msgId, noParams);
|
||||
}
|
||||
|
||||
std::string translateBookCategory(const std::string& lang, const std::string& category);
|
||||
struct LangPreference
|
||||
{
|
||||
const std::string lang;
|
||||
const float preference;
|
||||
};
|
||||
|
||||
typedef std::vector<LangPreference> UserLangPreferences;
|
||||
|
||||
UserLangPreferences parseUserLanguagePreferences(const std::string& s);
|
||||
|
||||
std::string selectMostSuitableLanguage(const UserLangPreferences& prefs);
|
||||
|
||||
} // namespace kiwix
|
||||
|
||||
#endif // KIWIX_I18N
|
||||
#endif // KIWIX_SERVER_I18N
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 Veloman Yunkan <veloman.yunkan@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 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_SERVER_I18N_UTILS
|
||||
#define KIWIX_SERVER_I18N_UTILS
|
||||
|
||||
#include "i18n.h"
|
||||
#include <mustache.hpp>
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
struct I18nString {
|
||||
const char* const key;
|
||||
const char* const value;
|
||||
};
|
||||
|
||||
struct I18nStringTable {
|
||||
const char* const lang;
|
||||
const size_t entryCount;
|
||||
const I18nString* const entries;
|
||||
|
||||
const char* get(const std::string& key) const;
|
||||
};
|
||||
|
||||
namespace i18n
|
||||
{
|
||||
|
||||
class GetTranslatedStringWithMsgId
|
||||
{
|
||||
typedef kainjow::mustache::basic_data<std::string> MustacheString;
|
||||
typedef std::pair<std::string, MustacheString> MsgIdAndTranslation;
|
||||
|
||||
public:
|
||||
explicit GetTranslatedStringWithMsgId(const std::string& lang) : m_lang(lang) {}
|
||||
|
||||
MsgIdAndTranslation operator()(const std::string& key) const
|
||||
{
|
||||
return {key, getTranslatedString(m_lang, key)};
|
||||
}
|
||||
|
||||
MsgIdAndTranslation operator()(const std::string& key, const Parameters& params) const
|
||||
{
|
||||
return {key, expandParameterizedString(m_lang, key, params)};
|
||||
}
|
||||
|
||||
private:
|
||||
const std::string m_lang;
|
||||
};
|
||||
|
||||
} // namespace i18n
|
||||
|
||||
struct LangPreference
|
||||
{
|
||||
const std::string lang;
|
||||
const float preference;
|
||||
};
|
||||
|
||||
typedef std::vector<LangPreference> UserLangPreferences;
|
||||
|
||||
UserLangPreferences parseUserLanguagePreferences(const std::string& s);
|
||||
|
||||
std::string selectMostSuitableLanguage(const UserLangPreferences& prefs);
|
||||
|
||||
} // namespace kiwix
|
||||
|
||||
#endif // KIWIX_SERVER_I18N_UTILS
|
||||
@@ -54,7 +54,7 @@ extern "C" {
|
||||
#include "search_renderer.h"
|
||||
#include "opds_dumper.h"
|
||||
#include "html_dumper.h"
|
||||
#include "i18n_utils.h"
|
||||
#include "i18n.h"
|
||||
|
||||
#include <zim/uuid.h>
|
||||
#include <zim/error.h>
|
||||
@@ -407,7 +407,7 @@ public:
|
||||
|
||||
InternalServer::InternalServer(LibraryPtr library,
|
||||
std::shared_ptr<NameMapper> nameMapper,
|
||||
IpAddress addr,
|
||||
std::string addr,
|
||||
int port,
|
||||
std::string root,
|
||||
int nbThreads,
|
||||
@@ -416,7 +416,6 @@ InternalServer::InternalServer(LibraryPtr library,
|
||||
bool withTaskbar,
|
||||
bool withLibraryButton,
|
||||
bool blockExternalLinks,
|
||||
IpMode ipMode,
|
||||
std::string indexTemplateString,
|
||||
int ipConnectionLimit) :
|
||||
m_addr(addr),
|
||||
@@ -429,7 +428,6 @@ InternalServer::InternalServer(LibraryPtr library,
|
||||
m_withTaskbar(withTaskbar),
|
||||
m_withLibraryButton(withLibraryButton),
|
||||
m_blockExternalLinks(blockExternalLinks),
|
||||
m_ipMode(ipMode),
|
||||
m_indexTemplateString(indexTemplateString.empty() ? RESOURCE::templates::index_html : indexTemplateString),
|
||||
m_ipConnectionLimit(ipConnectionLimit),
|
||||
mp_daemon(nullptr),
|
||||
@@ -453,53 +451,28 @@ bool InternalServer::start() {
|
||||
if (m_verbose.load())
|
||||
flags |= MHD_USE_DEBUG;
|
||||
|
||||
|
||||
struct sockaddr_in sockAddr4={0};
|
||||
sockAddr4.sin_family = AF_INET;
|
||||
sockAddr4.sin_port = htons(m_port);
|
||||
struct sockaddr_in6 sockAddr6={0};
|
||||
sockAddr6.sin6_family = AF_INET6;
|
||||
sockAddr6.sin6_port = htons(m_port);
|
||||
|
||||
struct sockaddr_in sockAddr;
|
||||
memset(&sockAddr, 0, sizeof(sockAddr));
|
||||
sockAddr.sin_family = AF_INET;
|
||||
sockAddr.sin_port = htons(m_port);
|
||||
if (m_addr.empty()) {
|
||||
sockAddr6.sin6_addr = in6addr_any;
|
||||
sockAddr4.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
m_addr = kiwix::getBestPublicIps(m_ipMode);
|
||||
if (0 != INADDR_ANY) {
|
||||
sockAddr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
}
|
||||
m_addr = kiwix::getBestPublicIp();
|
||||
} else {
|
||||
bool ipv6 = inet_pton(AF_INET6, m_addr.addr6.c_str(), &(sockAddr6.sin6_addr.s6_addr)) == 1;
|
||||
bool ipv4 = inet_pton(AF_INET, m_addr.addr.c_str(), &(sockAddr4.sin_addr.s_addr)) == 1;
|
||||
|
||||
if (ipv6){
|
||||
m_ipMode = IpMode::ipv6;
|
||||
} else if (!ipv4) {
|
||||
const std::string& address = m_addr.addr.empty() ? m_addr.addr6 : m_addr.addr;
|
||||
std::cerr << "IP address " << address << " is not a valid IP address" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ipAvailable(m_addr)) {
|
||||
std::cerr << "IP address " << (m_addr.addr.empty() ? m_addr.addr6 : m_addr.addr) << " is not available on this system" << std::endl;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_ipMode == IpMode::all) {
|
||||
flags|=MHD_USE_DUAL_STACK;
|
||||
} else if (m_ipMode == IpMode::ipv6) {
|
||||
flags|=MHD_USE_IPv6;
|
||||
}
|
||||
|
||||
struct sockaddr* sockaddr = (m_ipMode==IpMode::all || m_ipMode==IpMode::ipv6)
|
||||
? (struct sockaddr*)&sockAddr6
|
||||
: (struct sockaddr*)&sockAddr4;
|
||||
|
||||
mp_daemon = MHD_start_daemon(flags,
|
||||
m_port,
|
||||
NULL,
|
||||
NULL,
|
||||
&staticHandlerCallback,
|
||||
this,
|
||||
MHD_OPTION_SOCK_ADDR, sockaddr,
|
||||
MHD_OPTION_SOCK_ADDR, &sockAddr,
|
||||
MHD_OPTION_THREAD_POOL_SIZE, m_nbThreads,
|
||||
MHD_OPTION_PER_IP_CONNECTION_LIMIT, m_ipConnectionLimit,
|
||||
MHD_OPTION_END);
|
||||
@@ -964,7 +937,7 @@ std::unique_ptr<Response> InternalServer::handle_search_request(const RequestCon
|
||||
} catch(std::runtime_error& e) {
|
||||
// Searcher->search will throw a runtime error if there is no valid xapian database to do the search.
|
||||
// (in case of zim file not containing a index)
|
||||
const auto cssUrl = renderUrl(m_root, RESOURCE::templates::url_of_search_results_css_tmpl);
|
||||
const auto cssUrl = renderUrl(m_root, RESOURCE::templates::url_of_search_results_css);
|
||||
HTTPErrorResponse response(request, MHD_HTTP_NOT_FOUND,
|
||||
"fulltext-search-unavailable",
|
||||
"404-page-heading",
|
||||
|
||||
@@ -27,7 +27,6 @@ extern "C" {
|
||||
|
||||
#include "library.h"
|
||||
#include "name_mapper.h"
|
||||
#include "tools.h"
|
||||
|
||||
#include <zim/search.h>
|
||||
#include <zim/suggestion.h>
|
||||
@@ -95,7 +94,7 @@ class InternalServer {
|
||||
public:
|
||||
InternalServer(LibraryPtr library,
|
||||
std::shared_ptr<NameMapper> nameMapper,
|
||||
IpAddress addr,
|
||||
std::string addr,
|
||||
int port,
|
||||
std::string root,
|
||||
int nbThreads,
|
||||
@@ -104,7 +103,6 @@ class InternalServer {
|
||||
bool withTaskbar,
|
||||
bool withLibraryButton,
|
||||
bool blockExternalLinks,
|
||||
IpMode ipMode,
|
||||
std::string indexTemplateString,
|
||||
int ipConnectionLimit);
|
||||
virtual ~InternalServer();
|
||||
@@ -118,9 +116,8 @@ class InternalServer {
|
||||
void** cont_cls);
|
||||
bool start();
|
||||
void stop();
|
||||
IpAddress getAddress() const { return m_addr; }
|
||||
int getPort() const { return m_port; }
|
||||
IpMode getIpMode() const { return m_ipMode; }
|
||||
std::string getAddress() { return m_addr; }
|
||||
int getPort() { return m_port; }
|
||||
|
||||
private: // functions
|
||||
std::unique_ptr<Response> handle_request(const RequestContext& request);
|
||||
@@ -167,7 +164,7 @@ class InternalServer {
|
||||
typedef ConcurrentCache<std::string, std::shared_ptr<LockableSuggestionSearcher>> SuggestionSearcherCache;
|
||||
|
||||
private: // data
|
||||
IpAddress m_addr;
|
||||
std::string m_addr;
|
||||
int m_port;
|
||||
std::string m_root; // URI-encoded
|
||||
std::string m_rootPrefixOfDecodedURL; // URI-decoded
|
||||
@@ -177,7 +174,6 @@ class InternalServer {
|
||||
bool m_withTaskbar;
|
||||
bool m_withLibraryButton;
|
||||
bool m_blockExternalLinks;
|
||||
IpMode m_ipMode;
|
||||
std::string m_indexTemplateString;
|
||||
int m_ipConnectionLimit;
|
||||
struct MHD_Daemon* mp_daemon;
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
#include <cctype>
|
||||
|
||||
#include "tools/stringTools.h"
|
||||
#include "i18n_utils.h"
|
||||
#include "i18n.h"
|
||||
|
||||
namespace kiwix {
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
#include <mustache.hpp>
|
||||
#include "byte_range.h"
|
||||
#include "etag.h"
|
||||
#include "i18n_utils.h"
|
||||
#include "i18n.h"
|
||||
|
||||
#include <zim/item.h>
|
||||
|
||||
|
||||
@@ -10,12 +10,10 @@ namespace
|
||||
|
||||
// These mappings are not provided by the ICU library, any such mappings can be manually added here
|
||||
std::map<std::string, std::string> iso639_3 = {
|
||||
{"ami", "Amis"},
|
||||
{"atj", "atikamekw"},
|
||||
{"azb", "آذربایجان دیلی"},
|
||||
{"bcl", "central bikol"},
|
||||
{"bgs", "tagabawa"},
|
||||
{"blk", "ပအိုဝ်ႏ"},
|
||||
{"bxr", "буряад хэлэн"},
|
||||
{"cbk", "chavacano"},
|
||||
{"cdo", "閩東語"},
|
||||
@@ -25,16 +23,13 @@ std::map<std::string, std::string> iso639_3 = {
|
||||
{"eml", "emiliân-rumagnōl"},
|
||||
{"fbs", "српскохрватски"},
|
||||
{"fon", "fɔ̀ngbè"},
|
||||
{"gcr", "Kriyòl gwiyannen"},
|
||||
{"guw", "Gungbe"},
|
||||
{"hbs", "srpskohrvatski"},
|
||||
{"hyw", "հայերէն/հայերեն"},
|
||||
{"ido", "ido"},
|
||||
{"kbp", "kabɩyɛ"},
|
||||
{"kld", "Gamilaraay"},
|
||||
{"lbe", "лакку маз"},
|
||||
{"lbj", "ལ་དྭགས་སྐད་"},
|
||||
{"lld", "ladin"},
|
||||
{"map", "Austronesian"},
|
||||
{"mhr", "марий йылме"},
|
||||
{"mnw", "ဘာသာမန်"},
|
||||
@@ -46,15 +41,10 @@ std::map<std::string, std::string> iso639_3 = {
|
||||
{"olo", "livvi"},
|
||||
{"pih", "Pitcairn-Norfolk"},
|
||||
{"pnb", "Western Panjabi"},
|
||||
{"pwn", "Pinayuanan"},
|
||||
{"rmr", "Caló"},
|
||||
{"rmy", "romani shib"},
|
||||
{"roa", "romance languages"},
|
||||
{"skr", "سرائیکی"},
|
||||
{"szy", "Sakizaya"},
|
||||
{"tay", "Tayal"},
|
||||
{"tgl", "Wikang Tagalog"},
|
||||
{"twi", "Akwapem Twi"},
|
||||
{"twi", "twi"},
|
||||
// ICU for Ubuntu versions <= focal (20.04) returns "" for the language code ""
|
||||
// unlike the later versions - which returns "und". We map this value to "Undetermined" for a common ground.
|
||||
{"", "Undetermined"},
|
||||
@@ -68,7 +58,6 @@ void fillLanguagesMap()
|
||||
const kiwix::ICULanguageInfo lang(*icuLangPtr);
|
||||
iso639_3.insert({lang.iso3Code(), lang.selfName()});
|
||||
}
|
||||
iso639_3.erase("mul");
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
@@ -32,14 +32,12 @@
|
||||
#include <stdexcept>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <iphlpapi.h>
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <iostream>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <net/if.h>
|
||||
#include <netdb.h>
|
||||
@@ -49,12 +47,6 @@
|
||||
#include <sys/sockio.h>
|
||||
#endif
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
size_t write_callback_to_iss(char* ptr, size_t size, size_t nmemb, void* userdata)
|
||||
{
|
||||
auto str = static_cast<std::stringstream*>(userdata);
|
||||
@@ -62,9 +54,7 @@ size_t write_callback_to_iss(char* ptr, size_t size, size_t nmemb, void* userdat
|
||||
return nmemb;
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
std::string download(const std::string& url) {
|
||||
std::string kiwix::download(const std::string& url) {
|
||||
auto curl = curl_easy_init();
|
||||
std::stringstream ss;
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||
@@ -85,174 +75,103 @@ std::string download(const std::string& url) {
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
std::map<std::string, std::string> kiwix::getNetworkInterfaces() {
|
||||
std::map<std::string, std::string> interfaces;
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
std::map<std::string, IpAddress> getNetworkInterfacesWin() {
|
||||
std::map<std::string, IpAddress> interfaces;
|
||||
|
||||
const int working_buffer_size = 15000;
|
||||
const int max_tries = 3;
|
||||
|
||||
ULONG flags = GAA_FLAG_INCLUDE_PREFIX;
|
||||
|
||||
// default to unspecified address family (both)
|
||||
ULONG family = AF_UNSPEC;
|
||||
|
||||
ULONG outBufLen = working_buffer_size;
|
||||
ULONG Iterations = 0;
|
||||
DWORD dwRetVal = 0;
|
||||
PIP_ADAPTER_ADDRESSES interfacesHead = NULL;
|
||||
|
||||
// Successively allocate the required memory until GetAdaptersAddresses does not
|
||||
// results in ERROR_BUFFER_OVERFLOW for a maximum of max_tries
|
||||
do{
|
||||
interfacesHead = (IP_ADAPTER_ADDRESSES *) malloc(outBufLen);
|
||||
if (interfacesHead == NULL) {
|
||||
std::cerr << "Memory allocation failed for IP_ADAPTER_ADDRESSES struct" << std::endl;
|
||||
return interfaces;
|
||||
}
|
||||
|
||||
dwRetVal = GetAdaptersAddresses(family, flags, NULL, interfacesHead, &outBufLen);
|
||||
} while ((dwRetVal == ERROR_BUFFER_OVERFLOW) && (Iterations < max_tries));
|
||||
|
||||
if (dwRetVal == NO_ERROR) {
|
||||
PIP_ADAPTER_UNICAST_ADDRESS pUnicast = NULL;
|
||||
unsigned int i = 0;
|
||||
for (PIP_ADAPTER_ADDRESSES temp = interfacesHead; temp != NULL;
|
||||
temp = temp->Next) {
|
||||
pUnicast = temp->FirstUnicastAddress;
|
||||
if (pUnicast != NULL) {
|
||||
for (i = 0; pUnicast != NULL; i++){
|
||||
if (pUnicast->Address.lpSockaddr->sa_family == AF_INET)
|
||||
{
|
||||
sockaddr_in *si = (sockaddr_in *)(pUnicast->Address.lpSockaddr);
|
||||
char host[INET_ADDRSTRLEN]={0};
|
||||
inet_ntop(AF_INET, &(si->sin_addr), host, sizeof(host));
|
||||
interfaces[temp->AdapterName].addr=host;
|
||||
}
|
||||
else if (pUnicast->Address.lpSockaddr->sa_family == AF_INET6)
|
||||
{
|
||||
sockaddr_in6 *si = (sockaddr_in6 *)(pUnicast->Address.lpSockaddr);
|
||||
char host[INET6_ADDRSTRLEN]={0};
|
||||
inet_ntop(AF_INET6, &(si->sin6_addr), host, sizeof(host));
|
||||
if (!IN6_IS_ADDR_LINKLOCAL(&(si->sin6_addr)))
|
||||
interfaces[temp->AdapterName].addr6=host;
|
||||
}
|
||||
pUnicast = pUnicast->Next;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::cerr << "Call to GetAdaptersAddresses failed with error: "<< dwRetVal << std::endl;
|
||||
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;
|
||||
}
|
||||
|
||||
if (interfacesHead) free(interfacesHead);
|
||||
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;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
std::map<std::string, IpAddress> getNetworkInterfacesPosix() {
|
||||
std::map<std::string, IpAddress> interfaces;
|
||||
|
||||
struct ifaddrs *interfacesHead;
|
||||
if (getifaddrs(&interfacesHead) == -1) {
|
||||
perror("getifaddrs");
|
||||
}
|
||||
|
||||
for (ifaddrs *temp = interfacesHead; temp != NULL; temp = temp->ifa_next) {
|
||||
if (temp->ifa_addr == NULL) continue;
|
||||
|
||||
if (temp->ifa_addr->sa_family == AF_INET) {
|
||||
sockaddr_in *si = (sockaddr_in *)(temp->ifa_addr);
|
||||
char host[INET_ADDRSTRLEN] = {0};
|
||||
inet_ntop(AF_INET, &(si->sin_addr), host, sizeof(host));
|
||||
interfaces[temp->ifa_name].addr=host;
|
||||
} else if (temp->ifa_addr->sa_family == AF_INET6) {
|
||||
sockaddr_in6 *si = (sockaddr_in6 *)(temp->ifa_addr);
|
||||
char host[INET6_ADDRSTRLEN] = {0};
|
||||
inet_ntop(AF_INET6, &(si->sin6_addr), host, sizeof(host));
|
||||
if (!IN6_IS_ADDR_LINKLOCAL(&(si->sin6_addr)))
|
||||
interfaces[temp->ifa_name].addr6=host;
|
||||
}
|
||||
}
|
||||
|
||||
freeifaddrs(interfacesHead);
|
||||
|
||||
return interfaces;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
std::map<std::string, IpAddress> getNetworkInterfacesIPv4Or6() {
|
||||
#ifdef _WIN32
|
||||
return getNetworkInterfacesWin();
|
||||
#else
|
||||
return getNetworkInterfacesPosix();
|
||||
#endif
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> getNetworkInterfaces() {
|
||||
std::map<std::string, std::string> result;
|
||||
for ( const auto& kv : getNetworkInterfacesIPv4Or6() ) {
|
||||
const std::string& interfaceName = kv.first;
|
||||
const auto& ipAddresses = kv.second;
|
||||
if ( !ipAddresses.addr.empty() ) {
|
||||
result[interfaceName] = ipAddresses.addr;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
IpAddress getBestPublicIps(IpMode mode) {
|
||||
IpAddress bestPublicIps = IpAddress{"127.0.0.1","::1"};
|
||||
std::map<std::string, IpAddress> interfaces = getNetworkInterfacesIPv4Or6();
|
||||
std::string kiwix::getBestPublicIp() {
|
||||
auto interfaces = getNetworkInterfaces();
|
||||
|
||||
#ifndef _WIN32
|
||||
const char* const prioritizedNames[] = { "eth0", "eth1", "wlan0", "wlan1", "en0", "en1" };
|
||||
const char* const prioritizedNames[] =
|
||||
{ "eth0", "eth1", "wlan0", "wlan1", "en0", "en1" };
|
||||
for(auto name: prioritizedNames) {
|
||||
const auto it = interfaces.find(name);
|
||||
if(it == interfaces.end()) continue;
|
||||
const IpAddress& interfaceIps = (*it).second;
|
||||
if(!bestPublicIps.empty4() && interfaceIps.valid4(mode)) bestPublicIps.addr = interfaceIps.addr;
|
||||
if(!bestPublicIps.empty6() && interfaceIps.valid6(mode)) bestPublicIps.addr6 = interfaceIps.addr6;
|
||||
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) {
|
||||
const IpAddress& interfaceIps = itr.second;
|
||||
if(!bestPublicIps.empty4() && interfaceIps.valid4(mode) && interfaceIps.addr.find(prefix) == 0) bestPublicIps.addr = interfaceIps.addr;
|
||||
if(!bestPublicIps.empty6() && interfaceIps.valid6(mode)) bestPublicIps.addr6 = interfaceIps.addr6;
|
||||
auto interfaceIp = itr.second;
|
||||
if (interfaceIp.find(prefix) == 0) {
|
||||
return interfaceIp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bestPublicIps;
|
||||
return "127.0.0.1";
|
||||
}
|
||||
|
||||
std::string getBestPublicIp()
|
||||
{
|
||||
return getBestPublicIps(IpMode::ipv4).addr;
|
||||
}
|
||||
|
||||
bool ipAvailable(const IpAddress& addr)
|
||||
{
|
||||
auto interfaces = kiwix::getNetworkInterfacesIPv4Or6();
|
||||
for (const auto& interface : interfaces) {
|
||||
IpAddress interfaceIps = interface.second;
|
||||
if (!interfaceIps.empty4() && interfaceIps.addr == addr.addr) return true;
|
||||
if (!interfaceIps.empty6() && interfaceIps.addr6 == addr.addr6) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace kiwix
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
#endif
|
||||
|
||||
#include "tools/stringTools.h"
|
||||
#include "server/i18n_utils.h"
|
||||
#include "server/i18n.h"
|
||||
#include "libkiwix-resources.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
@@ -320,6 +320,16 @@ bool kiwix::fileReadable(const std::string& path)
|
||||
#endif
|
||||
}
|
||||
|
||||
bool makeDirectory(const std::string& path)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
int status = _wmkdir(Utf8ToWide(path).c_str());
|
||||
#else
|
||||
int status = mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
|
||||
#endif
|
||||
return status == 0;
|
||||
}
|
||||
|
||||
std::string makeTmpDirectory()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
@@ -428,6 +438,52 @@ std::string kiwix::getCurrentDirectory()
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string kiwix::getDataDirectory()
|
||||
{
|
||||
// Try to get the dataDir from the `KIWIX_DATA_DIR` env var
|
||||
#ifdef _WIN32
|
||||
wchar_t* cDataDir = ::_wgetenv(L"KIWIX_DATA_DIR");
|
||||
if (cDataDir != nullptr) {
|
||||
return WideToUtf8(cDataDir);
|
||||
}
|
||||
#else
|
||||
char* cDataDir = ::getenv("KIWIX_DATA_DIR");
|
||||
if (cDataDir != nullptr) {
|
||||
return cDataDir;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Compute the dataDir from the user directory.
|
||||
std::string dataDir;
|
||||
#ifdef _WIN32
|
||||
cDataDir = ::_wgetenv(L"APPDATA");
|
||||
if (cDataDir == nullptr)
|
||||
cDataDir = ::_wgetenv(L"USERPROFILE");
|
||||
if (cDataDir != nullptr)
|
||||
dataDir = WideToUtf8(cDataDir);
|
||||
#else
|
||||
cDataDir = ::getenv("XDG_DATA_HOME");
|
||||
if (cDataDir != nullptr) {
|
||||
dataDir = cDataDir;
|
||||
} else {
|
||||
cDataDir = ::getenv("HOME");
|
||||
if (cDataDir != nullptr) {
|
||||
dataDir = cDataDir;
|
||||
dataDir = appendToDirectory(dataDir, ".local");
|
||||
dataDir = appendToDirectory(dataDir, "share");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (!dataDir.empty()) {
|
||||
dataDir = appendToDirectory(dataDir, "kiwix");
|
||||
makeDirectory(dataDir);
|
||||
return dataDir;
|
||||
}
|
||||
|
||||
// Let's use the currentDirectory
|
||||
return getCurrentDirectory();
|
||||
}
|
||||
|
||||
static std::map<std::string, std::string> extMimeTypes = {
|
||||
{ "html", "text/html"},
|
||||
{ "htm", "text/html"},
|
||||
|
||||
@@ -29,6 +29,7 @@ std::wstring Utf8ToWide(const std::string& str);
|
||||
|
||||
unsigned int getFileSize(const std::string& path);
|
||||
std::string getFileSizeAsString(const std::string& path);
|
||||
bool makeDirectory(const std::string& path);
|
||||
std::string makeTmpDirectory();
|
||||
bool copyFile(const std::string& sourcePath, const std::string& destPath);
|
||||
bool writeTextFile(const std::string& path, const std::string& content);
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <regex>
|
||||
|
||||
/* tell ICU where to find its dat file (tables) */
|
||||
void kiwix::loadICUExternalTables()
|
||||
@@ -257,7 +256,7 @@ std::string kiwix::urlDecode(const std::string& value, bool component)
|
||||
|
||||
// If there aren't enough characters left for this to be a
|
||||
// valid escape code, just use the character and move on
|
||||
if (value.end() - it < 3) {
|
||||
if (it > value.end() - 3) {
|
||||
os << *it;
|
||||
continue;
|
||||
}
|
||||
@@ -440,13 +439,3 @@ template<>
|
||||
std::string kiwix::extractFromString(const std::string& str) {
|
||||
return str;
|
||||
}
|
||||
|
||||
std::string kiwix::getSlugifiedFileName(const std::string& filename)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
const std::regex reservedCharsReg(R"([<>:"/\\|?*])");
|
||||
#else
|
||||
const std::regex reservedCharsReg("/");
|
||||
#endif
|
||||
return std::regex_replace(filename, reservedCharsReg, "_");
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
skin/i18n/ar.json
|
||||
skin/i18n/bn.json
|
||||
skin/i18n/br.json
|
||||
skin/i18n/cs.json
|
||||
skin/i18n/dag.json
|
||||
skin/i18n/de.json
|
||||
skin/i18n/dga.json
|
||||
skin/i18n/el.json
|
||||
@@ -10,12 +8,10 @@ skin/i18n/en.json
|
||||
skin/i18n/es.json
|
||||
skin/i18n/fi.json
|
||||
skin/i18n/fr.json
|
||||
skin/i18n/ha.json
|
||||
skin/i18n/he.json
|
||||
skin/i18n/hi.json
|
||||
skin/i18n/hy.json
|
||||
skin/i18n/ia.json
|
||||
skin/i18n/ig.json
|
||||
skin/i18n/it.json
|
||||
skin/i18n/ja.json
|
||||
skin/i18n/ko.json
|
||||
@@ -27,7 +23,6 @@ skin/i18n/nl.json
|
||||
skin/i18n/nqo.json
|
||||
skin/i18n/or.json
|
||||
skin/i18n/pl.json
|
||||
skin/i18n/pt-br.json
|
||||
skin/i18n/ru.json
|
||||
skin/i18n/sc.json
|
||||
skin/i18n/sk.json
|
||||
@@ -35,7 +30,6 @@ skin/i18n/skr-arab.json
|
||||
skin/i18n/sl.json
|
||||
skin/i18n/sq.json
|
||||
skin/i18n/sv.json
|
||||
skin/i18n/sw.json
|
||||
skin/i18n/te.json
|
||||
skin/i18n/test.json
|
||||
skin/i18n/tr.json
|
||||
|
||||
@@ -4,7 +4,6 @@ skin/magnet.png
|
||||
skin/feed.svg
|
||||
skin/langSelector.svg
|
||||
skin/download.png
|
||||
skin/download-white.svg
|
||||
skin/hash.png
|
||||
skin/search-icon.svg
|
||||
skin/iso6391To3.js
|
||||
@@ -18,7 +17,6 @@ skin/fonts/Poppins.ttf
|
||||
skin/fonts/Roboto.ttf
|
||||
skin/search_results.css
|
||||
skin/blank.html
|
||||
skin/polyfills.js
|
||||
skin/viewer.js
|
||||
skin/i18n.js
|
||||
skin/languages.js
|
||||
@@ -38,7 +36,7 @@ templates/catalog_v2_entry.xml
|
||||
templates/catalog_v2_partial_entry.xml
|
||||
templates/catalog_v2_categories.xml
|
||||
templates/catalog_v2_languages.xml
|
||||
templates/url_of_search_results_css.tmpl
|
||||
templates/url_of_search_results_css
|
||||
templates/viewer_settings.js
|
||||
templates/no_js_library_page.html
|
||||
templates/no_js_download.html
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
|
||||
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<g id="SVGRepo_iconCarrier"> <g id="Interface / Download"> <path id="Vector" d="M6 21H18M12 3V17M12 17L17 12M12 17L7 12" stroke="#f6f5f4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </g> </g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 546 B |
@@ -8,23 +8,11 @@
|
||||
"404-page-heading": "পাওয়া যায়নি",
|
||||
"500-page-title": "অভ্যন্তরীণ সার্ভার ত্রুটি",
|
||||
"500-page-heading": "অভ্যন্তরীণ সার্ভার ত্রুটি",
|
||||
"search-result-book-info": "{{BOOK_TITLE}} থেকে",
|
||||
"word-count": "{{COUNT}}টি শব্দ",
|
||||
"library-button-text": "স্বাগত পাতায় চলুন",
|
||||
"home-button-text": "'{{BOOK_TITLE}}'-এর প্রধান পাতায় চলুন",
|
||||
"searchbox-tooltip": "'{{BOOK_TITLE}}' অনুসন্ধান করুন",
|
||||
"search": "অনুসন্ধান",
|
||||
"welcome-to-kiwix-server": "কিউইক্স সার্ভারে স্বাগতম",
|
||||
"download-links-title": "বই ডাউনলোড করুন",
|
||||
"preview-book": "প্রাকদর্শন",
|
||||
"book-category.wikibooks": "উইকিবই",
|
||||
"book-category.wikinews": "উইকিসংবাদ",
|
||||
"book-category.wikipedia": "উইকিপিডিয়া",
|
||||
"book-category.wikiquote": "উইকিউক্তি",
|
||||
"book-category.wikisource": "উইকিসংকলন",
|
||||
"book-category.wikispecies": "উইকিপ্রজাতি",
|
||||
"book-category.wikiversity": "উইকিবিশ্ববিদ্যালয়",
|
||||
"book-category.wikivoyage": "উইকিভ্রমণ",
|
||||
"book-category.wiktionary": "উইকিঅভিধান",
|
||||
"book-category.other": "অন্যান্য"
|
||||
"preview-book": "প্রাকদর্শন"
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Adriendelucca",
|
||||
"Y-M D"
|
||||
]
|
||||
},
|
||||
"name": "brezhoneg",
|
||||
"suggest-full-text-search": "E lec'h emañ \"{{{SEARCH_TERMS}}}\"...",
|
||||
"no-such-book": "N’eus ket eus al levr-mañ: {{BOOK_NAME}}",
|
||||
"no-book-found": "N’eus levr ebet a glot gant an dezverkoù-se",
|
||||
"url-not-found": "N’eo ket bet kavet an URL \"{{url}}\" goulennet war ar servijer-mañ.",
|
||||
"random-article-failure": "Chaous! N’hon eus ket gellet dibab ur pennad dre ziouer evidoc’h :(",
|
||||
"400-page-title": "Reked amwiriek",
|
||||
"400-page-heading": "Reked amwiriek",
|
||||
"404-page-heading": "N'eo ket bet kavet",
|
||||
"500-page-title": "Fazi diabarzh ar servijer",
|
||||
"500-page-heading": "Fazi diabarzh ar servijer",
|
||||
"search-results-page-title": "Klask: {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "Disoc’hoù <b>{{START}}-{{END}}</b> diwar <b>{{COUNT}}</b> evit <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"empty-search-results-page-header": "Disoc’h ebet kavet evit <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"search-result-book-info": "diouzh {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} a c’herioù",
|
||||
"library-button-text": "Mont d’ar bajenn degemer",
|
||||
"home-button-text": "Mont da bajenn degemer \"{{BOOK_TITLE}}\"",
|
||||
"random-page-button-text": "Mont d’ur bajenn dre zegouezh",
|
||||
"searchbox-tooltip": "Klask '{{BOOK_TITLE}}'",
|
||||
"powered-by-kiwix-html": "Lusket gant <a href=\"https://kiwix.org\">Kiwix</a>",
|
||||
"search": "Klask",
|
||||
"book-filtering-all-categories": "An holl rummadoù",
|
||||
"book-filtering-all-languages": "An holl yezhoù",
|
||||
"count-of-matching-books": "{{COUNT}} levr",
|
||||
"download": "Pellgargañ",
|
||||
"direct-download-link-text": "Eeun",
|
||||
"filter-by-tag": "Silañ gant an dikedenn \"{{TAG}}\"",
|
||||
"stop-filtering-by-tag": "Paouez da silañ gant an dikedenn \"{{TAG}}\"",
|
||||
"welcome-to-kiwix-server": "Degemer mat er servijer Kiwix",
|
||||
"download-links-heading": "Liammoù pellgargañ evit <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
"download-links-title": "Pellgargañ al levr",
|
||||
"preview-book": "Rakwelet",
|
||||
"unknown-error": "Fazi dianav"
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Kalakpagh",
|
||||
"Ruky Wunpini"
|
||||
]
|
||||
},
|
||||
"name": "Silimiinsili",
|
||||
"suggest-full-text-search": "Gbubi la '{{{SEARCH_TERMS}}}'...",
|
||||
"no-such-book": "Lala buku kani:{{BOOK_NAME}}",
|
||||
"too-many-books": "Buku nima pam ka bɛ daa suhi ({{NB_BOOKS}}) din ni ka tariga nyɛ {{LIMIT}}",
|
||||
"no-book-found": "Buku kani lu zahim a ni piigi yaɣa shɛli",
|
||||
"url-not-found": "URL \"{{url}}\" shɛli bɛ ni daa suhi daa kani n-ti tum tumda ŋɔ.",
|
||||
"suggest-search": "Niŋmi lahabali pali vihigu zaŋ n-ti <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
||||
"random-article-failure": "Oops! Zaɣisiya ni di gahim piigi lahabali :(",
|
||||
"invalid-raw-data-type": "{{DATATYPE}} nyɛla din suhibu bi niŋ viɛnyɛla zaŋ n-ti lahabali kahili.",
|
||||
"invalid-request": "URL \"{{{url}}}\" shɛli bɛ ni daa suhi ŋɔ nyɛla din bi suhi viɛnyɛla.",
|
||||
"no-value-for-arg": "Dariza shɛli bi ti zaŋ n-ti nangban'kpeeni {{ARGUMENT}}",
|
||||
"no-query": "Yɛlshɛli bi yiina",
|
||||
"raw-entry-not-found": "Ku tooi nya {{DATATYPE}} kpɛbu {{ENTRY}}",
|
||||
"400-page-title": "Suhigu din bi niŋ viɛnyɛla",
|
||||
"400-page-heading": "Suhigu din bi niŋ viɛnyɛla",
|
||||
"404-page-title": "Lahabali kani",
|
||||
"404-page-heading": "Kani",
|
||||
"500-page-title": "Puuni tum tumda chiriŋ",
|
||||
"500-page-heading": "Puuni tum tumda chiriŋ",
|
||||
"500-page-text": "Puuni tum tumda chiriŋ niŋya. Ti niŋ yolitem zaŋ jɛndi li :/",
|
||||
"fulltext-search-unavailable": "Lahabali pali vihigu kani",
|
||||
"search-results-page-title": "Vihima:{{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "Chaɣili nima <b>{{START}}-{{END}}</b> of <b>{{COUNT}}</b> for <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"empty-search-results-page-header": "Chaɣili daa kani zaŋ n-ti\n <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"search-result-book-info": "yina {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} bachi nima",
|
||||
"library-button-text": "Cham solɔɣu",
|
||||
"home-button-text": "Cham yaɣili maŋmaŋ zaŋ n-ti\n'{{BOOK_TITLE}}'",
|
||||
"random-page-button-text": "Cham gahim piigi yaɣili",
|
||||
"searchbox-tooltip": "Vihima '{{BOOK_TITLE}}'",
|
||||
"confusion-of-tongues": "Buku nima ayi bee gari balli koŋkoba nyɛ din yɛn be vihigu ŋɔ ni ka di ni tooi chɛ ka di laasabu wali.",
|
||||
"welcome-page-overzealous-filter": "Labisibu kani. A ni yu ni a\n<a href=\"{{URL}}\">reset filter</a>?",
|
||||
"powered-by-kiwix-html": "Din niŋ li nyɛ <a href=\"https://kiwix.org\">Kiwix</a>",
|
||||
"search": "Vihima",
|
||||
"book-filtering-all-categories": "Pubu zaa",
|
||||
"book-filtering-all-languages": "Bala zaa",
|
||||
"count-of-matching-books": "{{COUNT}} Buku(nima)",
|
||||
"download": "Deebu",
|
||||
"direct-download-link-text": "Tibi",
|
||||
"direct-download-alt-text": "Tibi deebu",
|
||||
"hash-download-link-text": "Sha256 hash",
|
||||
"hash-download-alt-text": "Deebu daliŋ",
|
||||
"welcome-to-kiwix-server": "Maraba Kiwix tum tumda",
|
||||
"download-links-heading": "Deemi soli zaŋ n-ti <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
"download-links-title": "Deemi buku",
|
||||
"preview-book": "Daŋyuli",
|
||||
"unknown-error": "Chiriŋ din bi tooi baŋ"
|
||||
}
|
||||
@@ -2,9 +2,7 @@
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"IMayBeABitShy",
|
||||
"Justman10000",
|
||||
"Lucas Werkmeister",
|
||||
"Rofiatmustapha12",
|
||||
"ThisCarthing"
|
||||
]
|
||||
},
|
||||
@@ -17,7 +15,6 @@
|
||||
"suggest-search": "Führe eine Volltextsuche nach <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a> durch",
|
||||
"random-article-failure": "Hoppla! Konnte keinen zufälligen Artikel auswählen :(",
|
||||
"invalid-raw-data-type": "{{DATATYPE}} ist keine gültige Anfrage für unverarbeiteten Inhalt",
|
||||
"invalid-request": "Die angeforderte URL „{{{url}}}“ ist keine gültige Anfrage.",
|
||||
"no-value-for-arg": "Kein Wert für den Parameter {{ARGUMENT}} gegeben",
|
||||
"no-query": "Keine Suchanfrage gegeben.",
|
||||
"raw-entry-not-found": "Eintrag {{ENTRY}} des Typs {{DATATYPE}} konnte nicht gefunden werden.",
|
||||
@@ -27,14 +24,8 @@
|
||||
"404-page-heading": "Nicht gefunden",
|
||||
"500-page-title": "Interner Server-Fehler",
|
||||
"500-page-heading": "Interner Server-Fehler",
|
||||
"500-page-text": "Es ist ein interner Serverfehler aufgetreten. Das tut uns leid :/",
|
||||
"fulltext-search-unavailable": "Die Volltestsuche steht nicht zur Verfügung.",
|
||||
"no-search-results": "Die Volltextsuche ist für diesen Inhalt nicht verfügbar.",
|
||||
"search-results-page-title": "Suche: {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "Ergebnisse <b>{{START}}-{{END}}</b> von <b>{{COUNT}}</b> für <b>„{{{SEARCH_PATTERN}}}“</b>",
|
||||
"empty-search-results-page-header": "Für <b>„{{{SEARCH_PATTERN}}}“</b> wurden keine Ergebnisse gefunden.",
|
||||
"search-result-book-info": "von {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} Wörter",
|
||||
"library-button-text": "Zur Willkommensseite gehen",
|
||||
"home-button-text": "Zur Hauptseite von '{{BOOK_TITLE}}' gehen",
|
||||
"random-page-button-text": "Zu einer zufällig ausgewählten Seite gehen",
|
||||
@@ -48,13 +39,13 @@
|
||||
"count-of-matching-books": "{{COUNT}} Bücher",
|
||||
"download": "Herunterladen",
|
||||
"direct-download-link-text": "Direkt",
|
||||
"direct-download-alt-text": "Direktes Herunterladen über HTTP(S)",
|
||||
"hash-download-link-text": "SHA-256 Prüfsumme",
|
||||
"hash-download-alt-text": "SHA-256-Dateiprüfsumme anzeigen",
|
||||
"direct-download-alt-text": "direkt herunterladen",
|
||||
"hash-download-link-text": "Sha256 Hash",
|
||||
"hash-download-alt-text": "Hash herunterladen",
|
||||
"magnet-link-text": "Magnet Link",
|
||||
"magnet-alt-text": "Download über Magnet-Link",
|
||||
"torrent-download-link-text": "BitTorrent",
|
||||
"torrent-download-alt-text": "Herunterladen über BitTorrent",
|
||||
"magnet-alt-text": "Magnet Link herunterladen",
|
||||
"torrent-download-link-text": "Torrent-Datei",
|
||||
"torrent-download-alt-text": "Torrent herunterladen",
|
||||
"library-opds-feed-all-entries": "ODPS Feed der Bibliothek - Alle Einträge",
|
||||
"filter-by-tag": "Nach Tag \"{{TAG}}\" filtern",
|
||||
"stop-filtering-by-tag": "Filterung nach Tag \"{{TAG}}\" aufheben",
|
||||
@@ -62,6 +53,5 @@
|
||||
"welcome-to-kiwix-server": "Wilkommen beim Kiwix Server",
|
||||
"download-links-heading": "Download Links für <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
"download-links-title": "Buch herunterladen",
|
||||
"preview-book": "Vorschau",
|
||||
"unknown-error": "Unbekannter Fehler"
|
||||
"preview-book": "Vorschau"
|
||||
}
|
||||
|
||||
@@ -2,12 +2,9 @@
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Kelson",
|
||||
"Norhorn",
|
||||
"Ανώνυμος Βικιπαιδιστής"
|
||||
"Norhorn"
|
||||
]
|
||||
},
|
||||
"name": "Αγγλικά",
|
||||
"no-such-book": "Δεν υπάρχει τέτοιο βιβλίο: {{BOOK_NAME}}",
|
||||
"welcome-page-overzealous-filter": "Κανένα αποτέλεσμα. Θέλετε να <a href=\"{{URL}}\">επαναφέρετε το φίλτρο</a>;",
|
||||
"powered-by-kiwix-html": "Με την υποστήριξη by <a href=\"https://kiwix.org\">Kiwix</a>",
|
||||
"search": "Αναζήτηση",
|
||||
@@ -18,14 +15,8 @@
|
||||
"direct-download-link-text": "Απευθείας",
|
||||
"direct-download-alt-text": "άμεση λήψη",
|
||||
"hash-download-alt-text": "λήψη αναγνωριστικού",
|
||||
"magnet-alt-text": "λήψη μαγνήτη",
|
||||
"torrent-download-link-text": "Αρχείο torrent",
|
||||
"torrent-download-alt-text": "λήψη torrent",
|
||||
"filter-by-tag": "Φίλτρο ανά ετικέτα \"{{TAG}}\"",
|
||||
"stop-filtering-by-tag": "Διακοπή φίλτρου ανά ετικέτα \"{{TAG}}\"",
|
||||
"welcome-to-kiwix-server": "Καλώς ορίσατε στον διακομιστή Kiwix",
|
||||
"download-links-heading": "Λήψη συνδέσμων για <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
"download-links-title": "Κατεβάστε το βιβλίο",
|
||||
"preview-book": "Προεπισκόπηση",
|
||||
"unknown-error": "Άγνωστο σφάλμα"
|
||||
"stop-filtering-by-tag": "Διακοπή φίλτρου ανά ετικέτα \"{{TAG}}\""
|
||||
}
|
||||
|
||||
@@ -43,16 +43,16 @@
|
||||
, "count-of-matching-books": "{{COUNT}} book(s)"
|
||||
, "download": "Download"
|
||||
, "direct-download-link-text": "Direct"
|
||||
, "direct-download-alt-text": "Download directly via HTTP(S)"
|
||||
, "hash-download-link-text": "SHA-256 checksum"
|
||||
, "hash-download-alt-text": "Display SHA-256 file checksum"
|
||||
, "direct-download-alt-text": "direct download"
|
||||
, "hash-download-link-text": "Sha256 hash"
|
||||
, "hash-download-alt-text": "download hash"
|
||||
, "magnet-link-text": "Magnet link"
|
||||
, "magnet-alt-text": "Download via Magnet link"
|
||||
, "torrent-download-link-text": "BitTorrent"
|
||||
, "torrent-download-alt-text": "Download via BitTorrent"
|
||||
, "magnet-alt-text": "download magnet"
|
||||
, "torrent-download-link-text": "Torrent file"
|
||||
, "torrent-download-alt-text": "download torrent"
|
||||
, "library-opds-feed-all-entries": "Library OPDS Feed - All entries"
|
||||
, "filter-by-tag": "Filter by tag \"{{{TAG}}}\""
|
||||
, "stop-filtering-by-tag": "Stop filtering by tag \"{{{TAG}}}\""
|
||||
, "filter-by-tag": "Filter by tag \"{{TAG}}\""
|
||||
, "stop-filtering-by-tag": "Stop filtering by tag \"{{TAG}}\""
|
||||
, "library-opds-feed-parameterised": "Library OPDS Feed - entries matching {{#LANG}}\nLanguage: {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategory: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nTag: {{TAG}} {{/TAG}}{{#Q}}\nQuery: {{Q}} {{/Q}}"
|
||||
, "welcome-to-kiwix-server": "Welcome to Kiwix Server"
|
||||
, "download-links-heading": "Download links for <b><i>{{BOOK_TITLE}}</i></b>"
|
||||
@@ -60,22 +60,4 @@
|
||||
, "preview-book": "Preview"
|
||||
, "non-translated-text": "{{MSG}}"
|
||||
, "unknown-error": "Unknown error"
|
||||
, "book-category.gutenberg": "Gutenberg"
|
||||
, "book-category.iFixit": "iFixit"
|
||||
, "book-category.mooc": "MOOC"
|
||||
, "book-category.phet": "Phet"
|
||||
, "book-category.stack_exchange": "Stack Exchange"
|
||||
, "book-category.ted": "Ted"
|
||||
, "book-category.vikidia": "Vikidia"
|
||||
, "book-category.wikibooks": "Wikibooks"
|
||||
, "book-category.wikihow": "wikiHow"
|
||||
, "book-category.wikinews": "Wikinews"
|
||||
, "book-category.wikipedia": "Wikipedia"
|
||||
, "book-category.wikiquote": "Wikiquote"
|
||||
, "book-category.wikisource": "Wikisource"
|
||||
, "book-category.wikispecies": "Wikispecies"
|
||||
, "book-category.wikiversity": "Wikiversity"
|
||||
, "book-category.wikivoyage": "Wikivoyage"
|
||||
, "book-category.wiktionary": "Wiktionary"
|
||||
, "book-category.other": "Other"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"AlexanderFF",
|
||||
"Fitoschido",
|
||||
"Ovruni",
|
||||
"SpikeShroom",
|
||||
@@ -46,14 +45,13 @@
|
||||
"hash-download-alt-text": "descargar hash",
|
||||
"magnet-link-text": "Enlace magnético",
|
||||
"magnet-alt-text": "Descargar link magnético",
|
||||
"torrent-download-link-text": "BitTorrent",
|
||||
"torrent-download-alt-text": "Descargar a través de BitTorrent",
|
||||
"torrent-download-link-text": "Archivo de torrent",
|
||||
"torrent-download-alt-text": "descargar torrent",
|
||||
"filter-by-tag": "Filtrar por etiqueta \"{{TAG}}\"",
|
||||
"stop-filtering-by-tag": "Dejar de filtrar por etiqueta \"{{TAG}}\"",
|
||||
"library-opds-feed-parameterised": "Feed OPDS de la biblioteca: entradas que coinciden con {{#LANG}}\nLanguage: {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategory: {{CATEGORY}} {{/CATEGORY}} {{#TAG}}\nEtiqueta: {{TAG}} {{/TAG}}{{#Q}}\nConsulta: {{Q}} {{/Q}}",
|
||||
"welcome-to-kiwix-server": "Bienvenido al servidor Kiwix",
|
||||
"download-links-heading": "Enlaces de descarga para <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
"download-links-title": "Descargar libro",
|
||||
"preview-book": "Previsualizar",
|
||||
"unknown-error": "Error desconocido"
|
||||
"preview-book": "Previsualizar"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
},
|
||||
"name": "suomi",
|
||||
"suggest-full-text-search": "sisältää '{{{SEARCH_TERMS}}}'...",
|
||||
"no-such-book": "Kirjaa {{BOOK_NAME}} ei ole olemassa",
|
||||
"url-not-found": "Pyydettyä URL-osoitetta \"{{url}}\" ei löytynyt tältä palvelimelta.",
|
||||
"400-page-title": "Virheellinen pyyntö",
|
||||
"400-page-heading": "Virheellinen pyyntö",
|
||||
@@ -16,7 +15,6 @@
|
||||
"404-page-heading": "Ei löytynyt",
|
||||
"500-page-title": "Sisäinen palvelinvirhe",
|
||||
"500-page-heading": "Sisäinen palvelinvirhe",
|
||||
"word-count": "{{COUNT}} sanaa",
|
||||
"library-button-text": "Siirry tervetulosivulle",
|
||||
"home-button-text": "Siirry kirjan '{{BOOK_TITLE}}' etusivulle",
|
||||
"random-page-button-text": "Siirry satunnaiselle sivulle",
|
||||
@@ -24,14 +22,9 @@
|
||||
"search": "Hae",
|
||||
"book-filtering-all-categories": "Kaikki luokat",
|
||||
"book-filtering-all-languages": "Kaikki kielet",
|
||||
"count-of-matching-books": "{{COUNT}} kirja(a)",
|
||||
"download": "Lataa",
|
||||
"magnet-link-text": "Magnet-linkki",
|
||||
"magnet-alt-text": "lataa magnet",
|
||||
"torrent-download-link-text": "Torrent-tiedosto",
|
||||
"torrent-download-alt-text": "lataa torrent-tiedosto",
|
||||
"filter-by-tag": "Suodata tunnisteen ”{{TAG}}” mukaan",
|
||||
"download-links-title": "Lataa kirja",
|
||||
"preview-book": "Esikatsele",
|
||||
"unknown-error": "Tuntematon virhe"
|
||||
"preview-book": "Esikatsele"
|
||||
}
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Adriendelucca",
|
||||
"Gomoko",
|
||||
"Goombiis",
|
||||
"Melimeli",
|
||||
"Stephane",
|
||||
"Thibaut120094",
|
||||
"Urhixidur",
|
||||
"Verdy p",
|
||||
"Vikoula5",
|
||||
"Wladek92"
|
||||
@@ -35,11 +31,6 @@
|
||||
"500-page-text": "Une erreur de serveur interne s'est produite. Nous en sommes désolés :/",
|
||||
"fulltext-search-unavailable": "Recherche en texte intégral non disponible",
|
||||
"no-search-results": "Le moteur de recherche en texte intégral n’est pas disponible pour ce contenu.",
|
||||
"search-results-page-title": "Rechercher : {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "Résultats <b>{{START}}-{{END}}</b> sur<b> {{COUNT}}</b> pour <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"empty-search-results-page-header": "Aucun résultat n’a été trouvé pour <b>« {{{SEARCH_PATTERN}}} »</b>",
|
||||
"search-result-book-info": "à partir de {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} mots",
|
||||
"library-button-text": "Aller à la page de bienvenue",
|
||||
"home-button-text": "Aller à la page principale de « {{BOOK_TITLE}} »",
|
||||
"random-page-button-text": "Aller à une page sélectionnée aléatoirement",
|
||||
@@ -53,13 +44,13 @@
|
||||
"count-of-matching-books": "{{COUNT}} livre(s)",
|
||||
"download": "Télécharger",
|
||||
"direct-download-link-text": "Direct",
|
||||
"direct-download-alt-text": "Télécharger directement via HTTP(S)",
|
||||
"hash-download-link-text": "Hachage SHA-256",
|
||||
"hash-download-alt-text": "Affiche le hachage SHA-256 du fichier",
|
||||
"direct-download-alt-text": "téléchargement direct",
|
||||
"hash-download-link-text": "Hachage sha256",
|
||||
"hash-download-alt-text": "télécharger le hachage",
|
||||
"magnet-link-text": "Lien Magnet",
|
||||
"magnet-alt-text": "Télécharger via le lien Magnet",
|
||||
"torrent-download-link-text": "BitTorrent",
|
||||
"torrent-download-alt-text": "Télécharger via BitTorrent",
|
||||
"magnet-alt-text": "télécharger le lien Magnet",
|
||||
"torrent-download-link-text": "Fichier torrent",
|
||||
"torrent-download-alt-text": "télécharger le torrent",
|
||||
"library-opds-feed-all-entries": "Flux OPDS de la bibliothèque – Toutes les entrées",
|
||||
"filter-by-tag": "Filtrer par la balise « {{TAG}} »",
|
||||
"stop-filtering-by-tag": "Arrêter le filtrage par la balise « {{TAG}} »",
|
||||
@@ -68,15 +59,5 @@
|
||||
"download-links-heading": "Liens de téléchargement pour <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
"download-links-title": "Télécharger le livre",
|
||||
"preview-book": "Aperçu",
|
||||
"unknown-error": "Erreur inconnue",
|
||||
"book-category.wikibooks": "Wikilivres",
|
||||
"book-category.wikinews": "Wikinews",
|
||||
"book-category.wikipedia": "Wikipédia",
|
||||
"book-category.wikiquote": "Wikiquote",
|
||||
"book-category.wikisource": "Wikisource",
|
||||
"book-category.wikispecies": "Wikispecies",
|
||||
"book-category.wikiversity": "Wikiversité",
|
||||
"book-category.wikivoyage": "Wikivoyage",
|
||||
"book-category.wiktionary": "Wiktionnaire",
|
||||
"book-category.other": "Autre"
|
||||
"unknown-error": "Erreur inconnue"
|
||||
}
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Abelidokoo",
|
||||
"El-hussain14",
|
||||
"Rofiatmustapha12",
|
||||
"Smshika",
|
||||
"Yusuf Sa'adu"
|
||||
]
|
||||
},
|
||||
"name": "Turanci",
|
||||
"suggest-full-text-search": "dauke da ''{{{SEARCH_TERMS}}}''...",
|
||||
"no-such-book": "Babu irin wannan littafin: {{BOOK_NAME}}",
|
||||
"too-many-books": "An nemi littattafai da yawa ({{NB_BOOKS}}) inda iyaka shine {{LIMIT}}",
|
||||
"no-book-found": "Babu wani littafi da ya dace da ma'aunin zaɓi",
|
||||
"url-not-found": "Ba a sami URL ɗin da ake nema \"{{url}}\" akan wannan sabar ba.",
|
||||
"suggest-search": "Yi cikakken bincike na rubutu don <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
||||
"random-article-failure": "Kash! An kasa ɗaukar labarin bazuwar :(",
|
||||
"invalid-raw-data-type": "{{DATATYPE}} ba ingantaccen buƙatun ɗanyen abun ciki bane.",
|
||||
"invalid-request": "URL ɗin da ake nema \"{{{url}}}\" ba buƙatu mai inganci bane.",
|
||||
"no-value-for-arg": "Babu ƙima da aka bayar don hujja {{ARGUMENT}}",
|
||||
"no-query": "Ba a bayar da tambaya ba.",
|
||||
"raw-entry-not-found": "Ba a iya samun shigarwar {{DATATYPE}} {{ENTRY}}",
|
||||
"400-page-title": "nema mara inganci",
|
||||
"400-page-heading": "nema mara inganci",
|
||||
"404-page-title": "Ba a samo abun ciki ba",
|
||||
"404-page-heading": "Ba a Samu ba",
|
||||
"500-page-title": "Kuskuren na Cikin Saba",
|
||||
"500-page-heading": "Kuskuren na Cikin Saba",
|
||||
"500-page-text": "An sami kuskuren uwar garken ciki. Munyi nadama akan hakan :/",
|
||||
"fulltext-search-unavailable": "Babu binciken cikakken rubutu",
|
||||
"no-search-results": "Babu injin binciken cikakken rubutu don wannan abun ciki.",
|
||||
"search-results-page-title": "Bincika: {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "Sakamako <b>{{START}}-{{END}}</b> na <b>{{COUNT}}</b> na <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"empty-search-results-page-header": "Ba a sami sakamakon <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"search-result-book-info": "daga {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} kalmomi",
|
||||
"library-button-text": "Je zuwa shafin maraba",
|
||||
"home-button-text": "Jeka babban shafin '{{BOOK_TITLE}}'",
|
||||
"random-page-button-text": "Je zuwa shafin da aka zaɓa ba da gangan ba",
|
||||
"searchbox-tooltip": "Bincika '{{BOOK_TITLE}}'",
|
||||
"confusion-of-tongues": "Littattafai biyu ko fiye a cikin harsuna daban-daban za su shiga cikin bincike, wanda zai iya haifar da sakamako mai ruɗani.",
|
||||
"welcome-page-overzealous-filter": "Babu sakamako. Kuna so a <a href=\"{{URL}}\">sake saita tace</a>?",
|
||||
"powered-by-kiwix-html": "<a href=\"https://kiwix.org\">Kiwix</a> ne ke ƙarfafa shi",
|
||||
"search": "Nema",
|
||||
"book-filtering-all-categories": "Dukkanin nau'o'in",
|
||||
"book-filtering-all-languages": "Duka harsuna",
|
||||
"count-of-matching-books": "{{COUNT}} littafi(s)",
|
||||
"download": "Sauke",
|
||||
"direct-download-link-text": "Kai tsaye",
|
||||
"direct-download-alt-text": "saukewa kai tsaye",
|
||||
"hash-download-link-text": "Sha256 hash",
|
||||
"hash-download-alt-text": "sauke hash",
|
||||
"magnet-link-text": "Magnet link",
|
||||
"magnet-alt-text": "Magnet ɗin saukewa",
|
||||
"torrent-download-link-text": "Torrent fayil",
|
||||
"torrent-download-alt-text": "download torrent",
|
||||
"library-opds-feed-all-entries": "Ciyarwar OPDS Library - Duk shigarwar",
|
||||
"filter-by-tag": "Tace da alamar \"{{TAG}}\"",
|
||||
"stop-filtering-by-tag": "Dakatar da tacewa ta hanyar \"{{TAG}}\"",
|
||||
"library-opds-feed-parameterised": " OPDS ciyar wa OPDS- entries matching {{#LANG}}\nLanguage: {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategory: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nTag: {{TAG}} {{/TAG}}{{#Q}}\nQuery: {{Q}} {{/Q}}",
|
||||
"welcome-to-kiwix-server": "Barka da zowa manhajar Kiwix",
|
||||
"download-links-heading": "Bring out ways through which people can join hands together .{{BOOK_TITLE}}",
|
||||
"download-links-title": "Sauke littafin",
|
||||
"preview-book": "Dubawa",
|
||||
"unknown-error": "Kuskuren da ba a sani ba"
|
||||
}
|
||||
@@ -27,11 +27,6 @@
|
||||
"500-page-text": "אירעה שגיאת שרת פנימית. אנחנו מצטערים על זה :/",
|
||||
"fulltext-search-unavailable": "חיפוש בטקסט מלא אינו זמין",
|
||||
"no-search-results": "מנוע החיפוש בטקסט מלא אינו זמין עבור התוכן הזה.",
|
||||
"search-results-page-title": "חיפוש: {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "תוצאות <b>{{START}} עד {{END}}</b> מתוך <b>{{COUNT}}</b> עבור <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"empty-search-results-page-header": "לא נמצאו תוצאות עבור <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"search-result-book-info": "מתוך {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} מילים",
|
||||
"library-button-text": "מעבר לדף הבית \"ברוך בואך\"",
|
||||
"home-button-text": "מעבר לדף הראשי של \"{{BOOK_TITLE}}\"",
|
||||
"random-page-button-text": "מעבר לדף שנבחר אקראית",
|
||||
@@ -45,13 +40,13 @@
|
||||
"count-of-matching-books": "{{COUNT}} ספרים",
|
||||
"download": "הורדה",
|
||||
"direct-download-link-text": "ישירה",
|
||||
"direct-download-alt-text": "הורדה ישירה דרך HTTP(S)",
|
||||
"hash-download-link-text": "סיכום ביקורת Sha256",
|
||||
"hash-download-alt-text": "הצגת סיכום ביקורת SHA-256",
|
||||
"direct-download-alt-text": "הורדה ישירה",
|
||||
"hash-download-link-text": "גיבוב Sha256",
|
||||
"hash-download-alt-text": "הורדת גיבוב",
|
||||
"magnet-link-text": "קישור Magnet",
|
||||
"magnet-alt-text": "הורדה באמצעות קישור magnet",
|
||||
"torrent-download-link-text": "ביטורנט",
|
||||
"torrent-download-alt-text": "הורדה באמצעות ביטורנט",
|
||||
"magnet-alt-text": "הורדת magnet",
|
||||
"torrent-download-link-text": "קובץ טורנט",
|
||||
"torrent-download-alt-text": "הורדת טורנט",
|
||||
"library-opds-feed-all-entries": "הזנת ספריית OPDS - כל הרשומות",
|
||||
"filter-by-tag": "סינון לפי התג \"{{TAG}}\"",
|
||||
"stop-filtering-by-tag": "להפסיק סינון לפי התג \"{{TAG}}\"",
|
||||
@@ -60,17 +55,5 @@
|
||||
"download-links-heading": "הורדת קישורים עבור <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
"download-links-title": "הורדת ספר",
|
||||
"preview-book": "תצוגה מקדימה",
|
||||
"unknown-error": "שגיאה בלתי־ידועה",
|
||||
"book-category.gutenberg": "גוטנברג",
|
||||
"book-category.vikidia": "ויקידיה",
|
||||
"book-category.wikibooks": "ויקיספר",
|
||||
"book-category.wikinews": "ויקיחדשות",
|
||||
"book-category.wikipedia": "ויקיפדיה",
|
||||
"book-category.wikiquote": "ויקיציטוט",
|
||||
"book-category.wikisource": "ויקיטקסט (Wikisource)",
|
||||
"book-category.wikispecies": "ויקימינים",
|
||||
"book-category.wikiversity": "ויקיברסיטה",
|
||||
"book-category.wikivoyage": "ויקימסע",
|
||||
"book-category.wiktionary": "ויקימילון",
|
||||
"book-category.other": "אחר"
|
||||
"unknown-error": "שגיאה בלתי־ידועה"
|
||||
}
|
||||
|
||||
@@ -52,15 +52,5 @@
|
||||
"welcome-to-kiwix-server": "कीविक्स सर्वर में आपका स्वागत है",
|
||||
"download-links-heading": "<b><i>{{BOOK_TITLE}}</i></b> के लिए डाउनलोड लिंक",
|
||||
"download-links-title": "पुस्तक डाउनलोड करें",
|
||||
"preview-book": "पूर्वावलोकन",
|
||||
"book-category.wikibooks": "विकिपुस्तक",
|
||||
"book-category.wikinews": "विकिसमाचार",
|
||||
"book-category.wikipedia": "विकिपीडिया",
|
||||
"book-category.wikiquote": "विकिसूक्ति",
|
||||
"book-category.wikisource": "विकिस्रोत",
|
||||
"book-category.wikispecies": "विकिप्रजाति",
|
||||
"book-category.wikiversity": "विकिविश्वविद्यालय",
|
||||
"book-category.wikivoyage": "विकियात्रा",
|
||||
"book-category.wiktionary": "विकिकोश",
|
||||
"book-category.other": "अन्य"
|
||||
"preview-book": "पूर्वावलोकन"
|
||||
}
|
||||
|
||||
@@ -17,15 +17,5 @@
|
||||
"home-button-text": "Դեպի '{{BOOK_TITLE}}'֊ի գլխավոր էջը",
|
||||
"random-page-button-text": "Բացել պատահական էջ",
|
||||
"searchbox-tooltip": "Որոնել '{{BOOK_TITLE}}'֊ում",
|
||||
"book-filtering-all-categories": "Բոլոր կատեգորիաներ",
|
||||
"book-category.wikibooks": "Վիքիգրքեր",
|
||||
"book-category.wikinews": "Վիքիլուրեր",
|
||||
"book-category.wikipedia": "Վիքիպեդիա",
|
||||
"book-category.wikiquote": "Վիքիքաղվածք",
|
||||
"book-category.wikisource": "Վիքիդարան",
|
||||
"book-category.wikispecies": "Վիքիցեղեր",
|
||||
"book-category.wikiversity": "Վիքիլսարան",
|
||||
"book-category.wikivoyage": "Վիքիճամփորդ",
|
||||
"book-category.wiktionary": "Վիքիբառարան",
|
||||
"book-category.other": "Այլ"
|
||||
"book-filtering-all-categories": "Բոլոր կատեգորիաներ"
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
"suggest-search": "Facer un recerca in texto complete de <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
||||
"random-article-failure": "Ups! Non poteva eliger un articulo aleatori :(",
|
||||
"invalid-raw-data-type": "{{DATATYPE}} non es un request valide pro contento crude.",
|
||||
"invalid-request": "Le URL requestate “{{{url}}}” non es un requesta valide.",
|
||||
"no-value-for-arg": "Necun valor fornite pro le argumento {{ARGUMENT}}",
|
||||
"no-query": "Necun consulta fornite.",
|
||||
"raw-entry-not-found": "Non pote trovar le entrata {{ENTRY}} del typo {{DATATYPE}}",
|
||||
@@ -24,14 +23,8 @@
|
||||
"404-page-heading": "Non trovate",
|
||||
"500-page-title": "Error interne del servitor",
|
||||
"500-page-heading": "Error interne del servitor",
|
||||
"500-page-text": "Un error interne del servitor ha occurrite. Nos lo regretta :/",
|
||||
"fulltext-search-unavailable": "Le recerca in texto complete es indisponibile",
|
||||
"no-search-results": "Le motor de recerca in texto complete non es disponibile pro iste contento.",
|
||||
"search-results-page-title": "Cercar: {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "Resultatos <b>{{START}}-{{END}}</b> de <b>{{COUNT}}</b> pro <b>“{{{SEARCH_PATTERN}}}”</b>",
|
||||
"empty-search-results-page-header": "Necun resultato ha essite trovate pro <b>“{{{SEARCH_PATTERN}}}”</b>",
|
||||
"search-result-book-info": "de {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} parolas",
|
||||
"library-button-text": "Ir al pagina de benvenita",
|
||||
"home-button-text": "Ir al pagina principal de ''{{BOOK_TITLE}}",
|
||||
"random-page-button-text": "Ir a un pagina seligite aleatorimente",
|
||||
@@ -45,13 +38,13 @@
|
||||
"count-of-matching-books": "{{COUNT}} libro(s)",
|
||||
"download": "Discargar",
|
||||
"direct-download-link-text": "Directe",
|
||||
"direct-download-alt-text": "Discargamento directe per HTTP(S)",
|
||||
"hash-download-link-text": "Summa de controlo SHA-256",
|
||||
"hash-download-alt-text": "Monstrar le summa de controlo SHA-256 del file",
|
||||
"direct-download-alt-text": "discargamento directe",
|
||||
"hash-download-link-text": "Hash SHA256",
|
||||
"hash-download-alt-text": "hash del discargamento",
|
||||
"magnet-link-text": "Ligamine Magnet",
|
||||
"magnet-alt-text": "Discargar con ligamine Magnet",
|
||||
"torrent-download-link-text": "BitTorrent",
|
||||
"torrent-download-alt-text": "Discargar per medio de BitTorrent",
|
||||
"magnet-alt-text": "ligamine \"magnet\" de discargamento",
|
||||
"torrent-download-link-text": "File Torrent",
|
||||
"torrent-download-alt-text": "discargar Torrent",
|
||||
"library-opds-feed-all-entries": "Fluxo OPDS del bibliotheca – Tote le entratas",
|
||||
"filter-by-tag": "Filtrar per etiquetta \"{{TAG}}\"",
|
||||
"stop-filtering-by-tag": "Non plus filtrar per etiquetta \"{{TAG}}\"",
|
||||
@@ -59,6 +52,5 @@
|
||||
"welcome-to-kiwix-server": "Benvenite al servitor Kiwix",
|
||||
"download-links-heading": "Discargar ligamines pro <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
"download-links-title": "Discargar libro",
|
||||
"preview-book": "Previsualisation",
|
||||
"unknown-error": "Error incognite"
|
||||
"preview-book": "Previsualisation"
|
||||
}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Accuratecy051",
|
||||
"Ngostary2k",
|
||||
"Oby Ezeilo"
|
||||
]
|
||||
},
|
||||
"name": "Bekee",
|
||||
"suggest-full-text-search": "nwere {{{SEARCH_TERMS}}}'",
|
||||
"no-such-book": "Enweghị akwụkwọ dị otú a: {{BOOK_NAME}}",
|
||||
"too-many-books": "Arịrịọ ọtụtụ akwụkwọ ({{NB_BOOKS}}) ebe oke bụ {{LIMIT}}",
|
||||
"no-book-found": "Ọ nweghị akwụkwọ dabara na nhọpụta nhọrọ",
|
||||
"url-not-found": "Ahụghị URL a rịọrọ \"{{url}}\" na nkesa a.",
|
||||
"suggest-search": "Mee ọchụchọ ederede zuru oke maka <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
||||
"random-article-failure": "Ee! Ịhọrọ akụkọ enweghị usoro :(",
|
||||
"invalid-raw-data-type": "{{DATATYPE}} abụghị arịrịọ ziri ezi maka ọdịnaya raw.",
|
||||
"invalid-request": "Arịrịọ gbasara URL \"{{{url}}}\" e zighi ezi.",
|
||||
"no-value-for-arg": "Ọ nweghị uru enyere maka arụmụka {{ARGUMENT}}",
|
||||
"no-query": "Ọnweghị ajụjụ enyere.",
|
||||
"raw-entry-not-found": "Enweghị ike ịchọta ntinye {{DATATYPE}} {{ENTRY}}",
|
||||
"400-page-title": "Arịrịọ na-ezighi ezi",
|
||||
"400-page-heading": "Arịrịọ na-ezighi ezi",
|
||||
"404-page-title": "Ahụghị ọdịnaya",
|
||||
"404-page-heading": "Ahụghị",
|
||||
"500-page-title": "Mperi Sava Ime",
|
||||
"500-page-heading": "Mperi Sava Ime",
|
||||
"500-page-text": "Enwere mperi ihe nkesa dị n'ime. Ọ dị anyị nwute na nke ahụ :/",
|
||||
"fulltext-search-unavailable": "Ọchịchọ ederede zuru ezu adịghị",
|
||||
"no-search-results": "Igwe nchọta ederede zuru oke adịghị maka ọdịnaya a.",
|
||||
"search-results-page-title": "Chọọ: {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "Rịzọlt ga <b>{{START}}-{{END}}</b> nke <b>{{COUNT}}</b> maka <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"empty-search-results-page-header": "Ọnweghị rịzọlt ahụrụ maka <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"search-result-book-info": "sitere na {{BOOK_TITLE}}",
|
||||
"word-count": "Okwu {{COUNT}}",
|
||||
"library-button-text": "Gaa na ibe nnabata",
|
||||
"home-button-text": "Gaa na isi ibe nke '{{BOOK_TITLE}}'",
|
||||
"random-page-button-text": "Gaa na ibe ahọpụtara enweghị usoro",
|
||||
"searchbox-tooltip": "Chọọ '{{BOOK_TITLE}}'",
|
||||
"confusion-of-tongues": "Akwụkwọ abụọ ma ọ bụ karịa n'asụsụ dị iche iche ga-esonye na nchọ, nke nwere ike ibute nsonaazụ mgbagwoju anya.",
|
||||
"welcome-page-overzealous-filter": "Enweghị nsonaazụ. Ọ ga-amasị gị <a href=\"{{URL}}\">ịtọgharịa nzacha</a> ?",
|
||||
"powered-by-kiwix-html": "<a href=\"https://kiwix.org\">Kiwix</a> kwadoro ya",
|
||||
"search": "Chọọ",
|
||||
"book-filtering-all-categories": "Nkeji niile",
|
||||
"book-filtering-all-languages": "Asụsụ niile",
|
||||
"count-of-matching-books": "akwụkwọ {{COUNT}}",
|
||||
"download": "Budata",
|
||||
"direct-download-link-text": "Gosi",
|
||||
"direct-download-alt-text": "nbudata ozugbo",
|
||||
"hash-download-link-text": "Sha256 hash",
|
||||
"hash-download-alt-text": "budata hash",
|
||||
"magnet-link-text": "Njikọ magnet",
|
||||
"magnet-alt-text": "ibudata magnet",
|
||||
"torrent-download-link-text": " faịlụ nke Torrent",
|
||||
"torrent-download-alt-text": "Budata torrent",
|
||||
"library-opds-feed-all-entries": "Ọbá akwụkwọ OPDS Feed - Ihe niile",
|
||||
"filter-by-tag": "Wepụta site na mkpado \"{{TAG}}\"",
|
||||
"stop-filtering-by-tag": "Kwụsị nzacha site na mkpado \"{{TAG}}\"",
|
||||
"library-opds-feed-parameterised": "Ọbá akwụkwọ OPDS nri - ndenye dabara na {{#LANG}}\nAsụsụ: {{LANG}} {{/LANG}}{{#CATEGORY}}\n Kategori: {{CATEGORY}} {{/CATEGORY}} {{#TAG}}\nTag: {{TAG}} {{/TAG}}{{#Q}}\n Ajụjụ: {{Q}} {{/Q}}",
|
||||
"welcome-to-kiwix-server": "Nabata na Kiwix Server",
|
||||
"download-links-heading": "Budata njikọ maka <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
"download-links-title": "Budata akwụkwọ",
|
||||
"preview-book": "Ziwe nkirimaàtụ̀",
|
||||
"unknown-error": "amaghị m njehie"
|
||||
}
|
||||
@@ -24,11 +24,6 @@
|
||||
"500-page-title": "Errore interno del server",
|
||||
"500-page-heading": "Errore interno del server",
|
||||
"500-page-text": "Si è verificato un errore interno del server. Ci dispiace :/",
|
||||
"search-results-page-title": "Cerca: {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "Risultati <b>{{START}}-{{END}}</b> di <b>{{COUNT}}</b> per <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"empty-search-results-page-header": "Non è stato trovato alcun risultato per <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"search-result-book-info": "da {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} parole",
|
||||
"library-button-text": "Vai alla pagina di benvenuto",
|
||||
"home-button-text": "Vai alla pagina principale di '{{BOOK_TITLE}}'",
|
||||
"random-page-button-text": "Vai a una pagina selezionata casualmente",
|
||||
@@ -37,10 +32,6 @@
|
||||
"book-filtering-all-languages": "Tutte le lingue",
|
||||
"count-of-matching-books": "{{COUNT}} libro/i",
|
||||
"download": "Scarica",
|
||||
"direct-download-alt-text": "Scarica direttamente tramite HTTP(S)",
|
||||
"magnet-alt-text": "Scarica tramite collegamento Magnet",
|
||||
"torrent-download-link-text": "BitTorrent",
|
||||
"torrent-download-alt-text": "Scarica tramite BitTorrent",
|
||||
"download-links-title": "Scarica libro",
|
||||
"preview-book": "Anteprima",
|
||||
"unknown-error": "Errore sconosciuto"
|
||||
|
||||
@@ -15,7 +15,5 @@
|
||||
"500-page-heading": "내부 서버 오류",
|
||||
"fulltext-search-unavailable": "전문 검색을 사용할 수 없습니다",
|
||||
"random-page-button-text": "무작위로 선택된 문서로 이동",
|
||||
"hash-download-link-text": "SHA-256 체크섬",
|
||||
"torrent-download-link-text": "비트토렌트",
|
||||
"preview-book": "미리 보기"
|
||||
}
|
||||
|
||||
@@ -7,10 +7,6 @@
|
||||
]
|
||||
},
|
||||
"name": "Lëtzebuergesch",
|
||||
"suggest-full-text-search": "enthält '{{{SEARCH_TERMS}}}'...",
|
||||
"no-such-book": "Buch net fonnt: {{BOOK_NAME}}",
|
||||
"too-many-books": "Ze vill Bicher ugefrot ({{NB_BOOKS}}), d'Limitt läit bei {{LIMIT}}",
|
||||
"url-not-found": "Déi ugefroten URL „{{url}}“ gouf op dësem Server net fonnt.",
|
||||
"suggest-search": "Maacht eng Volltext-Sich fir <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
||||
"random-article-failure": "Ups! Et konnt keen zoufällegen Artikel ausgewielt ginn :(",
|
||||
"404-page-title": "Inhalt net fonnt",
|
||||
@@ -19,11 +15,6 @@
|
||||
"500-page-heading": "Interne Feeler um Server",
|
||||
"500-page-text": "Et ass en interne Serverfeeler opgetrueden. Mir entschëllegen eis dofir :/",
|
||||
"fulltext-search-unavailable": "Volltext-Sich net verfügbar",
|
||||
"search-results-page-title": "Sichen: {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "Resultater <b>{{START}}-{{END}}</b> vu(n) <b>{{COUNT}}</b> fir <b>„{{{SEARCH_PATTERN}}}“</b>",
|
||||
"empty-search-results-page-header": "Keng Resultater fonnt fir <b>„{{{SEARCH_PATTERN}}}“</b>",
|
||||
"search-result-book-info": "aus {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} Wierder",
|
||||
"home-button-text": "Gitt op d'Haaptsäit vun '{{BOOK_TITLE}}'",
|
||||
"random-page-button-text": "Gitt op eng zoufälleg gewielte Säit",
|
||||
"searchbox-tooltip": "No '{{BOOK_TITLE}}' sichen",
|
||||
@@ -34,18 +25,6 @@
|
||||
"count-of-matching-books": "{{COUNT}} Buch/Bicher",
|
||||
"download": "Eroflueden",
|
||||
"direct-download-link-text": "Direkt",
|
||||
"torrent-download-link-text": "BitTorrent",
|
||||
"download-links-title": "Buch eroflueden",
|
||||
"unknown-error": "Onbekannte Feeler",
|
||||
"book-category.stack_exchange": "Stack Exchange",
|
||||
"book-category.wikibooks": "Wikibooks",
|
||||
"book-category.wikinews": "Wikinews",
|
||||
"book-category.wikipedia": "Wikipedia",
|
||||
"book-category.wikiquote": "Wikiquote",
|
||||
"book-category.wikisource": "Wikisource",
|
||||
"book-category.wikispecies": "Wikispecies",
|
||||
"book-category.wikiversity": "Wikiversity",
|
||||
"book-category.wikivoyage": "Wikivoyage",
|
||||
"book-category.wiktionary": "Wiktionnaire",
|
||||
"book-category.other": "Anerer"
|
||||
"unknown-error": "Onbekannte Feeler"
|
||||
}
|
||||
|
||||
@@ -27,11 +27,6 @@
|
||||
"500-page-text": "Настана внатрешна грешка во опслужувачот. Жал ни е :/",
|
||||
"fulltext-search-unavailable": "Целотекстното пребарување е недостапно",
|
||||
"no-search-results": "Погонот за целотекстно пребарување не е достапен за оваа содржина.",
|
||||
"search-results-page-title": "Пребарување: {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "Исход <b>{{START}}-{{END}}</b> од <b>{{COUNT}}</b> за <b>„{{{SEARCH_PATTERN}}}“</b>",
|
||||
"empty-search-results-page-header": "Не најдов ништо за <b>„{{{SEARCH_PATTERN}}}“</b>",
|
||||
"search-result-book-info": "од {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} зборови",
|
||||
"library-button-text": "Оди на воведната страница",
|
||||
"home-button-text": "Оди на главната страница на „{{BOOK_TITLE}}“",
|
||||
"random-page-button-text": "Оди на случајно избрана страница",
|
||||
@@ -45,13 +40,13 @@
|
||||
"count-of-matching-books": "{{COUNT}} книги",
|
||||
"download": "Преземи",
|
||||
"direct-download-link-text": "Непосредно",
|
||||
"direct-download-alt-text": "Непосредно преземање преку HTTP(S)",
|
||||
"hash-download-link-text": "Контролен збир Sha256",
|
||||
"hash-download-alt-text": "Прикажи контролен збир SHA-256 на податотеката",
|
||||
"direct-download-alt-text": "непосредно преземање",
|
||||
"hash-download-link-text": "Sha256-тараба",
|
||||
"hash-download-alt-text": "преземи тараба",
|
||||
"magnet-link-text": "Магнетна врска",
|
||||
"magnet-alt-text": "Преземи преку Magnet-врска",
|
||||
"torrent-download-link-text": "BitTorrent",
|
||||
"torrent-download-alt-text": "Преземи преку BitTorrent",
|
||||
"magnet-alt-text": "преземи магнет",
|
||||
"torrent-download-link-text": "Торентна податотека",
|
||||
"torrent-download-alt-text": "преземи торент",
|
||||
"library-opds-feed-all-entries": "Библиотечен тековник на OPDS — Сите ставки",
|
||||
"filter-by-tag": "Филтрирај по ознаката „{{TAG}}“",
|
||||
"stop-filtering-by-tag": "Запри филтрирање по ознаката „{{TAG}}“",
|
||||
@@ -60,23 +55,5 @@
|
||||
"download-links-heading": "Врски за преземање на <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
"download-links-title": "Преземи книга",
|
||||
"preview-book": "Преглед",
|
||||
"unknown-error": "Непозната грешка",
|
||||
"book-category.gutenberg": "Гутенберг",
|
||||
"book-category.iFixit": "iFixit",
|
||||
"book-category.mooc": "MOOC",
|
||||
"book-category.phet": "Phet",
|
||||
"book-category.stack_exchange": "Stack Exchange",
|
||||
"book-category.ted": "Ted",
|
||||
"book-category.vikidia": "Викидија",
|
||||
"book-category.wikibooks": "Викикниги",
|
||||
"book-category.wikihow": "wikiHow",
|
||||
"book-category.wikinews": "Викивести",
|
||||
"book-category.wikipedia": "Википедија",
|
||||
"book-category.wikiquote": "Викицитат",
|
||||
"book-category.wikisource": "Викиизвор",
|
||||
"book-category.wikispecies": "Викивидови",
|
||||
"book-category.wikiversity": "Викиуниверзитет",
|
||||
"book-category.wikivoyage": "Википатување",
|
||||
"book-category.wiktionary": "Викиречник",
|
||||
"book-category.other": "друго"
|
||||
"unknown-error": "Непозната грешка"
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
"authors": [
|
||||
"Kelson",
|
||||
"McDutchie",
|
||||
"Siebrand",
|
||||
"Vistaus"
|
||||
]
|
||||
},
|
||||
@@ -25,12 +24,8 @@
|
||||
"404-page-heading": "Niet gevonden",
|
||||
"500-page-title": "Interne serverfout",
|
||||
"500-page-heading": "Interne serverfout",
|
||||
"500-page-text": "Er is een interne serverfout opgetreden. Dat spijt ons",
|
||||
"fulltext-search-unavailable": "Zoeken in volledige tekst is niet beschikbaar",
|
||||
"no-search-results": "De zoekmachine voor volledige tekst is niet beschikbaar voor deze inhoud.",
|
||||
"search-results-page-title": "Zoeken: {{SEARCH_PATTERN}}",
|
||||
"search-result-book-info": "uit {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} woorden",
|
||||
"library-button-text": "Naar de welkomstpagina",
|
||||
"home-button-text": "Naar de hoofdpagina van ‘{{BOOK_TITLE}}’",
|
||||
"random-page-button-text": "Naar een willekeurig geselecteerde pagina gaan",
|
||||
@@ -44,13 +39,13 @@
|
||||
"count-of-matching-books": "{{COUNT}} boek(en)",
|
||||
"download": "Downloaden",
|
||||
"direct-download-link-text": "Direct",
|
||||
"direct-download-alt-text": "Direct downloaden via HTTP(S)",
|
||||
"hash-download-link-text": "SHA-256-controlesom",
|
||||
"hash-download-alt-text": "De SHA-256-controlesom van het bestand weergeven",
|
||||
"direct-download-alt-text": "directe download",
|
||||
"hash-download-link-text": "SHA256-hash",
|
||||
"hash-download-alt-text": "controlesom (hash) van de download",
|
||||
"magnet-link-text": "Magnet-link",
|
||||
"magnet-alt-text": "Downloaden via Magnet-link",
|
||||
"torrent-download-link-text": "BitTorrent",
|
||||
"torrent-download-alt-text": "Downloaden via BitTorrent",
|
||||
"magnet-alt-text": "magnet-link van de download",
|
||||
"torrent-download-link-text": "Torrent-bestand",
|
||||
"torrent-download-alt-text": "torrent downloaden",
|
||||
"library-opds-feed-all-entries": "OPDS-feed bibliotheek: alle vermeldingen",
|
||||
"filter-by-tag": "Filteren op label “{{TAG}}”",
|
||||
"stop-filtering-by-tag": "Niet meer filteren op label “{{TAG}}”",
|
||||
@@ -58,6 +53,5 @@
|
||||
"welcome-to-kiwix-server": "Welkom bij de Kiwix-server",
|
||||
"download-links-heading": "Downloadkoppelingen voor <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
"download-links-title": "Boek downloaden",
|
||||
"preview-book": "Voorvertoning",
|
||||
"unknown-error": "Onbekende fout"
|
||||
"preview-book": "Voorvertoning"
|
||||
}
|
||||
|
||||
@@ -25,15 +25,7 @@
|
||||
"home-button-text": "Przejdź do głównej strony '{{BOOK_TITLE}}'",
|
||||
"random-page-button-text": "Przejdź do losowo wybranej strony",
|
||||
"searchbox-tooltip": "Szukaj '{{BOOK_TITLE}}'",
|
||||
"search": "Szukaj",
|
||||
"book-filtering-all-categories": "Wszystkie Kategorie",
|
||||
"book-filtering-all-languages": "Wszystkie języki",
|
||||
"download": "Pobierz",
|
||||
"direct-download-link-text": "Bezpośrednio",
|
||||
"direct-download-alt-text": "bezpośrednie pobieranie",
|
||||
"torrent-download-link-text": "Plik torrent",
|
||||
"welcome-to-kiwix-server": "Witamy na serwerze Kiwix",
|
||||
"download-links-title": "Pobierz książkę",
|
||||
"preview-book": "Podgląd",
|
||||
"book-category.other": "Inne"
|
||||
"preview-book": "Podgląd"
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Eduardoaddad",
|
||||
"Obiru",
|
||||
"Re demz",
|
||||
"YoReaper"
|
||||
]
|
||||
},
|
||||
"name": "Português",
|
||||
"suggest-full-text-search": "Contendo '{{{SEARCH_TERMS}}}'...",
|
||||
"400-page-title": "Requisição inválida",
|
||||
"400-page-heading": "Requisição inválida",
|
||||
"404-page-title": "Conteúdo não encontrado",
|
||||
"404-page-heading": "Não encontrado",
|
||||
"500-page-title": "Erro interno do servidor",
|
||||
"500-page-heading": "Erro interno do servidor",
|
||||
"500-page-text": "Aconteceu um erro interno do servidor. Nós pedimos desculpas sobre isso :/",
|
||||
"fulltext-search-unavailable": "Busca por texto completo está indisponível",
|
||||
"search-results-page-title": "Buscar: {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "Resultados <b>{{START}}-{{END}}</b> de <b>{{COUNT}}</b> para <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"empty-search-results-page-header": "Nenhum resultado encontrado para <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"search-result-book-info": "de {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} palavras",
|
||||
"library-button-text": "Ir para página inicial",
|
||||
"home-button-text": "Ir para página principal de '{{BOOK_TITLE}}'",
|
||||
"random-page-button-text": "Ir para uma página aleatória",
|
||||
"searchbox-tooltip": "Buscar '{{BOOK_TITLE}}'",
|
||||
"confusion-of-tongues": "Dois ou mais livros em diferentes idiomas podem participar da pesquisa, isso pode proporcionar resultados confusos.",
|
||||
"search": "Pesquisar",
|
||||
"book-filtering-all-categories": "Todas as categorias",
|
||||
"book-filtering-all-languages": "Todos os idiomas",
|
||||
"count-of-matching-books": "{{COUNT}} livro(s)",
|
||||
"download": "Baixar",
|
||||
"hash-download-link-text": "Verificação SHA-256",
|
||||
"hash-download-alt-text": "Exibir arquivo de verificação SHA-256",
|
||||
"torrent-download-link-text": "BitTorrent",
|
||||
"torrent-download-alt-text": "Baixar via BitTorrent",
|
||||
"welcome-to-kiwix-server": "Bem vindo ao servidor Kiwix",
|
||||
"download-links-heading": "Links para baixar <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
"download-links-title": "Download do livro",
|
||||
"preview-book": "Pré-visualizar",
|
||||
"unknown-error": "Erro desconhecido"
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"B3rnas"
|
||||
]
|
||||
},
|
||||
"name": "português",
|
||||
"suggest-full-text-search": "contendo '{{{SEARCH_TERMS}}}'...",
|
||||
"no-such-book": "Não existe o livro: {{BOOK_NAME}}",
|
||||
"too-many-books": "Demasiadas solicitações de livros ({{NB_BOOKS}}) onde o limite é {{LIMIT}}",
|
||||
"no-book-found": "Nenhum livro corresponde aos critérios de seleção",
|
||||
"url-not-found": "A URL solicitada \"{{url}}\" não foi encontrada neste servidor.",
|
||||
"suggest-search": "Faça uma pesquisa de texto completo para <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
||||
"random-article-failure": "Ups! Erro ao eleger um artigo aleatório :(",
|
||||
"invalid-raw-data-type": "{{DATATYPE}} não é uma solicitação válida para conteúdo bruto.",
|
||||
"invalid-request": "A URL solicitada \"{{{url}}}\" não é uma solicitação válida.",
|
||||
"no-value-for-arg": "Nenhum valor fornecido para o argumento {{ARGUMENT}}",
|
||||
"no-query": "Nenhuma consulta fornecida.",
|
||||
"raw-entry-not-found": "Não é possível encontrar a entrada {{DATATYPE}} {{ENTRY}}",
|
||||
"400-page-title": "Solicitação inválida",
|
||||
"400-page-heading": "Solicitação inválida",
|
||||
"404-page-title": "Conteúdo não encontrado",
|
||||
"404-page-heading": "Não encontrado",
|
||||
"500-page-title": "Erro interno do servidor",
|
||||
"500-page-heading": "Erro interno do servidor",
|
||||
"500-page-text": "Ocorreu um erro interno do servidor. Lamentamos por isso :/",
|
||||
"fulltext-search-unavailable": "Pesquisa de texto completo indisponível",
|
||||
"no-search-results": "O motor de busca de texto completo não está disponível para este conteúdo.",
|
||||
"search-results-page-title": "Pesquisar: {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "Resultados <b>{{START}}-{{END}}</b> de <b>{{COUNT}}</b> para <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"empty-search-results-page-header": "Nenhum resultado foi encontrado para <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"search-result-book-info": "de {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} palavras",
|
||||
"library-button-text": "Ir para página inicial",
|
||||
"home-button-text": "Vá para a página principal de '{{BOOK_TITLE}}'",
|
||||
"random-page-button-text": "Vá para uma página selecionada aleatoriamente",
|
||||
"searchbox-tooltip": "Procurar '{{BOOK_TITLE}}'",
|
||||
"confusion-of-tongues": "Dois ou mais livros em idiomas diferentes participariam da pesquisa, o que poderia levar a resultados confusos.",
|
||||
"welcome-page-overzealous-filter": "Nenhum resultado. Gostaria de <a href=\"{{URL}}\">redefinir o filtro</a> ?",
|
||||
"powered-by-kiwix-html": "Desenvolvido por <a href=\"https://kiwix.org\">Kiwix</a>",
|
||||
"search": "Pesquisar",
|
||||
"book-filtering-all-categories": "Todas as categorias",
|
||||
"book-filtering-all-languages": "Todos os idiomas",
|
||||
"count-of-matching-books": "{{COUNT}} livro(s)",
|
||||
"download": "Transferir",
|
||||
"direct-download-link-text": "Direto",
|
||||
"direct-download-alt-text": "Descarregar diretamente através de HTTP (S)",
|
||||
"hash-download-link-text": "Verificação SHA-256",
|
||||
"hash-download-alt-text": "Exibir arquivo de verificação SHA-256",
|
||||
"magnet-link-text": "Link magnético",
|
||||
"magnet-alt-text": "Descarregar através do link Magnet",
|
||||
"torrent-download-link-text": "BitTorrent",
|
||||
"torrent-download-alt-text": "Descarregar através de BitTorrent",
|
||||
"library-opds-feed-all-entries": "Feed OPDS da biblioteca - Todas as entradas",
|
||||
"filter-by-tag": "Filtrar por tag \"{{TAG}}\"",
|
||||
"stop-filtering-by-tag": "Pare de filtrar pela tag \"{{TAG}}\"",
|
||||
"library-opds-feed-parameterised": "Feed OPDS da biblioteca - entradas que correspondem a {{#LANG}}\nIdioma: {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategoria: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nTag: {{TAG}} {{/TAG}}{{#Q}}\nConsulta: {{Q}} {{/Q}}",
|
||||
"welcome-to-kiwix-server": "Bem-vindo ao Kiwix Server",
|
||||
"download-links-heading": "Links para download de <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
"download-links-title": "Descarregar livros",
|
||||
"preview-book": "Pré-visualização",
|
||||
"unknown-error": "Erro desconhecido",
|
||||
"book-category.wikibooks": "Wikilivros",
|
||||
"book-category.wikinews": "Wikinotícias",
|
||||
"book-category.wikipedia": "Wikipédia",
|
||||
"book-category.wikiquote": "Wikiquote",
|
||||
"book-category.wikisource": "Wikisource",
|
||||
"book-category.wikispecies": "Wikispecies",
|
||||
"book-category.wikiversity": "Wikiversidade",
|
||||
"book-category.wikivoyage": "Wikivoyage",
|
||||
"book-category.wiktionary": "Wikcionário",
|
||||
"book-category.other": "Outro"
|
||||
}
|
||||
@@ -62,23 +62,5 @@
|
||||
"download-links-title": "Title for no-js download page",
|
||||
"preview-book": "Tooltip of book-tile leading to the book",
|
||||
"non-translated-text": "{{ignored}}\nUsed to display text that is generated at runtime and cannot be translated. Nothing to translate about this one.",
|
||||
"unknown-error": "Unknown error",
|
||||
"book-category.gutenberg": "Name for the category of books from the Gutenberg project",
|
||||
"book-category.iFixit": "Name for the category of iFixit books",
|
||||
"book-category.mooc": "Name for the category of MOOC books",
|
||||
"book-category.phet": "Name for the category of Phet books",
|
||||
"book-category.stack_exchange": "Name for the category of books from the Stack Exchange network books",
|
||||
"book-category.ted": "Name for the category of Ted books",
|
||||
"book-category.vikidia": "Name for the category of Vikidia books",
|
||||
"book-category.wikibooks": "Name for the category of Wikibooks books books",
|
||||
"book-category.wikihow": "Name for the category of wikiHow books",
|
||||
"book-category.wikinews": "Name for the category of Wikinews books",
|
||||
"book-category.wikipedia": "Name for the category of Wikipedia books",
|
||||
"book-category.wikiquote": "Name for the category of Wikiquote books",
|
||||
"book-category.wikisource": "Name for the category of Wikisource books",
|
||||
"book-category.wikispecies": "Name for the category of Wikispecies books",
|
||||
"book-category.wikiversity": "Name for the category of Wikiversity books",
|
||||
"book-category.wikivoyage": "Name for the category of Wikivoyage books",
|
||||
"book-category.wiktionary": "Name for the category of Wiktionary books",
|
||||
"book-category.other": "Books not belonging to any special category are listed under this one"
|
||||
"unknown-error": "Unknown error"
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
"Okras",
|
||||
"Pacha Tchernof",
|
||||
"Razno0",
|
||||
"Rofiatmustapha12",
|
||||
"Smavrina"
|
||||
]
|
||||
},
|
||||
@@ -19,7 +18,6 @@
|
||||
"suggest-search": "Выполните полнотекстовый поиск для <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
||||
"random-article-failure": "Ой! Не удалось выбрать случайную статью :(",
|
||||
"invalid-raw-data-type": "{{DATATYPE}} не является допустимым запросом необработанного контента.",
|
||||
"invalid-request": "Запрошенный URL-адрес «{{{url}}}» не является допустимым запросом.",
|
||||
"no-value-for-arg": "Не указано значение для аргумента {{ARGUMENT}}",
|
||||
"no-query": "Не предоставлен запрос.",
|
||||
"raw-entry-not-found": "Не удаётся найти запись {{ENTRY}} типа {{DATATYPE}}",
|
||||
@@ -29,41 +27,30 @@
|
||||
"404-page-heading": "Не найдено",
|
||||
"500-page-title": "Внутренняя ошибка сервера",
|
||||
"500-page-heading": "Внутренняя ошибка сервера",
|
||||
"500-page-text": "Произошла внутренняя ошибка сервера. Мы сожалеем об этом :/",
|
||||
"fulltext-search-unavailable": "Полнотекстовый поиск недоступен",
|
||||
"no-search-results": "Полнотекстовая поисковая система недоступна для этого содержания.",
|
||||
"search-results-page-title": "Поиск: {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "Результаты <b>{{START}}-{{END}}</b> из <b>{{COUNT}}</b> для <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"empty-search-results-page-header": "По запросу <b>\"{{{SEARCH_PATTERN}}}\"</b> результатов не найдено.",
|
||||
"search-result-book-info": "из {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} слов",
|
||||
"library-button-text": "Перейти на страницу-приветствие",
|
||||
"home-button-text": "Перейти на главную страницу '{{BOOK_TITLE}}'",
|
||||
"random-page-button-text": "Перейти на случайно выбранную страницу",
|
||||
"searchbox-tooltip": "Искать '{{BOOK_TITLE}}'",
|
||||
"confusion-of-tongues": "В поиске будут участвовать две или более книг на разных языках, что может привести к запутанным результатам.",
|
||||
"welcome-page-overzealous-filter": "Безрезультатно. Хотите <a href=\"{{URL}}\">сбросить фильтр</a> ?",
|
||||
"powered-by-kiwix-html": "При поддержке <a href=\"https://kiwix.org\">Kiwix</a>",
|
||||
"search": "Найти",
|
||||
"book-filtering-all-categories": "Все категории",
|
||||
"book-filtering-all-languages": "Все языки",
|
||||
"count-of-matching-books": "{{COUNT}} книг(и)",
|
||||
"download": "Скачать",
|
||||
"direct-download-link-text": "Прямой",
|
||||
"direct-download-alt-text": "Загрузка напрямую через HTTP(S)",
|
||||
"hash-download-link-text": "Контрольная сумма SHA-256",
|
||||
"hash-download-alt-text": "Показать контрольную сумму SHA-256 у файла",
|
||||
"direct-download-alt-text": "прямая загрузка",
|
||||
"hash-download-link-text": "Хэш Sha256",
|
||||
"hash-download-alt-text": "скачать хэш",
|
||||
"magnet-link-text": "Магнитная ссылка",
|
||||
"magnet-alt-text": "Скачать по Magnet-ссылке",
|
||||
"torrent-download-link-text": "BitTorrent",
|
||||
"torrent-download-alt-text": "Скачать через BitTorrent",
|
||||
"torrent-download-link-text": "Торрент-файл",
|
||||
"torrent-download-alt-text": "скачать торрент",
|
||||
"library-opds-feed-all-entries": "Канал библиотеки OPDS – все записи",
|
||||
"filter-by-tag": "Фильтровать по тегу \"{{TAG}}\"",
|
||||
"stop-filtering-by-tag": "Прекратить фильтрацию по тегу \"{{TAG}}\"",
|
||||
"library-opds-feed-parameterised": "Канал OPDS библиотеки – записи, соответствующие {{#LANG}}\nLanguage: {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategory: {{CATEGORY}} {{/CATEGORY}} {{#TAG}}\nTag: {{TAG}} {{/TAG}}{{#Q}}\nЗапрос: {{Q}} {{/Q}}",
|
||||
"welcome-to-kiwix-server": "Добро пожаловать на сервер Kiwix",
|
||||
"download-links-heading": "Ссылки для скачивания <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
"download-links-title": "Скачать книгу",
|
||||
"preview-book": "Предпросмотр",
|
||||
"unknown-error": "Неизвестная ошибка"
|
||||
"preview-book": "Предпросмотр"
|
||||
}
|
||||
|
||||
@@ -22,16 +22,5 @@
|
||||
"torrent-download-link-text": "ٹورنٹ فائل",
|
||||
"torrent-download-alt-text": "ٹورںٹ ݙاؤن لوڈ کرو",
|
||||
"download-links-title": "کتاب ڈاؤن لوڈ کرو",
|
||||
"preview-book": "پیشگی ݙکھالا",
|
||||
"book-category.vikidia": "وکی ڈیا",
|
||||
"book-category.wikibooks": "وکی کتاباں",
|
||||
"book-category.wikinews": "وکی خبراں",
|
||||
"book-category.wikipedia": "وکیپیڈیا",
|
||||
"book-category.wikiquote": "وکی ٻول",
|
||||
"book-category.wikisource": "وکی ماخذ",
|
||||
"book-category.wikispecies": "وکی سپیشیز",
|
||||
"book-category.wikiversity": "وکی ورسٹی",
|
||||
"book-category.wikivoyage": "وکی سیرسپاٹا",
|
||||
"book-category.wiktionary": "وکشنری",
|
||||
"book-category.other": "ٻیا"
|
||||
"preview-book": "پیشگی ݙکھالا"
|
||||
}
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Eleassar",
|
||||
"Kelson",
|
||||
"Rofiatmustapha12"
|
||||
"Kelson"
|
||||
]
|
||||
},
|
||||
"name": "slovenščina",
|
||||
@@ -28,11 +27,6 @@
|
||||
"500-page-text": "Prišlo je do notranje napake strežnika. Žal nam je za to. :/",
|
||||
"fulltext-search-unavailable": "Iskanje po celotnem besedilu ni na voljo",
|
||||
"no-search-results": "Iskalnik po celotnem besedilu za to vsebino ni na voljo.",
|
||||
"search-results-page-title": "Iskanje: {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "Zadetki <b>{{START}}–{{END}}</b> od <b>{{COUNT}}</b> za »<b>{{{SEARCH_PATTERN}}}</b>«",
|
||||
"empty-search-results-page-header": "Ni zadetkov za »<b>{{{SEARCH_PATTERN}}}</b>«",
|
||||
"search-result-book-info": "iz {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} besed",
|
||||
"library-button-text": "Pojdite na pozdravno stran",
|
||||
"home-button-text": "Pojdite na glavno stran »{{BOOK_TITLE}}«",
|
||||
"random-page-button-text": "Pojdite na naključno izbrano stran",
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Jopparn",
|
||||
"Rofiatmustapha12",
|
||||
"Sabelöga",
|
||||
"WikiPhoenix"
|
||||
]
|
||||
@@ -29,11 +28,6 @@
|
||||
"500-page-text": "Ett internt serverfel uppstod. Vi ber om ursäkt för det :/",
|
||||
"fulltext-search-unavailable": "Fulltextsökning är inte tillgänglig",
|
||||
"no-search-results": "Sökmaskinen för fulltext är inte tillgänglig för detta innehåll.",
|
||||
"search-results-page-title": "Sök: {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "Resultat <b>{{START}}-{{END}}</b> av <b>{{COUNT}}</b> för <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"empty-search-results-page-header": "Inga resultat hittades för <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"search-result-book-info": "från {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} ord",
|
||||
"library-button-text": "Gå till hemsidan",
|
||||
"home-button-text": "Gå till huvudsidan för \"{{BOOK_TITLE}}\"",
|
||||
"random-page-button-text": "Gå till en slumpmässigt utvald sida",
|
||||
@@ -47,13 +41,13 @@
|
||||
"count-of-matching-books": "{{COUNT}} böcker",
|
||||
"download": "Ladda ned",
|
||||
"direct-download-link-text": "Direkt",
|
||||
"direct-download-alt-text": "Ladda ner direkt via HTTP(S)",
|
||||
"hash-download-link-text": "SHA-256-kontrollsiffra",
|
||||
"hash-download-alt-text": "Visa SHA-256-filens kontrollsiffra",
|
||||
"direct-download-alt-text": "direktnedladdning",
|
||||
"hash-download-link-text": "Sha256-hash",
|
||||
"hash-download-alt-text": "ladda ned hash",
|
||||
"magnet-link-text": "Magnetlänk",
|
||||
"magnet-alt-text": "Ladda ner via Magnet-länk",
|
||||
"torrent-download-link-text": "BitTorrent",
|
||||
"torrent-download-alt-text": "Ladda ner via BitTorrent",
|
||||
"magnet-alt-text": "ladda ned magnet",
|
||||
"torrent-download-link-text": "Torrent-fil",
|
||||
"torrent-download-alt-text": "ladda ned torrent",
|
||||
"library-opds-feed-all-entries": "Library OPDS Feed - Alla poster",
|
||||
"filter-by-tag": "Filtrera efter taggen \"{{TAG}}\"",
|
||||
"stop-filtering-by-tag": "Sluta filtrera efter taggen \"{{TAG}}\"",
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Peggy",
|
||||
"Wangombe"
|
||||
]
|
||||
},
|
||||
"name": "Kiswahili",
|
||||
"suggest-full-text-search": "ina '{{{SEARCH_TERMS}}}}'...",
|
||||
"no-such-book": "Hakuna kitabu kama hiki: {{BOOK_NAME}}",
|
||||
"too-many-books": "Vitabu vingi mno vimeombwa ({{NB_BOOKS}}) ambapo kikomo ni {{LIMIT}}",
|
||||
"no-book-found": "Hakuna kitabu kinacholingana na vigezo vya uteuzi",
|
||||
"url-not-found": "URL iliyoombwa \"{{url}}\" haikupatikana kwenye seva hii.",
|
||||
"suggest-search": "Tafuta maandishi kamili ya <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
||||
"random-article-failure": "Lo! Imeshindwa kuchagua makala nasibu :(",
|
||||
"invalid-raw-data-type": "{{DATATYPE}} si ombi halali la maudhui ghafi.",
|
||||
"invalid-request": "URL iliyoombwa \"{{{url}}}\" si ombi halali.",
|
||||
"no-value-for-arg": "Hakuna thamani iliyotolewa kwa hoja {{ARGUMENT}}",
|
||||
"no-query": "Hakuna swali lililotolewa.",
|
||||
"raw-entry-not-found": "Haiwezi kupata ingizo la {{DATATYPE}} {{ENTRY}}",
|
||||
"400-page-title": "Ombi batili",
|
||||
"400-page-heading": "Ombi batili",
|
||||
"404-page-title": "Maudhui hayajapatikana",
|
||||
"404-page-heading": "Haijapatikana",
|
||||
"500-page-title": "Hitilafu ya Ndani ya Seva",
|
||||
"500-page-heading": "Hitilafu ya Ndani ya Seva",
|
||||
"500-page-text": "Hitilafu ya ndani ya seva imetokea. Tunasikitika kwa hilo:/",
|
||||
"fulltext-search-unavailable": "Utafutaji wa maandishi kamili haupatikani",
|
||||
"no-search-results": "Injini ya utafutaji ya maandishi kamili haipatikani kwa maudhui haya.",
|
||||
"search-results-page-title": "Tafuta: {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "Matokeo <b>{{START}}-{{END}}</b> ya <b>{{COUNT}}</b> ya <b>\"{{{SEARCH_PATTERN}}}}\"</b>",
|
||||
"empty-search-results-page-header": "Hakuna matokeo yaliyopatikana ya <b>\"{{{SEARCH_PATTERN}}}}\"</b>",
|
||||
"search-result-book-info": "kutoka kwa {{BOOK_TITLE}}",
|
||||
"word-count": "Maneno {{COUNT}}",
|
||||
"library-button-text": "Nenda katika wiki ya mwanzo",
|
||||
"home-button-text": "Nenda kwenye ukurasa mkuu wa '{{BOOK_TITLE}}'",
|
||||
"random-page-button-text": "Nenda kwa ukurasa uliochaguliwa kwa nasibu",
|
||||
"searchbox-tooltip": "Tafuta '{{BOOK_TITLE}}'",
|
||||
"confusion-of-tongues": "Vitabu viwili au zaidi katika lugha tofauti vitashiriki katika utafutaji, jambo ambalo linaweza kusababisha matokeo ya kutatanisha.",
|
||||
"welcome-page-overzealous-filter": "Hakuna matokeo. Je, ungependa <a href=\"{{URL}}\">kuweka upya kichujio</a> ?",
|
||||
"powered-by-kiwix-html": "Inaendeshwa na <a href=\"https://kiwix.org\">Kiwix</a>",
|
||||
"search": "Tafuta",
|
||||
"book-filtering-all-categories": "Kategoria Zote",
|
||||
"book-filtering-all-languages": "Lugha zote",
|
||||
"count-of-matching-books": "Vitabu {{COUNT}}",
|
||||
"download": "Pakua",
|
||||
"direct-download-link-text": "Moja kwa moja",
|
||||
"direct-download-alt-text": "kupakua moja kwa moja",
|
||||
"hash-download-link-text": "Sha256 heshi",
|
||||
"hash-download-alt-text": "pakua heshi",
|
||||
"magnet-link-text": "Kiungo cha sumaku",
|
||||
"magnet-alt-text": "sumaku ya kupakua",
|
||||
"torrent-download-link-text": "Faili ya Torrent",
|
||||
"torrent-download-alt-text": "pakua torrent",
|
||||
"library-opds-feed-all-entries": "Mlisho wa OPDS wa Maktaba - Maingizo yote",
|
||||
"filter-by-tag": "Chuja kwa lebo \"{{TAG}}\"",
|
||||
"stop-filtering-by-tag": "Acha kuchuja kwa lebo \"{{TAG}}\"",
|
||||
"library-opds-feed-parameterised": "Mlisho wa OPDS wa Maktaba - maingizo yanayolingana {{#LANG}}\nLugha: {{LANG}} {{/LANG}}{{#CATEGORY}}\nKitengo: {{CATEGORY}} {{/CATEGORY}} {{#TAG}}\nTag: {{TAG}} {{/TAG}}{{#Q}}\nSwali: {{Q}} {{/Q}}",
|
||||
"welcome-to-kiwix-server": "Karibu kwenye Seva ya Kiwix",
|
||||
"download-links-heading": "Pakua viungo vya <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
"download-links-title": "Pakua vitabu",
|
||||
"preview-book": "Hakiki",
|
||||
"unknown-error": "Hitilafu isiyojulikana"
|
||||
}
|
||||
@@ -47,22 +47,4 @@
|
||||
, "empty-search-results-page-header": "[I18N TESTING] No results were found for <b>\"{{{SEARCH_PATTERN}}}\"</b>"
|
||||
, "search-result-book-info": "from [I18N TESTING] {{BOOK_TITLE}}"
|
||||
, "word-count": "{{COUNT}} [I18N TESTING] words"
|
||||
, "book-category.gutenberg": "[I18N] Gutenberg [TESTING]"
|
||||
, "book-category.iFixit": "[I18N] iFixit [TESTING]"
|
||||
, "book-category.mooc": "[I18N] MOOC [TESTING]"
|
||||
, "book-category.phet": "[I18N] Phet [TESTING]"
|
||||
, "book-category.stack_exchange": "[I18N] Stack Exchange [TESTING]"
|
||||
, "book-category.ted": "[I18N] Ted [TESTING]"
|
||||
, "book-category.vikidia": "[I18N] Vikidia [TESTING]"
|
||||
, "book-category.wikibooks": "[I18N] Wikibooks [TESTING]"
|
||||
, "book-category.wikihow": "[I18N] wikiHow [TESTING]"
|
||||
, "book-category.wikinews": "[I18N] Wikinews [TESTING]"
|
||||
, "book-category.wikipedia": "[I18N] Wikipedia [TESTING]"
|
||||
, "book-category.wikiquote": "[I18N] Wikiquote [TESTING]"
|
||||
, "book-category.wikisource": "[I18N] Wikisource [TESTING]"
|
||||
, "book-category.wikispecies": "[I18N] Wikispecies [TESTING]"
|
||||
, "book-category.wikiversity": "[I18N] Wikiversity [TESTING]"
|
||||
, "book-category.wikivoyage": "[I18N] Wikivoyage [TESTING]"
|
||||
, "book-category.wiktionary": "[I18N] Wiktionary [TESTING]"
|
||||
, "book-category.other": "[I18N] Other [TESTING]"
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Hedda",
|
||||
"Rofiatmustapha12"
|
||||
"Hedda"
|
||||
]
|
||||
},
|
||||
"name": "Türkçe",
|
||||
@@ -14,7 +13,6 @@
|
||||
"suggest-search": "<a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a> için tam metin araması yapın",
|
||||
"random-article-failure": "Hata! Rastgele bir madde seçilemedi :(",
|
||||
"invalid-raw-data-type": "{{DATATYPE}}, ham içerik için geçerli bir istek değil.",
|
||||
"invalid-request": "İstenen \"{{{url}}}\" URL'si geçerli bir istek değil.",
|
||||
"no-value-for-arg": "{{ARGUMENT}} bağımsız değişkeni için değer sağlanmadı",
|
||||
"no-query": "Sorgu sağlanmadı.",
|
||||
"raw-entry-not-found": "{{DATATYPE}} {{ENTRY}} girişi bulunamadı",
|
||||
@@ -24,41 +22,10 @@
|
||||
"404-page-heading": "Bulunamadı",
|
||||
"500-page-title": "İç Sunucu Hatası",
|
||||
"500-page-heading": "İç Sunucu Hatası",
|
||||
"500-page-text": "Dahili bir sunucu hatası oluştu. Bunun için üzgünüz :/",
|
||||
"fulltext-search-unavailable": "Tam metin araması kullanılamıyor",
|
||||
"no-search-results": "Tam metin arama motoru bu içerik için kullanılamaz.",
|
||||
"search-results-page-title": "Arama: {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "<b>\"{{{SEARCH_PATTERN}}}\"</b> için <b>{{COUNT}}</b> sonuçtan <b>{{START}}-{{END}}</b> arası sonuçlar",
|
||||
"empty-search-results-page-header": "<b>\"{{{SEARCH_PATTERN}}}\"</b> için sonuç bulunamadı",
|
||||
"search-result-book-info": "{{BOOK_TITLE}} adlı kitaptan",
|
||||
"word-count": "{{COUNT}} kelime",
|
||||
"library-button-text": "Karşılama sayfasına git",
|
||||
"home-button-text": "'{{BOOK_TITLE}}' anasayfasına gidin",
|
||||
"random-page-button-text": "Rastgele seçilen bir sayfaya git",
|
||||
"searchbox-tooltip": "'{{BOOK_TITLE}}' ara",
|
||||
"confusion-of-tongues": "Aramaya farklı dillerde iki veya daha fazla kitap katılacak ve bu da kafa karıştırıcı sonuçlara yol açabilecektir.",
|
||||
"welcome-page-overzealous-filter": "Sonuç yok. <a href=\"{{URL}}\">Filtreyi sıfırlamak</a> ister misiniz?",
|
||||
"powered-by-kiwix-html": "<a href=\"https://kiwix.org\">Kiwix</a> tarafından desteklenmektedir",
|
||||
"search": "Ara",
|
||||
"book-filtering-all-categories": "Tüm kategoriler",
|
||||
"book-filtering-all-languages": "Tüm diller",
|
||||
"count-of-matching-books": "{{COUNT}} kitap",
|
||||
"download": "İndir",
|
||||
"direct-download-link-text": "Doğrudan",
|
||||
"direct-download-alt-text": "direkt indirme",
|
||||
"hash-download-link-text": "Sha256 haşesi",
|
||||
"hash-download-alt-text": "csv indir",
|
||||
"magnet-link-text": "Mıknatıs bağlantısı",
|
||||
"magnet-alt-text": "mıknatısı indir",
|
||||
"torrent-download-link-text": "Hedef dosya",
|
||||
"torrent-download-alt-text": "torrenti indir",
|
||||
"library-opds-feed-all-entries": "Kütüphane OPDS Akışı - Tüm girişler",
|
||||
"filter-by-tag": "\"{{TAG}}\" etiketine göre filtrele",
|
||||
"stop-filtering-by-tag": "\"{{TAG}}\" etiketine göre filtrelemeyi durdur",
|
||||
"library-opds-feed-parameterised": "Kütüphane OPDS Özet Akışı - {{#LANG}}\nLanguage: {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategory: {{CATEGORY}} {{/CATEGORY}} ile eşleşen girişler {{#TAG}}\nTag: {{TAG}} {{/TAG}}{{#Q}}\nQuery: {{Q}} {{/Q}}",
|
||||
"welcome-to-kiwix-server": "Kiwix Sunucusuna Hoş Geldiniz",
|
||||
"download-links-heading": "<b><i>{{BOOK_TITLE}}</i></b> için indirme bağlantıları",
|
||||
"download-links-title": "Kitapları indir",
|
||||
"preview-book": "Önizleme",
|
||||
"unknown-error": "Bilinmeyen hata"
|
||||
"searchbox-tooltip": "'{{BOOK_TITLE}}' ara"
|
||||
}
|
||||
|
||||
@@ -2,78 +2,24 @@
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"GuoPC",
|
||||
"IceButBin",
|
||||
"Kichin",
|
||||
"StarrySky",
|
||||
"Sunai",
|
||||
"XtexChooser",
|
||||
"沈澄心"
|
||||
"XtexChooser"
|
||||
]
|
||||
},
|
||||
"name": "简体中文",
|
||||
"suggest-full-text-search": "正在查找「{{{SEARCH_TERMS}}}」…",
|
||||
"no-such-book": "没有名为“{{BOOK_NAME}}”的图书",
|
||||
"too-many-books": "请求的图书过多 ({{NB_BOOKS}}),上限为 {{LIMIT}}",
|
||||
"no-book-found": "没有符合搜索要求的图书",
|
||||
"url-not-found": "在此服务器上找不到请求的 URL:{{url}}",
|
||||
"suggest-search": "对<a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>进行全文搜索",
|
||||
"random-article-failure": "抱歉! 随机条目失败了 (〒﹏〒) 【好生草的表情(】",
|
||||
"invalid-raw-data-type": "{{DATATYPE}} 对原请求无效。",
|
||||
"invalid-request": "请求的URL无效:{{{url}}}",
|
||||
"no-value-for-arg": "参数{{ARGUMENT}}无值",
|
||||
"name": "英语",
|
||||
"no-query": "未提供查询。",
|
||||
"raw-entry-not-found": "找不到 {{DATATYPE}} 条目 {{ENTRY}}",
|
||||
"400-page-title": "无效请求",
|
||||
"400-page-heading": "无效请求",
|
||||
"404-page-title": "未找到内容",
|
||||
"404-page-heading": "未找到",
|
||||
"500-page-title": "内部服务器错误",
|
||||
"500-page-heading": "内部服务器错误",
|
||||
"500-page-text": "内部服务器出现错误。真的十分抱歉 (;ŏ﹏ŏ~)",
|
||||
"fulltext-search-unavailable": "全文搜索不可用",
|
||||
"no-search-results": "全文搜索引擎不适用于该内容。",
|
||||
"search-results-page-title": "搜索:{{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "<b>“{{{SEARCH_PATTERN}}}”</b>的<b>第 {{START}}-{{END}}</b>个结果(共<b>{{COUNT}}</b>个)",
|
||||
"empty-search-results-page-header": "未找到<b>“{{{SEARCH_PATTERN}}}”</b>的结果",
|
||||
"search-result-book-info": "来自{{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} 个字",
|
||||
"library-button-text": "转到欢迎页面",
|
||||
"home-button-text": "转到“{{BOOK_TITLE}}”的主页",
|
||||
"random-page-button-text": "前往随机选择的页面",
|
||||
"searchbox-tooltip": "搜索“{{BOOK_TITLE}}”",
|
||||
"confusion-of-tongues": "两本或多本不同语言的图书将同时被搜索,这可能会导致搜索结果混乱。",
|
||||
"welcome-page-overzealous-filter": "没有结果。您想<a href=\"{{URL}}\">重置过滤器</a>吗?",
|
||||
"powered-by-kiwix-html": "由<a href=\"https://kiwix.org\">Kiwix</a>提供技术支持",
|
||||
"library-button-text": "前往欢迎页面",
|
||||
"search": "搜索",
|
||||
"book-filtering-all-categories": "所有分类",
|
||||
"book-filtering-all-languages": "所有语言",
|
||||
"count-of-matching-books": "{{COUNT}} 本书",
|
||||
"download": "下载",
|
||||
"direct-download-link-text": "直接",
|
||||
"direct-download-alt-text": "通过 HTTP(S) 直接下載",
|
||||
"hash-download-link-text": "SHA-256 校验和",
|
||||
"hash-download-alt-text": "显示 SHA-256 文件校验和",
|
||||
"magnet-link-text": "磁力链接",
|
||||
"magnet-alt-text": "通过磁力链接下载",
|
||||
"torrent-download-link-text": "BitTorrent",
|
||||
"torrent-download-alt-text": "通过 BitTorrent 下载",
|
||||
"library-opds-feed-all-entries": "图书馆 OPDS Feed - 所有条目",
|
||||
"filter-by-tag": "按标签“{{TAG}}”过滤",
|
||||
"stop-filtering-by-tag": "停止按标签“{{TAG}}”过滤",
|
||||
"library-opds-feed-parameterised": "图书馆 OPDS Feed - 匹配的项目 {{#LANG}}\n语言:{{LANG}} {{/LANG}}{{#CATEGORY}}\n分类:{{CATEGORY}} {{/CATEGORY}}{{#TAG}}\n标签:{{TAG}} {{/TAG}}{{#Q}}\n查询:{{Q}} {{/Q}}",
|
||||
"welcome-to-kiwix-server": "欢迎来到 Kiwix 服务器",
|
||||
"download-links-heading": "下载<b><i>{{BOOK_TITLE}}</i></b>的链接",
|
||||
"download-links-title": "下载书籍",
|
||||
"preview-book": "预览",
|
||||
"unknown-error": "未知错误",
|
||||
"book-category.wikibooks": "维基教科书",
|
||||
"book-category.wikinews": "维基新闻",
|
||||
"book-category.wikipedia": "维基百科",
|
||||
"book-category.wikiquote": "维基语录",
|
||||
"book-category.wikisource": "维基文库",
|
||||
"book-category.wikispecies": "维基物种",
|
||||
"book-category.wikiversity": "维基学院",
|
||||
"book-category.wikivoyage": "维基导游",
|
||||
"book-category.wiktionary": "维基词典",
|
||||
"book-category.other": "其他"
|
||||
"torrent-download-link-text": "种子文件",
|
||||
"preview-book": "预览"
|
||||
}
|
||||
|
||||
@@ -28,11 +28,6 @@
|
||||
"500-page-text": "內部伺服器發生錯誤。對此我們深感抱歉:/",
|
||||
"fulltext-search-unavailable": "全文搜尋無效",
|
||||
"no-search-results": "全文搜尋引擎不適用此內容。",
|
||||
"search-results-page-title": "搜尋:{{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "<b>「{{{SEARCH_PATTERN}}}」</b>的<b>第{{START}}-{{END}}筆</b>結果(共<b>{{COUNT}}</b>筆)",
|
||||
"empty-search-results-page-header": "未找到<b>「{{{SEARCH_PATTERN}}}」</b>的結果",
|
||||
"search-result-book-info": "來自{{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}}個字",
|
||||
"library-button-text": "前往歡迎首頁",
|
||||
"home-button-text": "前往「{{BOOK_TITLE}}」的首頁",
|
||||
"random-page-button-text": "前往隨機選取頁面",
|
||||
@@ -46,13 +41,13 @@
|
||||
"count-of-matching-books": "{{COUNT}} 本書籍",
|
||||
"download": "下載",
|
||||
"direct-download-link-text": "直接",
|
||||
"direct-download-alt-text": "直接透過 HTTP(S)下載",
|
||||
"hash-download-link-text": "SHA-256 核對和",
|
||||
"hash-download-alt-text": "顯示 SHA-256 檔案核對和",
|
||||
"direct-download-alt-text": "直接下載",
|
||||
"hash-download-link-text": "Sha256 雜湊",
|
||||
"hash-download-alt-text": "下載雜湊",
|
||||
"magnet-link-text": "Magnet 連結",
|
||||
"magnet-alt-text": "透過磁力連結下載",
|
||||
"torrent-download-link-text": "BitTorrent",
|
||||
"torrent-download-alt-text": "透過 BitTorrent 下載",
|
||||
"magnet-alt-text": "下載 magnet",
|
||||
"torrent-download-link-text": "Torrent 檔案",
|
||||
"torrent-download-alt-text": "下載 torrent",
|
||||
"library-opds-feed-all-entries": "圖書館 OPDS 摘要 - 所有項目",
|
||||
"filter-by-tag": "依標籤「{{TAG}}」篩選",
|
||||
"stop-filtering-by-tag": "停止依標籤「{{TAG}}」篩選",
|
||||
@@ -61,23 +56,5 @@
|
||||
"download-links-heading": "下載<b><i>{{BOOK_TITLE}}</i></b>的連結",
|
||||
"download-links-title": "下載書籍",
|
||||
"preview-book": "預覽",
|
||||
"unknown-error": "不明錯誤",
|
||||
"book-category.gutenberg": "古騰堡計劃",
|
||||
"book-category.iFixit": "iFixit",
|
||||
"book-category.mooc": "MOOC",
|
||||
"book-category.phet": "PhET",
|
||||
"book-category.stack_exchange": "Stack Exchange",
|
||||
"book-category.ted": "Ted",
|
||||
"book-category.vikidia": "Vikidia",
|
||||
"book-category.wikibooks": "維基教科書",
|
||||
"book-category.wikihow": "wikiHow",
|
||||
"book-category.wikinews": "維基新聞",
|
||||
"book-category.wikipedia": "維基百科",
|
||||
"book-category.wikiquote": "維基語錄",
|
||||
"book-category.wikisource": "維基文庫",
|
||||
"book-category.wikispecies": "維基物種",
|
||||
"book-category.wikiversity": "維基學院",
|
||||
"book-category.wikivoyage": "維基導遊",
|
||||
"book-category.wiktionary": "維基詞典",
|
||||
"book-category.other": "其他"
|
||||
"unknown-error": "不明錯誤"
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
|
||||
.tagFilterLabel {
|
||||
width: max-content;
|
||||
padding: 7px;
|
||||
padding: 10px;
|
||||
font-family: roboto;
|
||||
font-size: 12px;
|
||||
margin: 0 0 0 17px;
|
||||
@@ -152,24 +152,23 @@
|
||||
|
||||
.book__link {
|
||||
text-decoration: none;
|
||||
grid-column: 1 / 3;
|
||||
grid-row: 1 / 3;
|
||||
}
|
||||
|
||||
.book__wrapper {
|
||||
--tile-width: 250px;
|
||||
--tile-border-width: 1px;
|
||||
--tile-border-radius: 3px;
|
||||
color: #444343;
|
||||
height: 258px;
|
||||
width: var(--tile-width);
|
||||
height: 248px;
|
||||
width: 250px;
|
||||
margin: 15px;
|
||||
background-color: #f7f7f7;
|
||||
border: var(--tile-border-width) solid #ececec;
|
||||
border-radius: var(--tile-border-radius);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
border: 1px solid #ececec;
|
||||
border-radius: 3px;
|
||||
display: grid;
|
||||
grid-template-columns: 65px 1fr;
|
||||
grid-template-rows: 70px 120px 1fr 1fr;
|
||||
grid-gap: 5px;
|
||||
transition: transform 0.25s;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.book__wrapper:hover {
|
||||
@@ -178,12 +177,8 @@
|
||||
|
||||
.book__link__wrapper {
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"bookIcon bookHeader"
|
||||
"bookDesc bookDesc"
|
||||
;
|
||||
grid-template-columns: 65px 1fr;
|
||||
grid-template-rows: 70px 120px;
|
||||
grid-template-rows: 70px 120px 1fr 1fr;
|
||||
}
|
||||
|
||||
.book__icon {
|
||||
@@ -194,10 +189,7 @@
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
margin: 10px 0 0 10px;
|
||||
grid-area: bookIcon;
|
||||
}
|
||||
|
||||
.book__header {
|
||||
@@ -209,7 +201,6 @@
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
grid-area: bookHeader;
|
||||
}
|
||||
|
||||
.book__title {
|
||||
@@ -220,36 +211,28 @@
|
||||
}
|
||||
|
||||
.book__download {
|
||||
cursor: pointer;
|
||||
font-size: 1.2rem;
|
||||
background: #00b4e4;
|
||||
padding: 8px;
|
||||
border-radius: 0 0 var(--tile-border-radius) var(--tile-border-radius);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
width: var(--tile-width);
|
||||
left: calc(0px - var(--tile-border-width));
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.book__download > img {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
font-size: 1.1rem;
|
||||
margin: 3px 0;
|
||||
}
|
||||
|
||||
.book__download > span {
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
padding: 0 3px 1px;
|
||||
font-family: roboto;
|
||||
background: #00b4e4;
|
||||
color: white;
|
||||
box-shadow: 0 0 0 0;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.book__download:hover {
|
||||
background: royalblue;
|
||||
.book__download > span:hover {
|
||||
box-shadow: 0 5px 5px rgba(0, 0, 0, 0.1)
|
||||
}
|
||||
|
||||
.book__description {
|
||||
grid-column: 1 / 3;
|
||||
margin: 10px 10px 5px;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
@@ -260,13 +243,6 @@
|
||||
font-size: 1.2rem;
|
||||
color: #4d4d4d;
|
||||
line-height: 1.25;
|
||||
grid-area: bookDesc;
|
||||
}
|
||||
|
||||
.book__meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 10px 4px;
|
||||
}
|
||||
|
||||
.book__languageTag {
|
||||
@@ -279,6 +255,7 @@
|
||||
color: black;
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
margin: 10px auto 0 10px;
|
||||
border-radius: 5px;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
@@ -289,6 +266,8 @@
|
||||
font-size: 1.1rem;
|
||||
justify-content: flex-end;
|
||||
font-family: roboto;
|
||||
margin-right: 10px;
|
||||
margin-top: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
@@ -78,21 +78,15 @@
|
||||
return result;
|
||||
}
|
||||
|
||||
function humanFriendlyNumStr(num, precision) {
|
||||
const n = Math.abs(num).toFixed().length;
|
||||
return num.toFixed(Math.max(0, precision - n));
|
||||
}
|
||||
|
||||
const humanFriendlySize = (fileSize) => {
|
||||
if (fileSize === 0) {
|
||||
return '';
|
||||
}
|
||||
const units = ['bytes', 'KiB', 'MiB', 'GiB', 'TiB'];
|
||||
let quotient = Math.floor(Math.log2(fileSize) / 10);
|
||||
quotient = Math.min(quotient, units.length - 1);
|
||||
fileSize /= (1024 ** quotient);
|
||||
const fileSizeStr = humanFriendlyNumStr(fileSize, 3);
|
||||
return `${fileSizeStr} ${units[quotient]}`;
|
||||
const units = ['bytes', 'kB', 'MB', 'GB', 'TB'];
|
||||
let quotient = Math.floor(Math.log10(fileSize) / 3);
|
||||
quotient = quotient < units.length ? quotient : units.length - 1;
|
||||
fileSize /= (1000 ** quotient);
|
||||
return `${+fileSize.toFixed(2)} ${units[quotient]}`;
|
||||
};
|
||||
|
||||
const humanFriendlyTitle = (title) => {
|
||||
@@ -105,14 +99,6 @@
|
||||
return '';
|
||||
}
|
||||
|
||||
// Borrowed from https://stackoverflow.com/a/1912522
|
||||
function htmlDecode(input){
|
||||
var e = document.createElement('textarea');
|
||||
e.innerHTML = input;
|
||||
// handle case of empty input
|
||||
return e.childNodes.length === 0 ? "" : e.childNodes[0].nodeValue;
|
||||
}
|
||||
|
||||
function htmlEncode(str) {
|
||||
return str.replace(/[\u00A0-\u9999<>\&]/gim, (i) => `&#${i.charCodeAt(0)};`);
|
||||
}
|
||||
@@ -129,27 +115,9 @@
|
||||
|
||||
function generateTagLink(tagValue) {
|
||||
tagValue = tagValue.toLowerCase();
|
||||
const tagMessage = $t("filter-by-tag", {TAG: tagValue});
|
||||
const spanElement = document.createElement("span");
|
||||
spanElement.className = 'tag__link';
|
||||
spanElement.setAttribute('aria-label', tagMessage);
|
||||
spanElement.setAttribute('title', tagMessage);
|
||||
spanElement.setAttribute('data-tag', tagValue);
|
||||
spanElement.innerHTML = htmlEncode(tagValue);
|
||||
return spanElement.outerHTML;
|
||||
}
|
||||
|
||||
function downloadButtonText(zimSize) {
|
||||
return $t("download") + (zimSize ? ` - ${zimSize}`: '');
|
||||
}
|
||||
|
||||
function downloadButtonHtml(url, zimSize) {
|
||||
return url
|
||||
? `<div class="book__download" data-link="${url}" data-size="${zimSize}">
|
||||
<img src="${root}/skin/download-white.svg?KIWIXCACHEID">
|
||||
<span ">${downloadButtonText(zimSize)}</span>
|
||||
</div>`
|
||||
: '';
|
||||
const humanFriendlyTagValue = humanFriendlyTitle(tagValue);
|
||||
const tagMessage = $t("filter-by-tag", {TAG: humanFriendlyTagValue});
|
||||
return `<span class='tag__link' aria-label='${tagMessage}' title='${tagMessage}' data-tag=${tagValue}>${humanFriendlyTagValue}</span>`
|
||||
}
|
||||
|
||||
function generateBookHtml(book, sort = false) {
|
||||
@@ -170,7 +138,7 @@
|
||||
const mulLangList = langCodesList.filter(x => languages.hasOwnProperty(x)).map(x => languages[x]);
|
||||
language = mulLangList.join(', ');
|
||||
}
|
||||
const tags = htmlDecode(getInnerHtml(book, 'tags'));
|
||||
const tags = getInnerHtml(book, 'tags');
|
||||
const tagList = tags.split(';').filter(tag => {return !(tag.startsWith('_'))});
|
||||
const tagFilterLinks = tagList.map((tagValue) => generateTagLink(tagValue));
|
||||
const tagHtml = tagFilterLinks.join(' | ');
|
||||
@@ -196,7 +164,6 @@
|
||||
}
|
||||
const faviconAttr = iconUrl != undefined ? `style="background-image: url('${iconUrl}')"` : '';
|
||||
const languageAttr = langCode != '' ? `title="${language}" aria-label="${language}"` : 'style="background-color: transparent"';
|
||||
|
||||
divTag.innerHTML = `
|
||||
<div class="book__wrapper">
|
||||
<a class="book__link" href="${viewerLink}" data-hover="Preview">
|
||||
@@ -204,15 +171,13 @@
|
||||
<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}">${$t("download")} ${humanFriendlyZimSize ? ` - ${humanFriendlyZimSize}</span></div>`: ''}` : ''}
|
||||
</div>
|
||||
<div class="book__description" title="${description}">${description}</div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="book__meta">
|
||||
<div class="book__languageTag" ${languageAttr}>${getLanguageCodeToDisplay(langCode)}</div>
|
||||
<div class="book__tags"><div class="book__tags--wrapper">${tagHtml}</div></div>
|
||||
</div>
|
||||
${downloadButtonHtml(downloadLink, humanFriendlyZimSize)}
|
||||
</div></div>`;
|
||||
return divTag;
|
||||
}
|
||||
@@ -298,7 +263,6 @@
|
||||
}
|
||||
|
||||
function insertModal(button) {
|
||||
const downloadSize = button.getAttribute('data-size');
|
||||
const downloadLink = button.getAttribute('data-link');
|
||||
button.addEventListener('click', async (event) => {
|
||||
event.preventDefault();
|
||||
@@ -308,7 +272,7 @@
|
||||
<div class="modal-heading">
|
||||
<div class="modal-title">
|
||||
<div>
|
||||
${downloadButtonText(downloadSize)}
|
||||
Download
|
||||
</div>
|
||||
</div>
|
||||
<div onclick="closeModal()" class="modal-close-button">
|
||||
@@ -468,7 +432,7 @@
|
||||
booksToDelete.forEach(book => {iso.remove(book);});
|
||||
books.forEach((book) => {
|
||||
iso.insert(generateBookHtml(book, sort))
|
||||
const downloadButton = document.querySelector(`[data-id="${getInnerHtml(book, 'id')}"] .book__download`);
|
||||
const downloadButton = document.querySelector(`[data-id="${getInnerHtml(book, 'id')}"] .book__download span`);
|
||||
if (downloadButton) {
|
||||
insertModal(downloadButton);
|
||||
}
|
||||
@@ -522,8 +486,9 @@
|
||||
function addTagElement(tagValue, resetFilter) {
|
||||
const tagElement = document.getElementsByClassName('tagFilterLabel')[0];
|
||||
tagElement.style.display = 'inline-block';
|
||||
tagElement.innerHTML = htmlEncode(tagValue);
|
||||
const tagMessage = $t("stop-filtering-by-tag", {TAG: tagValue});
|
||||
const humanFriendlyTagValue = humanFriendlyTitle(tagValue);
|
||||
tagElement.innerHTML = `${humanFriendlyTagValue}`;
|
||||
const tagMessage = $t("stop-filtering-by-tag", {TAG: humanFriendlyTagValue});
|
||||
tagElement.setAttribute('aria-label', tagMessage);
|
||||
tagElement.setAttribute('title', tagMessage);
|
||||
if (resetFilter)
|
||||
|
||||
@@ -7,32 +7,17 @@ const uiLanguages = [
|
||||
{
|
||||
"iso_code": "bn",
|
||||
"self_name": "বাংলা",
|
||||
"translation_count": 14
|
||||
},
|
||||
{
|
||||
"iso_code": "br",
|
||||
"self_name": "brezhoneg",
|
||||
"translation_count": 35
|
||||
"translation_count": 12
|
||||
},
|
||||
{
|
||||
"iso_code": "cs",
|
||||
"self_name": "Čeština",
|
||||
"translation_count": 25
|
||||
},
|
||||
{
|
||||
"iso_code": "dag",
|
||||
"self_name": "Silimiinsili",
|
||||
"translation_count": 48
|
||||
},
|
||||
{
|
||||
"iso_code": "de",
|
||||
"self_name": "Deutsch",
|
||||
"translation_count": 57
|
||||
},
|
||||
{
|
||||
"iso_code": "el",
|
||||
"self_name": "Αγγλικά",
|
||||
"translation_count": 23
|
||||
"translation_count": 49
|
||||
},
|
||||
{
|
||||
"iso_code": "en",
|
||||
@@ -42,27 +27,22 @@ const uiLanguages = [
|
||||
{
|
||||
"iso_code": "es",
|
||||
"self_name": "español",
|
||||
"translation_count": 49
|
||||
"translation_count": 48
|
||||
},
|
||||
{
|
||||
"iso_code": "fi",
|
||||
"self_name": "suomi",
|
||||
"translation_count": 29
|
||||
"translation_count": 22
|
||||
},
|
||||
{
|
||||
"iso_code": "fr",
|
||||
"self_name": "Français",
|
||||
"translation_count": 57
|
||||
},
|
||||
{
|
||||
"iso_code": "ha",
|
||||
"self_name": "Turanci",
|
||||
"translation_count": 57
|
||||
"translation_count": 52
|
||||
},
|
||||
{
|
||||
"iso_code": "he",
|
||||
"self_name": "עברית",
|
||||
"translation_count": 57
|
||||
"translation_count": 52
|
||||
},
|
||||
{
|
||||
"iso_code": "hi",
|
||||
@@ -77,17 +57,12 @@ const uiLanguages = [
|
||||
{
|
||||
"iso_code": "ia",
|
||||
"self_name": "interlingua",
|
||||
"translation_count": 57
|
||||
},
|
||||
{
|
||||
"iso_code": "ig",
|
||||
"self_name": "Bekee",
|
||||
"translation_count": 57
|
||||
"translation_count": 49
|
||||
},
|
||||
{
|
||||
"iso_code": "it",
|
||||
"self_name": "italiano",
|
||||
"translation_count": 38
|
||||
"translation_count": 29
|
||||
},
|
||||
{
|
||||
"iso_code": "ja",
|
||||
@@ -97,7 +72,7 @@ const uiLanguages = [
|
||||
{
|
||||
"iso_code": "ko",
|
||||
"self_name": "한국어",
|
||||
"translation_count": 15
|
||||
"translation_count": 13
|
||||
},
|
||||
{
|
||||
"iso_code": "ku-latn",
|
||||
@@ -112,7 +87,7 @@ const uiLanguages = [
|
||||
{
|
||||
"iso_code": "mk",
|
||||
"self_name": "македонски",
|
||||
"translation_count": 57
|
||||
"translation_count": 52
|
||||
},
|
||||
{
|
||||
"iso_code": "ms",
|
||||
@@ -137,17 +112,12 @@ const uiLanguages = [
|
||||
{
|
||||
"iso_code": "pl",
|
||||
"self_name": "Polski",
|
||||
"translation_count": 31
|
||||
},
|
||||
{
|
||||
"iso_code": "pt-br",
|
||||
"self_name": "Português",
|
||||
"translation_count": 35
|
||||
"translation_count": 24
|
||||
},
|
||||
{
|
||||
"iso_code": "ru",
|
||||
"self_name": "русский",
|
||||
"translation_count": 57
|
||||
"translation_count": 45
|
||||
},
|
||||
{
|
||||
"iso_code": "sc",
|
||||
@@ -167,7 +137,7 @@ const uiLanguages = [
|
||||
{
|
||||
"iso_code": "sl",
|
||||
"self_name": "slovenščina",
|
||||
"translation_count": 57
|
||||
"translation_count": 52
|
||||
},
|
||||
{
|
||||
"iso_code": "sq",
|
||||
@@ -177,12 +147,7 @@ const uiLanguages = [
|
||||
{
|
||||
"iso_code": "sv",
|
||||
"self_name": "Svenska",
|
||||
"translation_count": 57
|
||||
},
|
||||
{
|
||||
"iso_code": "sw",
|
||||
"self_name": "Kiswahili",
|
||||
"translation_count": 57
|
||||
"translation_count": 52
|
||||
},
|
||||
{
|
||||
"iso_code": "te",
|
||||
@@ -192,16 +157,16 @@ const uiLanguages = [
|
||||
{
|
||||
"iso_code": "tr",
|
||||
"self_name": "Türkçe",
|
||||
"translation_count": 57
|
||||
"translation_count": 25
|
||||
},
|
||||
{
|
||||
"iso_code": "zh-hans",
|
||||
"self_name": "简体中文",
|
||||
"translation_count": 57
|
||||
"self_name": "英语",
|
||||
"translation_count": 16
|
||||
},
|
||||
{
|
||||
"iso_code": "zh-hant",
|
||||
"self_name": "繁體中文",
|
||||
"translation_count": 57
|
||||
"translation_count": 52
|
||||
}
|
||||
]
|
||||
@@ -1,21 +0,0 @@
|
||||
// A few browsers do not support the use of String.prototype.replaceAll method.
|
||||
// Hence we define it once we verify that it isn't supported. For documentation
|
||||
// see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replaceAll
|
||||
if (!String.prototype.replaceAll) {
|
||||
String.prototype.replaceAll = function (pattern, replacement) {
|
||||
// verify parameter: It must either be a string or a RegExp with a global flag.
|
||||
if (typeof pattern[Symbol.replace] === 'function') {
|
||||
// the pattern is a RegExp check for the presence of g flag.
|
||||
if (pattern.global) {
|
||||
return this.replace(pattern, replacement);
|
||||
} else {
|
||||
throw new TypeError('Global flag for regular expressions')
|
||||
}
|
||||
}
|
||||
// the pattern is not a RegExp, hence it must be a string.
|
||||
if (typeof pattern !== 'string') {
|
||||
throw new TypeError('pattern must either be a string or a RegExp with a global (g) flag.')
|
||||
}
|
||||
return this.replace(new RegExp(pattern, 'g'), replacement);
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 30ch;
|
||||
max-width: 160px;
|
||||
}
|
||||
|
||||
.kiwix .kiwix_centered {
|
||||
|
||||
@@ -310,12 +310,6 @@ function blockLink(url) {
|
||||
: url;
|
||||
}
|
||||
|
||||
function urlMustBeHandledByAnExternalApp(url) {
|
||||
const WHITELISTED_URL_SCHEMATA = ['http:', 'https:', 'about:', 'javascript:'];
|
||||
|
||||
return WHITELISTED_URL_SCHEMATA.indexOf(url.protocol) == -1;
|
||||
}
|
||||
|
||||
function isExternalUrl(url) {
|
||||
if ( url.startsWith(window.location.origin) )
|
||||
return false;
|
||||
@@ -335,34 +329,20 @@ function getRealHref(target) {
|
||||
return target_href;
|
||||
}
|
||||
|
||||
function setHrefAvoidingWombatRewriting(target, url) {
|
||||
const old_no_rewrite = target._no_rewrite;
|
||||
target._no_rewrite = true;
|
||||
target.setAttribute("href", url);
|
||||
target._no_rewrite = old_no_rewrite;
|
||||
}
|
||||
|
||||
function onClickEvent(e) {
|
||||
const iframeDocument = contentIframe.contentDocument;
|
||||
const target = matchingAncestorElement(e.target, iframeDocument, "a");
|
||||
if (target !== null && "href" in target) {
|
||||
const target_href = getRealHref(target);
|
||||
const target_url = new URL(target_href, iframeDocument.location);
|
||||
const isExternalAppUrl = urlMustBeHandledByAnExternalApp(target_url);
|
||||
if ( isExternalAppUrl && !viewerSettings.linkBlockingEnabled ) {
|
||||
target.setAttribute("target", "_blank");
|
||||
}
|
||||
|
||||
if (isExternalAppUrl || isExternalUrl(target_href)) {
|
||||
if (isExternalUrl(target_href)) {
|
||||
const possiblyBlockedLink = blockLink(target_href);
|
||||
if ( e.ctrlKey || e.shiftKey ) {
|
||||
// The link will be loaded in a new tab/window - update the link
|
||||
// and let the browser handle the rest.
|
||||
setHrefAvoidingWombatRewriting(target, possiblyBlockedLink);
|
||||
target.setAttribute("href", possiblyBlockedLink);
|
||||
} else {
|
||||
// Load the external URL in the viewer window (rather than iframe)
|
||||
contentIframe.contentWindow.parent.location = possiblyBlockedLink;
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
id="headFeedLink"
|
||||
href="{{root}}/catalog/v2/entries"
|
||||
/>
|
||||
<link rel="search" type="application/opensearchdescription+xml" href="{{root}}/catalog/searchdescription.xml" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{root}}/skin/favicon/apple-touch-icon.png?KIWIXCACHEID">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{{root}}/skin/favicon/favicon-32x32.png?KIWIXCACHEID">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{{root}}/skin/favicon/favicon-16x16.png?KIWIXCACHEID">
|
||||
@@ -32,7 +31,6 @@
|
||||
<meta name="msapplication-TileColor" content="#da532c">
|
||||
<meta name="msapplication-config" content="{{root}}/skin/favicon/browserconfig.xml?KIWIXCACHEID">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<script type="text/javascript" src="./skin/polyfills.js?KIWIXCACHEID"></script>
|
||||
<script type="text/javascript" src="./viewer_settings.js"></script>
|
||||
<script type="module" src="{{root}}/skin/i18n.js?KIWIXCACHEID" defer></script>
|
||||
<script type="text/javascript" src="{{root}}/skin/languages.js?KIWIXCACHEID" defer></script>
|
||||
@@ -93,7 +91,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<form id='kiwixSearchForm' class='kiwixNav__SearchForm'>
|
||||
<input type="text" name="q" accesskey="s" placeholder="Search" id="searchFilter" class='kiwixSearch filter'>
|
||||
<input type="text" name="q" placeholder="Search" id="searchFilter" class='kiwixSearch filter'>
|
||||
<span class="kiwixButton tagFilterLabel"></span>
|
||||
<input type="submit" class="kiwixButton kiwixButtonHover" id="searchButton" value="Search"/>
|
||||
</form>
|
||||
|
||||
@@ -50,11 +50,20 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.book__link__wrapper {
|
||||
grid-column: 1 / 3;
|
||||
grid-row: 1 / 3;
|
||||
}
|
||||
|
||||
.book__link {
|
||||
grid-row: 2 / 3;
|
||||
}
|
||||
|
||||
.kiwixHomeBody__results {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
#book__title>a {
|
||||
#book__title>a, .book__download a {
|
||||
text-decoration: none;
|
||||
all: unset;
|
||||
}
|
||||
@@ -81,7 +90,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<form id='kiwixSearchForm' class='kiwixNav__SearchForm' action="{{root}}/nojs">
|
||||
<input type="text" name="q" accesskey="s" placeholder="{{translations.search}}" id="searchFilter" class='kiwixSearch filter' value="{{searchQuery}}">
|
||||
<input type="text" name="q" placeholder="{{translations.search}}" id="searchFilter" class='kiwixSearch filter' value="{{searchQuery}}">
|
||||
<input type="submit" class="kiwixButton kiwixButtonHover" value="{{translations.search}}"/>
|
||||
</form>
|
||||
</div>
|
||||
@@ -108,32 +117,25 @@
|
||||
<h3 class="kiwixHomeBody__results">{{translations.count-of-matching-books}}</h3>
|
||||
{{#books}}
|
||||
<div class="book__wrapper">
|
||||
<a class="book__link" href="{{root}}/content/{{id}}" title="{{translations.preview-book}}" aria-label="{{translations.preview-book}}">
|
||||
<div class="book__link__wrapper">
|
||||
<div class="book__icon" {{faviconAttr}}></div>
|
||||
<div class="book__header">
|
||||
<div id="book__title">{{title}}</div>
|
||||
</div>
|
||||
<div class="book__description" title="{{description}}">{{description}}</div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="book__meta">
|
||||
<div class="book__languageTag" {{languageAttr}}>{{langCode}}</div>
|
||||
<div class="book__tags"><div class="book__tags--wrapper">
|
||||
{{#tagList}}
|
||||
<span class="tag__link" aria-label='{{tag}}' title='{{tag}}'>{{tag}}</span>
|
||||
{{/tagList}}
|
||||
</div>
|
||||
<div class="book__link__wrapper">
|
||||
<div class="book__icon" {{faviconAttr}}></div>
|
||||
<div class="book__header">
|
||||
<div id="book__title"><a href="{{root}}/content/{{id}}">{{title}}</a></div>
|
||||
{{#downloadAvailable}}
|
||||
<div class="book__download"><span><a href="{{root}}/nojs/download/{{id}}">{{translations.download}}</a></span></div>
|
||||
{{/downloadAvailable}}
|
||||
</div>
|
||||
<a class="book__link" href="{{root}}/content/{{id}}" title="{{translations.preview-book}}" aria-label="{{translations.preview-book}}">
|
||||
<div class="book__description" title="{{description}}">{{description}}</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="book__languageTag" {{languageAttr}}>{{langCode}}</div>
|
||||
<div class="book__tags"><div class="book__tags--wrapper">
|
||||
{{#tagList}}
|
||||
<span class="tag__link" aria-label='{{tag}}' title='{{tag}}'>{{tag}}</span>
|
||||
{{/tagList}}
|
||||
</div>
|
||||
{{#downloadAvailable}}
|
||||
<div>
|
||||
<a class="book__download" href="{{root}}/nojs/download/{{id}}">
|
||||
<img src="{{root}}/skin/download-white.svg?KIWIXCACHEID">
|
||||
<span>{{translations.download}}</span>
|
||||
</a>
|
||||
</div>
|
||||
{{/downloadAvailable}}
|
||||
</div>
|
||||
</div>
|
||||
{{/books}}
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
<link type="text/css" href="./skin/kiwix.css?KIWIXCACHEID" rel="Stylesheet" />
|
||||
<link type="text/css" href="./skin/taskbar.css?KIWIXCACHEID" rel="Stylesheet" />
|
||||
<link type="text/css" href="./skin/autoComplete/css/autoComplete.css?KIWIXCACHEID" rel="Stylesheet" />
|
||||
<script type="text/javascript" src="./skin/polyfills.js?KIWIXCACHEID"></script>
|
||||
<script type="text/javascript" src="./viewer_settings.js"></script>
|
||||
<script type="module" src="./skin/i18n.js?KIWIXCACHEID" defer></script>
|
||||
<script type="text/javascript" src="./skin/languages.js?KIWIXCACHEID" defer></script>
|
||||
@@ -45,22 +44,14 @@
|
||||
<input type="checkbox" id="kiwix_button_show_toggle">
|
||||
<label for="kiwix_button_show_toggle"><img src="./skin/caret.png?KIWIXCACHEID" alt=""></label>
|
||||
<div class="kiwix_button_cont">
|
||||
<a id="kiwix_serve_taskbar_library_button"
|
||||
title="Go to welcome page"
|
||||
accesskey="w"
|
||||
aria-label="Go to welcome page"
|
||||
href="./">
|
||||
<button>🏠</button>
|
||||
</a>
|
||||
<a id="kiwix_serve_taskbar_library_button" title="Go to welcome page" aria-label="Go to welcome page" href="./"><button>🏠</button></a>
|
||||
<span id="kiwix_serve_taskbar_book_ui_group">
|
||||
<a id="kiwix_serve_taskbar_home_button"
|
||||
title="Go to the main page of the current book"
|
||||
accesskey="h"
|
||||
aria-label="Go to the main page of the current book"
|
||||
onclick="gotoMainPageOfCurrentBook()"></a>
|
||||
<a id="kiwix_serve_taskbar_random_button"
|
||||
title="Go to a randomly selected page"
|
||||
accesskey="r"
|
||||
aria-label="Go to a randomly selected page"
|
||||
onclick="gotoRandomPage()">
|
||||
<button>🎲</button>
|
||||
|
||||
@@ -15,19 +15,11 @@ struct XMLDoc : pugi::xml_document
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
#if _WIN32
|
||||
# define DATA_ABS_PATH "C:\\data\\zim"
|
||||
# define ZARA_ABS_PATH "C:\\data\\zim\\zara.zim"
|
||||
#else
|
||||
# define DATA_ABS_PATH "/data/zim"
|
||||
# define ZARA_ABS_PATH "/data/zim/zara.zim"
|
||||
#endif
|
||||
|
||||
TEST(BookTest, updateFromXMLTest)
|
||||
{
|
||||
const XMLDoc xml(R"(
|
||||
<book id="zara"
|
||||
path="zara.zim"
|
||||
path="./zara.zim"
|
||||
url="https://who.org/zara.zim"
|
||||
title="Catch an infection in 24 hours"
|
||||
description="Complete guide to contagious diseases"
|
||||
@@ -48,9 +40,9 @@ TEST(BookTest, updateFromXMLTest)
|
||||
)");
|
||||
|
||||
kiwix::Book book;
|
||||
book.updateFromXml(xml.child("book"), DATA_ABS_PATH);
|
||||
book.updateFromXml(xml.child("book"), "/data/zim");
|
||||
|
||||
EXPECT_EQ(book.getPath(), ZARA_ABS_PATH);
|
||||
EXPECT_EQ(book.getPath(), "/data/zim/zara.zim");
|
||||
EXPECT_EQ(book.getUrl(), "https://who.org/zara.zim");
|
||||
EXPECT_EQ(book.getTitle(), "Catch an infection in 24 hours");
|
||||
EXPECT_EQ(book.getDescription(), "Complete guide to contagious diseases");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<library version="20110515">
|
||||
<book id="5dc0b3af-5df2-0925-f0ca-d2bf75e78af6" path="example.zim" title="Wikibooks" description="testZim" language="eng" creator="test" publisher="test" name="bookname_of_example_zim" tags="_ftindex:yes;_ftindex:yes;_pictures:yes;_videos:yes;_details:yes" date="2021-04-17" mediaCount="22" size="253" />
|
||||
<book id="5dc0b3af-5df2-0925-f0ca-d2bf75e78af6" path="example.zim" title="Wikibooks" description="testZim" language="eng" creator="test" publisher="test" tags="_ftindex:yes;_ftindex:yes;_pictures:yes;_videos:yes;_details:yes" date="2021-04-17" mediaCount="22" size="253" />
|
||||
<book id="6f1d19d0-633f-087b-fb55-7ac324ff9baf" path="zimfile.zim" title="Ray Charles" description="Wikipedia articles about Ray Charles" language="eng" creator="Wikipedia" publisher="Kiwix" name="wikipedia_en_ray_charles" flavour="_mini" tags="wikipedia;_category:wikipedia;_pictures:no;_videos:no;_details:no;_ftindex:yes" date="2020-03-31" articleCount="129" mediaCount="45" size="555" />
|
||||
</library>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<library version="1.0">
|
||||
<book
|
||||
id="raycharles"
|
||||
path="./zimfile_raycharles.zim"
|
||||
url="https://github.com/kiwix/libkiwix/raw/master/test/data/zimfile_raycharles.zim"
|
||||
path="./zimfile.zim"
|
||||
url="https://github.com/kiwix/libkiwix/raw/master/test/data/zimfile.zim"
|
||||
title="Ray Charles"
|
||||
description="Wikipedia articles about Ray Charles"
|
||||
language="eng"
|
||||
@@ -19,8 +19,8 @@
|
||||
></book>
|
||||
<book
|
||||
id="raycharles_uncategorized"
|
||||
path="./zimfile_raycharles_uncategorized.zim"
|
||||
url="https://github.com/kiwix/libkiwix/raw/master/test/data/zimfile_raycharles_uncategorized.zim"
|
||||
path="./zimfile.zim"
|
||||
url="https://github.com/kiwix/libkiwix/raw/master/test/data/zimfile.zim"
|
||||
title="Ray (uncategorized) Charles"
|
||||
description="No category is assigned to this library entry."
|
||||
language="rus,eng"
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
zimfile.zim
|
||||
@@ -1 +0,0 @@
|
||||
zimfile.zim
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "../src/server/i18n_utils.h"
|
||||
#include "../src/server/i18n.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
using namespace kiwix;
|
||||
@@ -48,17 +48,3 @@ TEST(ParameterizedMessage, messagesWithParameters)
|
||||
EXPECT_EQ(msg.getText("test"), "Filter [I18N] by [TESTING] tag \"\"");
|
||||
}
|
||||
}
|
||||
|
||||
TEST(I18n, translateBookCategory)
|
||||
{
|
||||
|
||||
EXPECT_EQ(translateBookCategory("en", "ted"), "Ted");
|
||||
EXPECT_EQ(translateBookCategory("test", "ted"), "[I18N] Ted [TESTING]");
|
||||
|
||||
EXPECT_EQ(translateBookCategory("en", "stack_exchange"), "Stack Exchange");
|
||||
EXPECT_EQ(translateBookCategory("test", "stack_exchange"), "[I18N] Stack Exchange [TESTING]");
|
||||
|
||||
// unknown categories are simply not translated
|
||||
EXPECT_EQ(translateBookCategory("en", "Qwerty"), "Qwerty");
|
||||
EXPECT_EQ(translateBookCategory("test", "Qwerty"), "Qwerty");
|
||||
}
|
||||
|
||||
505
test/library.cpp
505
test/library.cpp
@@ -20,6 +20,7 @@
|
||||
#include "gtest/gtest.h"
|
||||
#include <string>
|
||||
|
||||
|
||||
const char * sampleOpdsStream = R"(
|
||||
<feed xmlns="http://www.w3.org/2005/Atom"
|
||||
xmlns:dc="http://purl.org/dc/terms/"
|
||||
@@ -27,12 +28,12 @@ const char * sampleOpdsStream = R"(
|
||||
<id>00000000-0000-0000-0000-000000000000</id>
|
||||
<entry>
|
||||
<title>Encyclopédie de la Tunisie</title>
|
||||
<name>wikipedia_fr_tunisie</name>
|
||||
<flavour>novid</flavour>
|
||||
<name>wikipedia_fr_tunisie_novid_2018-10</name>
|
||||
<flavour>unforgettable</flavour>
|
||||
<id>urn:uuid:0c45160e-f917-760a-9159-dfe3c53cdcdd</id>
|
||||
<icon>/meta?name=favicon&content=wikipedia_fr_tunisie_novid_2018-10</icon>
|
||||
<updated>2018-10-08T00:00::00:Z</updated>
|
||||
<dc:issued>2018-10-08T00:00::00:Z</dc:issued>
|
||||
<dc:issued>8 Oct 2018</dc:issued>
|
||||
<language>fra</language>
|
||||
<summary>Le meilleur de Wikipédia sur la Tunisie</summary>
|
||||
<tags>wikipedia;novid;_ftindex</tags>
|
||||
@@ -48,53 +49,9 @@ const char * sampleOpdsStream = R"(
|
||||
<mediaCount>1100</mediaCount>
|
||||
<articleCount>172</articleCount>
|
||||
</entry>
|
||||
<entry>
|
||||
<title>Encyclopédie de la Tunisie</title>
|
||||
<name>wikipedia_fr_tunisie</name>
|
||||
<flavour>novid</flavour>
|
||||
<id>urn:uuid:0c45160e-f917-760a-9159-dfe3c53cdcdd_updated1yearlater</id>
|
||||
<updated>2019-10-08T00:00::00:Z</updated>
|
||||
<dc:issued>2019-10-08T00:00::00:Z</dc:issued>
|
||||
<language>fra</language>
|
||||
<summary>Le meilleur de Wikipédia sur la Tunisie. Updated in 2019</summary>
|
||||
<author>
|
||||
<name>Wikipedia</name>
|
||||
</author>
|
||||
<link rel="http://opds-spec.org/acquisition/open-access" type="application/x-zim" href="http://download.kiwix.org/zim/wikipedia/wikipedia_fr_tunisie_novid_2018-10.zim.meta4" length="90030080" />
|
||||
</entry>
|
||||
<entry>
|
||||
<title>Encyclopédie de la Tunisie</title>
|
||||
<name>wikipedia_fr_tunisie</name>
|
||||
<flavour>other_flavour</flavour>
|
||||
<id>urn:uuid:0c45160e-f917-760a-9159-dfe3c53cdcdd_flavour</id>
|
||||
<updated>2018-10-08T00:00::00:Z</updated>
|
||||
<dc:issued>2018-10-08T00:00::00:Z</dc:issued>
|
||||
<language>fra</language>
|
||||
<summary>Le meilleur de Wikipédia sur la Tunisie. With another flavour</summary>
|
||||
<author>
|
||||
<name>Wikipedia</name>
|
||||
</author>
|
||||
<link rel="http://opds-spec.org/acquisition/open-access" type="application/x-zim" href="http://download.kiwix.org/zim/wikipedia/wikipedia_fr_tunisie_novid_2018-10.zim.meta4" length="90030080" />
|
||||
</entry>
|
||||
<entry>
|
||||
<title>Encyclopédie de la Tunisie</title>
|
||||
<name>wikipedia_fr_tunisie</name>
|
||||
<flavour>other_flavour</flavour>
|
||||
<id>urn:uuid:0c45160e-f917-760a-9159-dfe3c53cdcdd_updated1yearlater_flavour</id>
|
||||
<updated>2019-10-08T00:00::00:Z</updated>
|
||||
<dc:issued>2019-10-08T00:00::00:Z</dc:issued>
|
||||
<language>fra</language>
|
||||
<summary>Le meilleur de Wikipédia sur la Tunisie. Updated in 2019, and other flavour</summary>
|
||||
<author>
|
||||
<name>Wikipedia</name>
|
||||
</author>
|
||||
<link rel="http://opds-spec.org/acquisition/open-access" type="application/x-zim" href="http://download.kiwix.org/zim/wikipedia/wikipedia_fr_tunisie_novid_2018-10.zim.meta4" length="90030080" />
|
||||
</entry>
|
||||
<entry>
|
||||
<title>Tania Louis</title>
|
||||
<id>urn:uuid:0d0bcd57-d3f6-cb22-44cc-a723ccb4e1b2</id>
|
||||
<name>biologie-tout-compris_fr_all</name>
|
||||
<flavour>full</flavour>
|
||||
<icon>/meta?name=favicon&content=biologie-tout-compris_fr_all_2018-06</icon>
|
||||
<updated>2018-06-23T00:00::00:Z</updated>
|
||||
<language>fra</language>
|
||||
@@ -110,8 +67,6 @@ const char * sampleOpdsStream = R"(
|
||||
<entry>
|
||||
<title>Wikiquote</title>
|
||||
<id>urn:uuid:0ea1cde6-441d-6c58-f2c7-21c2838e659f</id>
|
||||
<name>wikiquote_fr_all</name>
|
||||
<flavour>full</flavour>
|
||||
<icon>/meta?name=favicon&content=wikiquote_fr_all_nopic_2019-06</icon>
|
||||
<updated>2019-06-05T00:00::00:Z</updated>
|
||||
<language>fra,ita</language>
|
||||
@@ -128,8 +83,6 @@ const char * sampleOpdsStream = R"(
|
||||
<entry>
|
||||
<title>Géographie par Wikipédia</title>
|
||||
<id>urn:uuid:1123e574-6eef-6d54-28fc-13e4caeae474</id>
|
||||
<name>wikipedia_fr_geography</name>
|
||||
<flavour>full</flavour>
|
||||
<icon>/meta?name=favicon&content=wikipedia_fr_geography_nopic_2019-06</icon>
|
||||
<updated>2019-06-02T00:00::00:Z</updated>
|
||||
<summary>Une sélection d'articles de Wikipédia sur la géographie</summary>
|
||||
@@ -146,8 +99,6 @@ const char * sampleOpdsStream = R"(
|
||||
<entry>
|
||||
<title>Mathématiques</title>
|
||||
<id>urn:uuid:14829621-c490-c376-0792-9de558b57efa</id>
|
||||
<name>wikipedia_fr_mathematics</name>
|
||||
<flavour>novid</flavour>
|
||||
<icon>/meta?name=favicon&content=wikipedia_fr_mathematics_nopic_2019-05</icon>
|
||||
<updated>2019-05-13T00:00::00:Z</updated>
|
||||
<language>fra</language>
|
||||
@@ -164,8 +115,6 @@ const char * sampleOpdsStream = R"(
|
||||
<entry>
|
||||
<title>Granblue Fantasy Wiki</title>
|
||||
<id>urn:uuid:006cbd1b-16d8-b00d-a584-c1ae110a94ed</id>
|
||||
<name>grandbluefantasy_en_all</name>
|
||||
<flavour>novid</flavour>
|
||||
<icon>/meta?name=favicon&content=granbluefantasy_en_all_all_nopic_2018-10</icon>
|
||||
<updated>2018-10-14T00:00::00:Z</updated>
|
||||
<language>eng</language>
|
||||
@@ -181,8 +130,6 @@ const char * sampleOpdsStream = R"(
|
||||
<entry>
|
||||
<title>Movies & TV Stack Exchange</title>
|
||||
<id>urn:uuid:00f37b00-f4da-0675-995a-770f9c72903e</id>
|
||||
<name>movies.stackexchange.com_en_all</name>
|
||||
<flavour>novid</flavour>
|
||||
<icon>/meta?name=favicon&content=movies.stackexchange.com_en_all_2019-02</icon>
|
||||
<updated>2019-02-03T00:00::00:Z</updated>
|
||||
<language>eng</language>
|
||||
@@ -196,10 +143,8 @@ const char * sampleOpdsStream = R"(
|
||||
<link rel="http://opds-spec.org/image/thumbnail" type="image/png" href="/meta?name=favicon&content=movies.stackexchange.com_en_all_2019-02" />
|
||||
</entry>
|
||||
<entry>
|
||||
<title>TED"talks" - Business</title>
|
||||
<title>TED talks - Business</title>
|
||||
<id>urn:uuid:0189d9be-2fd0-b4b6-7300-20fab0b5cdc8</id>
|
||||
<name>ted_en_business</name>
|
||||
<flavour>nodet</flavour>
|
||||
<icon>/meta?name=favicon&content=ted_en_business_2018-07</icon>
|
||||
<updated>2018-07-23T00:00::00:Z</updated>
|
||||
<language>eng</language>
|
||||
@@ -212,28 +157,9 @@ const char * sampleOpdsStream = R"(
|
||||
<link rel="http://opds-spec.org/acquisition/open-access" type="application/x-zim" href="http://download.kiwix.org/zim/ted/ted_en_business_2018-07.zim.meta4" length="8855827456" />
|
||||
<link rel="http://opds-spec.org/image/thumbnail" type="image/png" href="/meta?name=favicon&content=ted_en_business_2018-07" />
|
||||
</entry>
|
||||
<entry>
|
||||
<title>Business talks about TED</title>
|
||||
<id>Dummy id </id>
|
||||
<name>speak_business</name>
|
||||
<flavour>nodet</flavour>
|
||||
<icon>/meta?name=favicon&content=ted_en_business_2018-07</icon>
|
||||
<updated>2018-08-23T00:00::00:Z</updated>
|
||||
<language>eng</language>
|
||||
<summary>Ideas worth spreading</summary>
|
||||
<tags></tags>
|
||||
<link type="text/html" href="/ted_en_business_2018-07" />
|
||||
<author>
|
||||
<name>TED</name>
|
||||
</author>
|
||||
<link rel="http://opds-spec.org/acquisition/open-access" type="application/x-zim" href="http://download.kiwix.org/zim/ted/ted_en_business_2018-07.zim.meta4" length="8855827456" />
|
||||
<link rel="http://opds-spec.org/image/thumbnail" type="image/png" href="/meta?name=favicon&content=ted_en_business_2018-07" />
|
||||
</entry>
|
||||
<entry>
|
||||
<title>Mythology & Folklore Stack Exchange</title>
|
||||
<id>urn:uuid:028055ac-4acc-1d54-65e0-a96de45e1b22</id>
|
||||
<name>mythology.stackexchange.com_en_all</name>
|
||||
<flavour>novid</flavour>
|
||||
<icon>/meta?name=favicon&content=mythology.stackexchange.com_en_all_2019-02</icon>
|
||||
<updated>2019-02-03T00:00::00:Z</updated>
|
||||
<language>eng</language>
|
||||
@@ -249,8 +175,6 @@ const char * sampleOpdsStream = R"(
|
||||
<entry>
|
||||
<title>Islam Stack Exchange</title>
|
||||
<id>urn:uuid:02e9c7ff-36fc-9c6e-6ac7-cd7085989029</id>
|
||||
<name>islam.stackexchange.com_en_all</name>
|
||||
<flavour>novid</flavour>
|
||||
<icon>/meta?name=favicon&content=islam.stackexchange.com_en_all_2019-01</icon>
|
||||
<updated>2019-01-31T00:00::00:Z</updated>
|
||||
<language>eng</language>
|
||||
@@ -267,21 +191,11 @@ const char * sampleOpdsStream = R"(
|
||||
|
||||
)";
|
||||
|
||||
#ifdef _WIN32
|
||||
# define ZIMFILE_PATH ".\\zimfile.zim"
|
||||
# define EXAMPLE_PATH ".\\example.zim"
|
||||
# define LIBRARY_PATH ".\\test\\library.xml"
|
||||
#else
|
||||
# define ZIMFILE_PATH "./zimfile.zim"
|
||||
# define EXAMPLE_PATH "./example.zim"
|
||||
# define LIBRARY_PATH "./test/library.xml"
|
||||
#endif
|
||||
|
||||
const char sampleLibraryXML[] = R"(
|
||||
<library version="1.0">
|
||||
<book
|
||||
id="raycharles"
|
||||
path=")" ZIMFILE_PATH R"("
|
||||
path="./zimfile.zim"
|
||||
url="https://github.com/kiwix/libkiwix/raw/master/test/data/zimfile.zim"
|
||||
title="Ray Charles"
|
||||
description="Wikipedia articles about Ray Charles"
|
||||
@@ -297,7 +211,7 @@ const char sampleLibraryXML[] = R"(
|
||||
></book>
|
||||
<book
|
||||
id="example"
|
||||
path=")" EXAMPLE_PATH R"("
|
||||
path="./example.zim"
|
||||
title="An example ZIM archive"
|
||||
description="An eXaMpLe book added to the catalog via XML"
|
||||
language="deu"
|
||||
@@ -315,7 +229,6 @@ const char sampleLibraryXML[] = R"(
|
||||
|
||||
#include "../include/library.h"
|
||||
#include "../include/manager.h"
|
||||
#include "../include/book.h"
|
||||
#include "../include/bookmark.h"
|
||||
|
||||
namespace
|
||||
@@ -329,17 +242,17 @@ TEST(LibraryOpdsImportTest, allInOne)
|
||||
kiwix::Manager manager(lib);
|
||||
manager.readOpds(sampleOpdsStream, "library-opds-import.unittests.dev");
|
||||
|
||||
EXPECT_EQ(14U, lib->getBookCount(true, true));
|
||||
EXPECT_EQ(10U, lib->getBookCount(true, true));
|
||||
|
||||
{
|
||||
const kiwix::Book& book1 = lib->getBookById("0c45160e-f917-760a-9159-dfe3c53cdcdd");
|
||||
|
||||
EXPECT_EQ(book1.getTitle(), "Encyclopédie de la Tunisie");
|
||||
EXPECT_EQ(book1.getName(), "wikipedia_fr_tunisie");
|
||||
EXPECT_EQ(book1.getFlavour(), "novid");
|
||||
EXPECT_EQ(book1.getName(), "wikipedia_fr_tunisie_novid_2018-10");
|
||||
EXPECT_EQ(book1.getFlavour(), "unforgettable");
|
||||
EXPECT_EQ(book1.getLanguages(), Langs{ "fra" });
|
||||
EXPECT_EQ(book1.getCommaSeparatedLanguages(), "fra");
|
||||
EXPECT_EQ(book1.getDate(), "2018-10-08");
|
||||
EXPECT_EQ(book1.getDate(), "8 Oct 2018");
|
||||
EXPECT_EQ(book1.getDescription(), "Le meilleur de Wikipédia sur la Tunisie");
|
||||
EXPECT_EQ(book1.getCreator(), "Wikipedia");
|
||||
EXPECT_EQ(book1.getPublisher(), "Wikipedia Publishing House");
|
||||
@@ -359,9 +272,9 @@ TEST(LibraryOpdsImportTest, allInOne)
|
||||
|
||||
{
|
||||
const kiwix::Book& book2 = lib->getBookById("0189d9be-2fd0-b4b6-7300-20fab0b5cdc8");
|
||||
EXPECT_EQ(book2.getTitle(), "TED\"talks\" - Business");
|
||||
EXPECT_EQ(book2.getName(), "ted_en_business");
|
||||
EXPECT_EQ(book2.getFlavour(), "nodet");
|
||||
EXPECT_EQ(book2.getTitle(), "TED talks - Business");
|
||||
EXPECT_EQ(book2.getName(), "");
|
||||
EXPECT_EQ(book2.getFlavour(), "");
|
||||
EXPECT_EQ(book2.getLanguages(), Langs{ "eng" });
|
||||
EXPECT_EQ(book2.getCommaSeparatedLanguages(), "eng");
|
||||
EXPECT_EQ(book2.getDate(), "2018-07-23");
|
||||
@@ -393,21 +306,14 @@ class LibraryTest : public ::testing::Test {
|
||||
void SetUp() override {
|
||||
kiwix::Manager manager(lib);
|
||||
manager.readOpds(sampleOpdsStream, "foo.urlHost");
|
||||
manager.readXml(sampleLibraryXML, false, LIBRARY_PATH, true);
|
||||
manager.readXml(sampleLibraryXML, false, "./test/library.xml", true);
|
||||
}
|
||||
|
||||
kiwix::Bookmark createBookmark(const std::string &id, const std::string& url="", const std::string& title="") {
|
||||
kiwix::Bookmark bookmark;
|
||||
bookmark.setBookId(id);
|
||||
bookmark.setUrl(url);
|
||||
bookmark.setTitle(title);
|
||||
return bookmark;
|
||||
};
|
||||
|
||||
kiwix::Bookmark createBookmark(const kiwix::Book& book, const std::string& url="", const std::string& title="") {
|
||||
kiwix::Bookmark bookmark(book, url, title);
|
||||
return bookmark;
|
||||
};
|
||||
kiwix::Bookmark createBookmark(const std::string &id) {
|
||||
kiwix::Bookmark bookmark;
|
||||
bookmark.setBookId(id);
|
||||
return bookmark;
|
||||
};
|
||||
|
||||
TitleCollection ids2Titles(const BookIdCollection& ids) {
|
||||
TitleCollection titles;
|
||||
@@ -421,320 +327,28 @@ class LibraryTest : public ::testing::Test {
|
||||
std::shared_ptr<kiwix::Library> lib;
|
||||
};
|
||||
|
||||
TEST_F(LibraryTest, createBookMark)
|
||||
{
|
||||
auto bookId = "0c45160e-f917-760a-9159-dfe3c53cdcdd";
|
||||
auto book = lib->getBookById(bookId);
|
||||
|
||||
auto bookmark = createBookmark(book, "/a/url", "A title");
|
||||
|
||||
EXPECT_EQ(bookmark.getUrl(), "/a/url");
|
||||
EXPECT_EQ(bookmark.getTitle(), "A title");
|
||||
EXPECT_EQ(bookmark.getBookId(), bookId);
|
||||
EXPECT_EQ(bookmark.getBookName(), book.getName());
|
||||
EXPECT_EQ(bookmark.getBookName(), "wikipedia_fr_tunisie");
|
||||
EXPECT_EQ(bookmark.getBookTitle(), book.getTitle());
|
||||
EXPECT_EQ(bookmark.getDate(), book.getDate());
|
||||
EXPECT_EQ(bookmark.getBookFlavour(), book.getFlavour());
|
||||
EXPECT_EQ(bookmark.getLanguage(), book.getCommaSeparatedLanguages());
|
||||
}
|
||||
|
||||
TEST_F(LibraryTest, getBookMarksTest)
|
||||
{
|
||||
auto bookId1 = "0c45160e-f917-760a-9159-dfe3c53cdcdd";
|
||||
auto bookId2 = "0189d9be-2fd0-b4b6-7300-20fab0b5cdc8";
|
||||
|
||||
auto book1 = lib->getBookById(bookId1);
|
||||
auto book2 = lib->getBookById(bookId2);
|
||||
|
||||
lib->addBookmark(createBookmark(book1));
|
||||
lib->addBookmark(createBookmark("invalid-book-id"));
|
||||
lib->addBookmark(createBookmark(book2));
|
||||
auto onlyValidBookmarks = lib->getBookmarks();
|
||||
auto allBookmarks = lib->getBookmarks(false);
|
||||
|
||||
EXPECT_EQ(onlyValidBookmarks[0].getBookId(), bookId1);
|
||||
EXPECT_EQ(onlyValidBookmarks[1].getBookId(), bookId2);
|
||||
|
||||
EXPECT_EQ(allBookmarks[0].getBookId(), bookId1);
|
||||
EXPECT_EQ(allBookmarks[1].getBookId(), "invalid-book-id");
|
||||
EXPECT_EQ(allBookmarks[2].getBookId(), bookId2);
|
||||
}
|
||||
|
||||
TEST_F(LibraryTest, bookmarksSerializationTest)
|
||||
{
|
||||
auto bookId1 = lib->getBooksIds()[0];
|
||||
auto bookId2 = lib->getBooksIds()[1];
|
||||
|
||||
auto book1 = lib->getBookById(bookId1);
|
||||
auto book2 = lib->getBookById(bookId2);
|
||||
|
||||
// Create bookmarks using three different ways.
|
||||
lib->addBookmark(createBookmark(bookId1, "a/url", "Article title1"));
|
||||
lib->addBookmark(createBookmark("invalid-book-id", "another/url", "Unknown title"));
|
||||
lib->addBookmark(createBookmark(book2, "a/url/2", "Article title2"));
|
||||
|
||||
lib->writeBookmarksToFile("__test__bookmarks.xml");
|
||||
|
||||
// Build a new library
|
||||
auto new_lib = kiwix::Library::create();
|
||||
{
|
||||
kiwix::Manager manager(new_lib);
|
||||
manager.readOpds(sampleOpdsStream, "foo.urlHost");
|
||||
manager.readXml(sampleLibraryXML, false, "./test/library.xml", true);
|
||||
manager.readBookmarkFile("__test__bookmarks.xml");
|
||||
}
|
||||
std::remove("__test__bookmarks.xml");
|
||||
|
||||
auto onlyValidBookmarks = new_lib->getBookmarks();
|
||||
auto allBookmarks = new_lib->getBookmarks(false);
|
||||
|
||||
ASSERT_EQ(onlyValidBookmarks.size(), 2U);
|
||||
EXPECT_EQ(onlyValidBookmarks[0].getBookId(), bookId1);
|
||||
EXPECT_EQ(onlyValidBookmarks[1].getBookId(), bookId2);
|
||||
|
||||
ASSERT_EQ(allBookmarks.size(), 3U);
|
||||
auto bookmark1 = allBookmarks[0];
|
||||
EXPECT_EQ(bookmark1.getBookId(), bookId1);
|
||||
EXPECT_EQ(bookmark1.getBookTitle(), book1.getTitle());
|
||||
EXPECT_EQ(bookmark1.getBookName(), book1.getName());
|
||||
EXPECT_EQ(bookmark1.getBookFlavour(), book1.getFlavour());
|
||||
EXPECT_EQ(bookmark1.getUrl(), "a/url");
|
||||
EXPECT_EQ(bookmark1.getTitle(), "Article title1");
|
||||
EXPECT_EQ(bookmark1.getLanguage(), book1.getCommaSeparatedLanguages());
|
||||
EXPECT_EQ(bookmark1.getDate(), book1.getDate());
|
||||
|
||||
auto bookmark2 = allBookmarks[1];
|
||||
EXPECT_EQ(bookmark2.getBookId(), "invalid-book-id");
|
||||
EXPECT_EQ(bookmark2.getBookTitle(), "");
|
||||
EXPECT_EQ(bookmark2.getBookName(), "");
|
||||
EXPECT_EQ(bookmark2.getBookFlavour(), "");
|
||||
EXPECT_EQ(bookmark2.getUrl(), "another/url");
|
||||
EXPECT_EQ(bookmark2.getTitle(), "Unknown title");
|
||||
EXPECT_EQ(bookmark2.getLanguage(), "");
|
||||
EXPECT_EQ(bookmark2.getDate(), "");
|
||||
|
||||
auto bookmark3 = allBookmarks[2];
|
||||
EXPECT_EQ(bookmark3.getBookId(), bookId2);
|
||||
EXPECT_EQ(bookmark3.getBookTitle(), book2.getTitle());
|
||||
EXPECT_EQ(bookmark3.getBookName(), book2.getName());
|
||||
EXPECT_EQ(bookmark3.getBookFlavour(), book2.getFlavour());
|
||||
EXPECT_EQ(bookmark3.getUrl(), "a/url/2");
|
||||
EXPECT_EQ(bookmark3.getTitle(), "Article title2");
|
||||
EXPECT_EQ(bookmark3.getLanguage(), book2.getCommaSeparatedLanguages());
|
||||
EXPECT_EQ(bookmark3.getDate(), book2.getDate());
|
||||
}
|
||||
|
||||
TEST_F(LibraryTest, MigrateBookmark)
|
||||
{
|
||||
std::string bookId1 = "0c45160e-f917-760a-9159-dfe3c53cdcdd";
|
||||
std::string bookId2 = "0189d9be-2fd0-b4b6-7300-20fab0b5cdc8";
|
||||
|
||||
auto book1 = lib->getBookById(bookId1);
|
||||
auto book1Flavour = lib->getBookById(bookId1+"_flavour");
|
||||
auto book2 = lib->getBookById(bookId2);
|
||||
|
||||
lib->addBookmark(createBookmark(book1));
|
||||
lib->addBookmark(createBookmark("invalid-book-id"));
|
||||
lib->addBookmark(createBookmark(book2));
|
||||
|
||||
auto wrongIdBookmark = createBookmark(book1);
|
||||
wrongIdBookmark.setBookId("wrong-book-id");
|
||||
lib->addBookmark(wrongIdBookmark);
|
||||
|
||||
auto wrongIdBookmarkNoName = createBookmark(book2);
|
||||
wrongIdBookmarkNoName.setBookId("wrong-book-id-noname");
|
||||
wrongIdBookmarkNoName.setBookName("");
|
||||
lib->addBookmark(wrongIdBookmarkNoName);
|
||||
|
||||
auto wrongIdFlavourBookmark = createBookmark(book1Flavour);
|
||||
wrongIdFlavourBookmark.setBookId("wrong-book-flavour-id");
|
||||
lib->addBookmark(wrongIdFlavourBookmark);
|
||||
|
||||
lib->addBookmark(createBookmark(bookId1));
|
||||
lib->addBookmark(createBookmark("invalid-bookmark-id"));
|
||||
lib->addBookmark(createBookmark(bookId2));
|
||||
auto onlyValidBookmarks = lib->getBookmarks();
|
||||
auto allBookmarks = lib->getBookmarks(false);
|
||||
|
||||
ASSERT_EQ(onlyValidBookmarks.size(), 2U);
|
||||
EXPECT_EQ(onlyValidBookmarks[0].getBookId(), bookId1);
|
||||
EXPECT_EQ(onlyValidBookmarks[1].getBookId(), bookId2);
|
||||
|
||||
ASSERT_EQ(allBookmarks.size(), 6U);
|
||||
EXPECT_EQ(allBookmarks[0].getBookId(), bookId1);
|
||||
EXPECT_EQ(allBookmarks[1].getBookId(), "invalid-book-id");
|
||||
EXPECT_EQ(allBookmarks[1].getBookId(), "invalid-bookmark-id");
|
||||
EXPECT_EQ(allBookmarks[2].getBookId(), bookId2);
|
||||
EXPECT_EQ(allBookmarks[3].getBookId(), "wrong-book-id");
|
||||
EXPECT_EQ(allBookmarks[4].getBookId(), "wrong-book-id-noname");
|
||||
EXPECT_EQ(allBookmarks[5].getBookId(), "wrong-book-flavour-id");
|
||||
|
||||
ASSERT_EQ(lib->migrateBookmarks("no-existant-book"), 0);
|
||||
|
||||
ASSERT_EQ(lib->migrateBookmarks(), std::make_tuple(3, 4));
|
||||
|
||||
onlyValidBookmarks = lib->getBookmarks();
|
||||
allBookmarks = lib->getBookmarks(false);
|
||||
|
||||
ASSERT_EQ(onlyValidBookmarks.size(), 5U);
|
||||
EXPECT_EQ(onlyValidBookmarks[0].getBookId(), bookId1);
|
||||
EXPECT_EQ(onlyValidBookmarks[1].getBookId(), bookId2);
|
||||
EXPECT_EQ(onlyValidBookmarks[2].getBookId(), bookId1+"_updated1yearlater");
|
||||
EXPECT_EQ(onlyValidBookmarks[3].getBookId(), bookId2);
|
||||
EXPECT_EQ(onlyValidBookmarks[4].getBookId(), bookId1+"_updated1yearlater_flavour");
|
||||
|
||||
ASSERT_EQ(allBookmarks.size(), 6U);
|
||||
EXPECT_EQ(allBookmarks[0].getBookId(), bookId1);
|
||||
EXPECT_EQ(allBookmarks[1].getBookId(), "invalid-book-id");
|
||||
EXPECT_EQ(allBookmarks[2].getBookId(), bookId2);
|
||||
EXPECT_EQ(allBookmarks[3].getBookId(), bookId1+"_updated1yearlater");
|
||||
EXPECT_EQ(allBookmarks[4].getBookId(), bookId2);
|
||||
EXPECT_EQ(allBookmarks[5].getBookId(), bookId1+"_updated1yearlater_flavour");
|
||||
|
||||
ASSERT_EQ(lib->migrateBookmarks(), std::make_tuple(0, 1));
|
||||
|
||||
ASSERT_EQ(lib->migrateBookmarks(bookId1), 1);
|
||||
allBookmarks = lib->getBookmarks(false);
|
||||
ASSERT_EQ(allBookmarks.size(), 6U);
|
||||
EXPECT_EQ(allBookmarks[0].getBookId(), bookId1+"_updated1yearlater");
|
||||
EXPECT_EQ(allBookmarks[1].getBookId(), "invalid-book-id");
|
||||
EXPECT_EQ(allBookmarks[2].getBookId(), bookId2);
|
||||
EXPECT_EQ(allBookmarks[3].getBookId(), bookId1+"_updated1yearlater");
|
||||
EXPECT_EQ(allBookmarks[4].getBookId(), bookId2);
|
||||
EXPECT_EQ(allBookmarks[5].getBookId(), bookId1+"_updated1yearlater_flavour");
|
||||
|
||||
ASSERT_EQ(lib->migrateBookmarks(bookId1, bookId2), 0); // No more bookId1 bookmark
|
||||
|
||||
ASSERT_EQ(lib->migrateBookmarks(bookId1+"_updated1yearlater", bookId2), 2);
|
||||
onlyValidBookmarks = lib->getBookmarks();
|
||||
allBookmarks = lib->getBookmarks(false);
|
||||
|
||||
ASSERT_EQ(onlyValidBookmarks.size(), 5U);
|
||||
EXPECT_EQ(onlyValidBookmarks[0].getBookId(), bookId2);
|
||||
EXPECT_EQ(onlyValidBookmarks[1].getBookId(), bookId2);
|
||||
EXPECT_EQ(onlyValidBookmarks[2].getBookId(), bookId2);
|
||||
EXPECT_EQ(onlyValidBookmarks[3].getBookId(), bookId2);
|
||||
EXPECT_EQ(onlyValidBookmarks[4].getBookId(), bookId1+"_updated1yearlater_flavour");
|
||||
|
||||
ASSERT_EQ(allBookmarks.size(), 6U);
|
||||
EXPECT_EQ(allBookmarks[0].getBookId(), bookId2);
|
||||
EXPECT_EQ(allBookmarks[1].getBookId(), "invalid-book-id");
|
||||
EXPECT_EQ(allBookmarks[2].getBookId(), bookId2);
|
||||
EXPECT_EQ(allBookmarks[3].getBookId(), bookId2);
|
||||
EXPECT_EQ(allBookmarks[4].getBookId(), bookId2);
|
||||
EXPECT_EQ(allBookmarks[5].getBookId(), bookId1+"_updated1yearlater_flavour");
|
||||
|
||||
ASSERT_EQ(lib->migrateBookmarks("invalid-book-id", bookId1), 1);
|
||||
|
||||
onlyValidBookmarks = lib->getBookmarks();
|
||||
allBookmarks = lib->getBookmarks(false);
|
||||
|
||||
ASSERT_EQ(onlyValidBookmarks.size(), 6U);
|
||||
EXPECT_EQ(onlyValidBookmarks[0].getBookId(), bookId2);
|
||||
EXPECT_EQ(onlyValidBookmarks[1].getBookId(), bookId1);
|
||||
EXPECT_EQ(onlyValidBookmarks[2].getBookId(), bookId2);
|
||||
EXPECT_EQ(onlyValidBookmarks[3].getBookId(), bookId2);
|
||||
EXPECT_EQ(onlyValidBookmarks[4].getBookId(), bookId2);
|
||||
EXPECT_EQ(onlyValidBookmarks[5].getBookId(), bookId1+"_updated1yearlater_flavour");
|
||||
|
||||
|
||||
ASSERT_EQ(allBookmarks.size(), 6U);
|
||||
EXPECT_EQ(allBookmarks[0].getBookId(), bookId2);
|
||||
EXPECT_EQ(allBookmarks[1].getBookId(), bookId1);
|
||||
EXPECT_EQ(allBookmarks[2].getBookId(), bookId2);
|
||||
EXPECT_EQ(allBookmarks[3].getBookId(), bookId2);
|
||||
EXPECT_EQ(allBookmarks[4].getBookId(), bookId2);
|
||||
EXPECT_EQ(allBookmarks[5].getBookId(), bookId1+"_updated1yearlater_flavour");
|
||||
}
|
||||
|
||||
TEST_F(LibraryTest, GetBestTargetBookIdOlder)
|
||||
{
|
||||
auto bookId = std::string("0c45160e-f917-760a-9159-dfe3c53cdcdd");
|
||||
|
||||
auto book = lib->getBookById(bookId);
|
||||
|
||||
auto validBookmark = createBookmark(book);
|
||||
lib->addBookmark(validBookmark);
|
||||
|
||||
ASSERT_EQ(lib->getBestTargetBookId(validBookmark, kiwix::UPGRADE_ONLY), bookId+"_updated1yearlater");
|
||||
ASSERT_EQ(lib->getBestTargetBookId(validBookmark, kiwix::ALLOW_DOWNGRADE), bookId+"_updated1yearlater");
|
||||
}
|
||||
|
||||
TEST_F(LibraryTest, GetBestTargetBookIdNewer)
|
||||
{
|
||||
auto bookId = std::string("0c45160e-f917-760a-9159-dfe3c53cdcdd_updated1yearlater");
|
||||
|
||||
auto book = lib->getBookById(bookId);
|
||||
EXPECT_EQ(book.getDate(), "2019-10-08");
|
||||
|
||||
auto validBookmark = createBookmark(book);
|
||||
// Make the bookmark more recent than any books in the library.
|
||||
// (But still pointing to existing book)
|
||||
validBookmark.setDate("2020-10-08");
|
||||
lib->addBookmark(validBookmark);
|
||||
|
||||
// The best book for the bookmark is bookId...
|
||||
ASSERT_EQ(lib->getBestTargetBookId(validBookmark, kiwix::UPGRADE_ONLY), bookId);
|
||||
// but there is not migration to do as the bookmark already point to it.
|
||||
ASSERT_EQ(lib->migrateBookmarks(bookId, kiwix::UPGRADE_ONLY), 0);
|
||||
|
||||
ASSERT_EQ(lib->getBestTargetBookId(validBookmark, kiwix::ALLOW_DOWNGRADE), bookId);
|
||||
}
|
||||
|
||||
TEST_F(LibraryTest, GetBestTargetBookIdInvalidOlder)
|
||||
{
|
||||
auto bookId = std::string("0c45160e-f917-760a-9159-dfe3c53cdcdd");
|
||||
|
||||
auto book = lib->getBookById(bookId);
|
||||
|
||||
auto invalidBookmark = createBookmark(book);
|
||||
invalidBookmark.setBookId("invalid-book-id");
|
||||
lib->addBookmark(invalidBookmark);
|
||||
|
||||
ASSERT_EQ(lib->getBestTargetBookId(invalidBookmark, kiwix::UPGRADE_ONLY), bookId+"_updated1yearlater");
|
||||
ASSERT_EQ(lib->getBestTargetBookId(invalidBookmark, kiwix::ALLOW_DOWNGRADE), bookId+"_updated1yearlater");
|
||||
}
|
||||
|
||||
TEST_F(LibraryTest, GetBestTargetBookIdInvalidNewer)
|
||||
{
|
||||
auto bookId = std::string("0c45160e-f917-760a-9159-dfe3c53cdcdd");
|
||||
|
||||
auto book = lib->getBookById(bookId);
|
||||
EXPECT_EQ(book.getDate(), "2018-10-08");
|
||||
|
||||
auto invalidBookmark = createBookmark(book);
|
||||
invalidBookmark.setBookId("invalid-book-id");
|
||||
invalidBookmark.setDate("2020-10-08");
|
||||
lib->addBookmark(invalidBookmark);
|
||||
|
||||
ASSERT_EQ(lib->getBestTargetBookId(invalidBookmark, kiwix::UPGRADE_ONLY), "");
|
||||
ASSERT_EQ(lib->getBestTargetBookId(invalidBookmark, kiwix::ALLOW_DOWNGRADE), bookId+"_updated1yearlater");
|
||||
}
|
||||
|
||||
TEST_F(LibraryTest, GetBestTargetBookIdFlavour)
|
||||
{
|
||||
auto bookId = std::string("0c45160e-f917-760a-9159-dfe3c53cdcdd_flavour");
|
||||
|
||||
auto book = lib->getBookById(bookId);
|
||||
EXPECT_EQ(book.getDate(), "2018-10-08");
|
||||
|
||||
auto invalidBookmark = createBookmark(book);
|
||||
invalidBookmark.setBookId("invalid-book-id");
|
||||
invalidBookmark.setDate("2020-10-08");
|
||||
lib->addBookmark(invalidBookmark);
|
||||
|
||||
ASSERT_EQ(lib->getBestTargetBookId(invalidBookmark, kiwix::UPGRADE_ONLY), "");
|
||||
ASSERT_EQ(lib->getBestTargetBookId(invalidBookmark, kiwix::ALLOW_DOWNGRADE), "0c45160e-f917-760a-9159-dfe3c53cdcdd_updated1yearlater_flavour");
|
||||
}
|
||||
|
||||
TEST_F(LibraryTest, GetBestTargetBookIdName)
|
||||
{
|
||||
ASSERT_EQ(lib->getBestTargetBookId("wikipedia_fr_tunisie"), "0c45160e-f917-760a-9159-dfe3c53cdcdd_updated1yearlater");
|
||||
ASSERT_EQ(lib->getBestTargetBookId("wikipedia_fr_tunisie", "novid"), "0c45160e-f917-760a-9159-dfe3c53cdcdd_updated1yearlater");
|
||||
ASSERT_EQ(lib->getBestTargetBookId("wikipedia_fr_tunisie", "other_flavour"), "0c45160e-f917-760a-9159-dfe3c53cdcdd_updated1yearlater_flavour");
|
||||
ASSERT_EQ(lib->getBestTargetBookId("wikipedia_fr_tunisie", "other_flavour", "2020-12-12"), "");
|
||||
}
|
||||
|
||||
TEST_F(LibraryTest, sanityCheck)
|
||||
{
|
||||
EXPECT_EQ(lib->getBookCount(true, true), 16U);
|
||||
EXPECT_EQ(lib->getBookCount(true, true), 12U);
|
||||
EXPECT_EQ(lib->getBooksLanguages(),
|
||||
std::vector<std::string>({"deu", "eng", "fra", "ita", "spa"})
|
||||
);
|
||||
@@ -786,10 +400,6 @@ TEST_F(LibraryTest, filterLocal)
|
||||
);
|
||||
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().local(false),
|
||||
"Business talks about TED",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Granblue Fantasy Wiki",
|
||||
"Géographie par Wikipédia",
|
||||
@@ -797,7 +407,7 @@ TEST_F(LibraryTest, filterLocal)
|
||||
"Mathématiques",
|
||||
"Movies & TV Stack Exchange",
|
||||
"Mythology & Folklore Stack Exchange",
|
||||
"TED\"talks\" - Business",
|
||||
"TED talks - Business",
|
||||
"Tania Louis",
|
||||
"Wikiquote"
|
||||
);
|
||||
@@ -806,10 +416,6 @@ TEST_F(LibraryTest, filterLocal)
|
||||
TEST_F(LibraryTest, filterRemote)
|
||||
{
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().remote(true),
|
||||
"Business talks about TED",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Granblue Fantasy Wiki",
|
||||
"Géographie par Wikipédia",
|
||||
@@ -818,7 +424,7 @@ TEST_F(LibraryTest, filterRemote)
|
||||
"Movies & TV Stack Exchange",
|
||||
"Mythology & Folklore Stack Exchange",
|
||||
"Ray Charles",
|
||||
"TED\"talks\" - Business",
|
||||
"TED talks - Business",
|
||||
"Tania Louis",
|
||||
"Wikiquote"
|
||||
);
|
||||
@@ -831,23 +437,21 @@ TEST_F(LibraryTest, filterRemote)
|
||||
TEST_F(LibraryTest, filterByLanguage)
|
||||
{
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().lang("eng"),
|
||||
"Business talks about TED",
|
||||
"Granblue Fantasy Wiki",
|
||||
"Islam Stack Exchange",
|
||||
"Movies & TV Stack Exchange",
|
||||
"Mythology & Folklore Stack Exchange",
|
||||
"Ray Charles",
|
||||
"TED\"talks\" - Business"
|
||||
"TED talks - Business"
|
||||
);
|
||||
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().query("lang:eng"),
|
||||
"Business talks about TED",
|
||||
"Granblue Fantasy Wiki",
|
||||
"Islam Stack Exchange",
|
||||
"Movies & TV Stack Exchange",
|
||||
"Mythology & Folklore Stack Exchange",
|
||||
"Ray Charles",
|
||||
"TED\"talks\" - Business"
|
||||
"TED talks - Business"
|
||||
);
|
||||
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().query("eng"),
|
||||
@@ -855,25 +459,6 @@ TEST_F(LibraryTest, filterByLanguage)
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(LibraryTest, filterByFlavour)
|
||||
{
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().flavour("full"),
|
||||
"Géographie par Wikipédia",
|
||||
"Tania Louis",
|
||||
"Wikiquote"
|
||||
);
|
||||
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().query("flavour:full"),
|
||||
"Géographie par Wikipédia",
|
||||
"Tania Louis",
|
||||
"Wikiquote"
|
||||
);
|
||||
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().query("full"),
|
||||
/* no results */
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(LibraryTest, filterByTags)
|
||||
{
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().acceptTags({"stackexchange"}),
|
||||
@@ -973,9 +558,6 @@ TEST_F(LibraryTest, filterByQuery)
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().query("Wiki"),
|
||||
"An example ZIM archive", // due to the "wikibooks" tag
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Granblue Fantasy Wiki",
|
||||
"Géographie par Wikipédia",
|
||||
"Mathématiques", // due to the "wikipedia" tag
|
||||
@@ -994,10 +576,6 @@ TEST_F(LibraryTest, filteringByEmptyQueryReturnsAllEntries)
|
||||
{
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().query(""),
|
||||
"An example ZIM archive",
|
||||
"Business talks about TED",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Granblue Fantasy Wiki",
|
||||
"Géographie par Wikipédia",
|
||||
@@ -1006,7 +584,7 @@ TEST_F(LibraryTest, filteringByEmptyQueryReturnsAllEntries)
|
||||
"Movies & TV Stack Exchange",
|
||||
"Mythology & Folklore Stack Exchange",
|
||||
"Ray Charles",
|
||||
"TED\"talks\" - Business",
|
||||
"TED talks - Business",
|
||||
"Tania Louis",
|
||||
"Wikiquote"
|
||||
);
|
||||
@@ -1015,9 +593,6 @@ TEST_F(LibraryTest, filteringByEmptyQueryReturnsAllEntries)
|
||||
TEST_F(LibraryTest, filterByCreator)
|
||||
{
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().creator("Wikipedia"),
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Géographie par Wikipédia",
|
||||
"Mathématiques",
|
||||
@@ -1059,9 +634,6 @@ TEST_F(LibraryTest, filterByCreator)
|
||||
);
|
||||
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().query("creator:Wikipedia"),
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Géographie par Wikipédia",
|
||||
"Mathématiques",
|
||||
@@ -1169,9 +741,6 @@ TEST_F(LibraryTest, filterByMaxSize)
|
||||
TEST_F(LibraryTest, filterByMultipleCriteria)
|
||||
{
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().query("Wiki").creator("Wikipedia"),
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Géographie par Wikipédia",
|
||||
"Mathématiques", // due to the "wikipedia" tag
|
||||
@@ -1179,17 +748,11 @@ TEST_F(LibraryTest, filterByMultipleCriteria)
|
||||
);
|
||||
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().query("Wiki").creator("Wikipedia").maxSize(100000000UL),
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Ray Charles"
|
||||
);
|
||||
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter().query("Wiki").creator("Wikipedia").maxSize(100000000UL).local(false),
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie"
|
||||
);
|
||||
}
|
||||
@@ -1247,10 +810,6 @@ TEST_F(LibraryTest, removeBooksNotUpdatedSince)
|
||||
{
|
||||
EXPECT_FILTER_RESULTS(kiwix::Filter(),
|
||||
"An example ZIM archive",
|
||||
"Business talks about TED",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Encyclopédie de la Tunisie",
|
||||
"Granblue Fantasy Wiki",
|
||||
"Géographie par Wikipédia",
|
||||
@@ -1259,7 +818,7 @@ TEST_F(LibraryTest, removeBooksNotUpdatedSince)
|
||||
"Movies & TV Stack Exchange",
|
||||
"Mythology & Folklore Stack Exchange",
|
||||
"Ray Charles",
|
||||
"TED\"talks\" - Business",
|
||||
"TED talks - Business",
|
||||
"Tania Louis",
|
||||
"Wikiquote"
|
||||
);
|
||||
@@ -1273,7 +832,7 @@ TEST_F(LibraryTest, removeBooksNotUpdatedSince)
|
||||
|
||||
const uint64_t rev2 = lib->getRevision();
|
||||
|
||||
EXPECT_EQ(13u, lib->removeBooksNotUpdatedSince(rev));
|
||||
EXPECT_EQ(9u, lib->removeBooksNotUpdatedSince(rev));
|
||||
|
||||
EXPECT_GT(lib->getRevision(), rev2);
|
||||
|
||||
|
||||
@@ -129,11 +129,11 @@ std::string maskVariableOPDSFeedData(std::string s)
|
||||
" href=\"/ROOT%23%3F/catalog/v2/illustration/raycharles/?size=48\"\n" \
|
||||
" type=\"image/png;width=48;height=48;scale=1\"/>\n ", \
|
||||
CONTENT_NAME, \
|
||||
"zimfile_raycharles", \
|
||||
"zimfile", \
|
||||
"569344"\
|
||||
)
|
||||
|
||||
#define RAY_CHARLES_CATALOG_ENTRY _RAY_CHARLES_CATALOG_ENTRY("zimfile_raycharles")
|
||||
#define RAY_CHARLES_CATALOG_ENTRY _RAY_CHARLES_CATALOG_ENTRY("zimfile")
|
||||
#define RAY_CHARLES_CATALOG_ENTRY_NO_MAPPER _RAY_CHARLES_CATALOG_ENTRY("raycharles")
|
||||
|
||||
#define UNCATEGORIZED_RAY_CHARLES_CATALOG_ENTRY CATALOG_ENTRY(\
|
||||
@@ -145,8 +145,8 @@ std::string maskVariableOPDSFeedData(std::string s)
|
||||
"",\
|
||||
"public_tag_with_a_value:value_of_a_public_tag;_private_tag_with_a_value:value_of_a_private_tag;wikipedia;_pictures:no;_videos:no;_details:no",\
|
||||
"",\
|
||||
"zimfile_raycharles_uncategorized", \
|
||||
"zimfile_raycharles_uncategorized", \
|
||||
"zimfile", \
|
||||
"zimfile", \
|
||||
"125952"\
|
||||
)
|
||||
|
||||
@@ -1033,7 +1033,7 @@ TEST_F(LibraryServerTest, no_name_mapper_catalog_v2_individual_entry_access)
|
||||
" />\n" \
|
||||
" <link\n" \
|
||||
" type=\"text/css\"\n" \
|
||||
" href=\"/ROOT%23%3F/skin/index.css?cacheid=ae79e41a\"\n" \
|
||||
" href=\"/ROOT%23%3F/skin/index.css?cacheid=1e78e7cf\"\n" \
|
||||
" rel=\"Stylesheet\"\n" \
|
||||
" />\n" \
|
||||
" <link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"/ROOT%23%3F/skin/favicon/apple-touch-icon.png?cacheid=f86f8df3\">\n" \
|
||||
@@ -1066,10 +1066,17 @@ TEST_F(LibraryServerTest, no_name_mapper_catalog_v2_individual_entry_access)
|
||||
" .tag__link {\n" \
|
||||
" pointer-events: none;\n" \
|
||||
" }\n\n" \
|
||||
" .book__link__wrapper {\n" \
|
||||
" grid-column: 1 / 3;\n" \
|
||||
" grid-row: 1 / 3;\n" \
|
||||
" }\n\n" \
|
||||
" .book__link {\n" \
|
||||
" grid-row: 2 / 3;\n" \
|
||||
" }\n\n" \
|
||||
" .kiwixHomeBody__results {\n" \
|
||||
" flex-basis: 100%;\n" \
|
||||
" }\n\n" \
|
||||
" #book__title>a {\n" \
|
||||
" #book__title>a, .book__download a {\n" \
|
||||
" text-decoration: none;\n" \
|
||||
" all: unset;\n" \
|
||||
" }\n" \
|
||||
@@ -1080,83 +1087,62 @@ TEST_F(LibraryServerTest, no_name_mapper_catalog_v2_individual_entry_access)
|
||||
|
||||
#define CHARLES_RAY_BOOK_HTML \
|
||||
" <div class=\"book__wrapper\">\n" \
|
||||
" <a class=\"book__link\" href=\"/ROOT%23%3F/content/zimfile%26other\" title=\"Preview\" aria-label=\"Preview\">\n" \
|
||||
" <div class=\"book__link__wrapper\">\n" \
|
||||
" <div class=\"book__icon\" style=background-image:url(/ROOT%23%3F/catalog/v2/illustration/charlesray/?size=48)></div>\n" \
|
||||
" <div class=\"book__header\">\n" \
|
||||
" <div id=\"book__title\">Charles, Ray</div>\n" \
|
||||
" </div>\n" \
|
||||
" <div class=\"book__description\" title=\"Wikipedia articles about Ray Charles\">Wikipedia articles about Ray Charles</div>\n" \
|
||||
" </div>\n" \
|
||||
" </a>\n" \
|
||||
" <div class=\"book__meta\">\n" \
|
||||
" <div class=\"book__languageTag\" >fra</div>\n" \
|
||||
" <div class=\"book__tags\"><div class=\"book__tags--wrapper\">\n" \
|
||||
" <span class=\"tag__link\" aria-label='unittest' title='unittest'>unittest</span>\n" \
|
||||
" <span class=\"tag__link\" aria-label='wikipedia' title='wikipedia'>wikipedia</span>\n" \
|
||||
" </div>\n" \
|
||||
" <div class=\"book__link__wrapper\">\n" \
|
||||
" <div class=\"book__icon\" style=background-image:url(/ROOT%23%3F/catalog/v2/illustration/charlesray/?size=48)></div>\n" \
|
||||
" <div class=\"book__header\">\n" \
|
||||
" <div id=\"book__title\"><a href=\"/ROOT%23%3F/content/zimfile%26other\">Charles, Ray</a></div>\n" \
|
||||
" <div class=\"book__download\"><span><a href=\"/ROOT%23%3F/nojs/download/zimfile%26other\">Download</a></span></div>\n" \
|
||||
" </div>\n" \
|
||||
" <a class=\"book__link\" href=\"/ROOT%23%3F/content/zimfile%26other\" title=\"Preview\" aria-label=\"Preview\">\n" \
|
||||
" <div class=\"book__description\" title=\"Wikipedia articles about Ray Charles\">Wikipedia articles about Ray Charles</div>\n" \
|
||||
" </a>\n" \
|
||||
" </div>\n" \
|
||||
" <div class=\"book__languageTag\" >fra</div>\n" \
|
||||
" <div class=\"book__tags\"><div class=\"book__tags--wrapper\">\n" \
|
||||
" <span class=\"tag__link\" aria-label='unittest' title='unittest'>unittest</span>\n" \
|
||||
" <span class=\"tag__link\" aria-label='wikipedia' title='wikipedia'>wikipedia</span>\n" \
|
||||
" </div>\n" \
|
||||
" <div>\n" \
|
||||
" <a class=\"book__download\" href=\"/ROOT%23%3F/nojs/download/zimfile%26other\">\n" \
|
||||
" <img src=\"/ROOT%23%3F/skin/download-white.svg?cacheid=079ab989\">\n" \
|
||||
" <span>Download</span>\n" \
|
||||
" </a>\n" \
|
||||
" </div>\n" \
|
||||
" </div>\n" \
|
||||
" </div>\n"
|
||||
|
||||
#define RAY_CHARLES_BOOK_HTML \
|
||||
" <div class=\"book__wrapper\">\n" \
|
||||
" <a class=\"book__link\" href=\"/ROOT%23%3F/content/zimfile_raycharles\" title=\"Preview\" aria-label=\"Preview\">\n" \
|
||||
" <div class=\"book__link__wrapper\">\n" \
|
||||
" <div class=\"book__icon\" style=background-image:url(/ROOT%23%3F/catalog/v2/illustration/raycharles/?size=48)></div>\n" \
|
||||
" <div class=\"book__header\">\n" \
|
||||
" <div id=\"book__title\">Ray Charles</div>\n" \
|
||||
" </div>\n" \
|
||||
" <div class=\"book__description\" title=\"Wikipedia articles about Ray Charles\">Wikipedia articles about Ray Charles</div>\n" \
|
||||
" </div>\n" \
|
||||
" </a>\n" \
|
||||
" <div class=\"book__meta\">\n" \
|
||||
" <div class=\"book__languageTag\" >eng</div>\n" \
|
||||
" <div class=\"book__tags\"><div class=\"book__tags--wrapper\">\n" \
|
||||
" <span class=\"tag__link\" aria-label='public_tag_without_a_value' title='public_tag_without_a_value'>public_tag_without_a_value</span>\n" \
|
||||
" <span class=\"tag__link\" aria-label='wikipedia' title='wikipedia'>wikipedia</span>\n" \
|
||||
" </div>\n" \
|
||||
" <div class=\"book__link__wrapper\">\n" \
|
||||
" <div class=\"book__icon\" style=background-image:url(/ROOT%23%3F/catalog/v2/illustration/raycharles/?size=48)></div>\n" \
|
||||
" <div class=\"book__header\">\n" \
|
||||
" <div id=\"book__title\"><a href=\"/ROOT%23%3F/content/zimfile\">Ray Charles</a></div>\n" \
|
||||
" <div class=\"book__download\"><span><a href=\"/ROOT%23%3F/nojs/download/zimfile\">Download</a></span></div>\n" \
|
||||
" </div>\n" \
|
||||
" <a class=\"book__link\" href=\"/ROOT%23%3F/content/zimfile\" title=\"Preview\" aria-label=\"Preview\">\n" \
|
||||
" <div class=\"book__description\" title=\"Wikipedia articles about Ray Charles\">Wikipedia articles about Ray Charles</div>\n" \
|
||||
" </a>\n" \
|
||||
" </div>\n" \
|
||||
" <div class=\"book__languageTag\" >eng</div>\n" \
|
||||
" <div class=\"book__tags\"><div class=\"book__tags--wrapper\">\n" \
|
||||
" <span class=\"tag__link\" aria-label='public_tag_without_a_value' title='public_tag_without_a_value'>public_tag_without_a_value</span>\n" \
|
||||
" <span class=\"tag__link\" aria-label='wikipedia' title='wikipedia'>wikipedia</span>\n" \
|
||||
" </div>\n" \
|
||||
" <div>\n" \
|
||||
" <a class=\"book__download\" href=\"/ROOT%23%3F/nojs/download/zimfile_raycharles\">\n" \
|
||||
" <img src=\"/ROOT%23%3F/skin/download-white.svg?cacheid=079ab989\">\n" \
|
||||
" <span>Download</span>\n" \
|
||||
" </a>\n" \
|
||||
" </div>\n" \
|
||||
" </div>\n" \
|
||||
" </div>\n"
|
||||
|
||||
#define RAY_CHARLES_UNCTZ_BOOK_HTML \
|
||||
" <div class=\"book__wrapper\">\n" \
|
||||
" <a class=\"book__link\" href=\"/ROOT%23%3F/content/zimfile_raycharles_uncategorized\" title=\"Preview\" aria-label=\"Preview\">\n" \
|
||||
" <div class=\"book__link__wrapper\">\n" \
|
||||
" <div class=\"book__icon\" style=background-image:url(/ROOT%23%3F/catalog/v2/illustration/raycharles_uncategorized/?size=48)></div>\n" \
|
||||
" <div class=\"book__header\">\n" \
|
||||
" <div id=\"book__title\">Ray (uncategorized) Charles</div>\n" \
|
||||
" </div>\n" \
|
||||
" <div class=\"book__description\" title=\"No category is assigned to this library entry.\">No category is assigned to this library entry.</div>\n" \
|
||||
" </div>\n" \
|
||||
" </a>\n" \
|
||||
" <div class=\"book__meta\">\n" \
|
||||
" <div class=\"book__languageTag\" >rus,eng</div>\n" \
|
||||
" <div class=\"book__tags\"><div class=\"book__tags--wrapper\">\n" \
|
||||
" <span class=\"tag__link\" aria-label='public_tag_with_a_value:value_of_a_public_tag' title='public_tag_with_a_value:value_of_a_public_tag'>public_tag_with_a_value:value_of_a_public_tag</span>\n" \
|
||||
" <span class=\"tag__link\" aria-label='wikipedia' title='wikipedia'>wikipedia</span>\n" \
|
||||
" </div>\n" \
|
||||
" <div class=\"book__link__wrapper\">\n" \
|
||||
" <div class=\"book__icon\" style=background-image:url(/ROOT%23%3F/catalog/v2/illustration/raycharles_uncategorized/?size=48)></div>\n" \
|
||||
" <div class=\"book__header\">\n" \
|
||||
" <div id=\"book__title\"><a href=\"/ROOT%23%3F/content/zimfile\">Ray (uncategorized) Charles</a></div>\n" \
|
||||
" <div class=\"book__download\"><span><a href=\"/ROOT%23%3F/nojs/download/zimfile\">Download</a></span></div>\n" \
|
||||
" </div>\n" \
|
||||
" <a class=\"book__link\" href=\"/ROOT%23%3F/content/zimfile\" title=\"Preview\" aria-label=\"Preview\">\n" \
|
||||
" <div class=\"book__description\" title=\"No category is assigned to this library entry.\">No category is assigned to this library entry.</div>\n" \
|
||||
" </a>\n" \
|
||||
" </div>\n" \
|
||||
" <div class=\"book__languageTag\" >rus,eng</div>\n" \
|
||||
" <div class=\"book__tags\"><div class=\"book__tags--wrapper\">\n" \
|
||||
" <span class=\"tag__link\" aria-label='public_tag_with_a_value:value_of_a_public_tag' title='public_tag_with_a_value:value_of_a_public_tag'>public_tag_with_a_value:value_of_a_public_tag</span>\n" \
|
||||
" <span class=\"tag__link\" aria-label='wikipedia' title='wikipedia'>wikipedia</span>\n" \
|
||||
" </div>\n" \
|
||||
" <div>\n" \
|
||||
" <a class=\"book__download\" href=\"/ROOT%23%3F/nojs/download/zimfile_raycharles_uncategorized\">\n" \
|
||||
" <img src=\"/ROOT%23%3F/skin/download-white.svg?cacheid=079ab989\">\n" \
|
||||
" <span>Download</span>\n" \
|
||||
" </a>\n" \
|
||||
" </div>\n" \
|
||||
" </div>\n" \
|
||||
" </div>\n"
|
||||
|
||||
#define FINAL_HTML_TEXT \
|
||||
@@ -1185,7 +1171,7 @@ TEST_F(LibraryServerTest, no_name_mapper_catalog_v2_individual_entry_access)
|
||||
" </div>\n" \
|
||||
" </div>\n" \
|
||||
" <form id='kiwixSearchForm' class='kiwixNav__SearchForm' action=\"/ROOT%23%3F/nojs\">\n" \
|
||||
" <input type=\"text\" name=\"q\" accesskey=\"s\" placeholder=\"Search\" id=\"searchFilter\" class='kiwixSearch filter' value=\"\">\n" \
|
||||
" <input type=\"text\" name=\"q\" placeholder=\"Search\" id=\"searchFilter\" class='kiwixSearch filter' value=\"\">\n" \
|
||||
" <input type=\"submit\" class=\"kiwixButton kiwixButtonHover\" value=\"Search\"/>\n" \
|
||||
" </form>\n" \
|
||||
" </div>\n"
|
||||
@@ -1238,17 +1224,17 @@ TEST_F(LibraryServerTest, no_name_mapper_catalog_v2_individual_entry_access)
|
||||
" <div class=\"downloadLinksTitle\">\n" \
|
||||
" Download links for <b><i>Ray (uncategorized) Charles</i></b>\n" \
|
||||
" </div>\n" \
|
||||
" <a href=\"https://github.com/kiwix/libkiwix/raw/master/test/data/zimfile_raycharles_uncategorized.zim\" download>\n" \
|
||||
" <a href=\"https://github.com/kiwix/libkiwix/raw/master/test/data/zimfile.zim\" download>\n" \
|
||||
" <div>Direct</div>\n" \
|
||||
" </a>\n" \
|
||||
" <a href=\"https://github.com/kiwix/libkiwix/raw/master/test/data/zimfile_raycharles_uncategorized.zim.sha256\" download>\n" \
|
||||
" <div>SHA-256 checksum</div>\n" \
|
||||
" <a href=\"https://github.com/kiwix/libkiwix/raw/master/test/data/zimfile.zim.sha256\" download>\n" \
|
||||
" <div>Sha256 hash</div>\n" \
|
||||
" </a>\n" \
|
||||
" <a href=\"https://github.com/kiwix/libkiwix/raw/master/test/data/zimfile_raycharles_uncategorized.zim.magnet\" target=\"_blank\">\n" \
|
||||
" <a href=\"https://github.com/kiwix/libkiwix/raw/master/test/data/zimfile.zim.magnet\" target=\"_blank\">\n" \
|
||||
" <div>Magnet link</div>\n" \
|
||||
" </a>\n" \
|
||||
" <a href=\"https://github.com/kiwix/libkiwix/raw/master/test/data/zimfile_raycharles_uncategorized.zim.torrent\" download>\n" \
|
||||
" <div>BitTorrent</div>\n" \
|
||||
" <a href=\"https://github.com/kiwix/libkiwix/raw/master/test/data/zimfile.zim.torrent\" download>\n" \
|
||||
" <div>Torrent file</div>\n" \
|
||||
" </a>\n" \
|
||||
"</body>\n" \
|
||||
"</html>"
|
||||
@@ -1287,7 +1273,7 @@ TEST_F(LibraryServerTest, noJS) {
|
||||
FINAL_HTML_TEXT);
|
||||
|
||||
// no_js_download
|
||||
r = zfs1_->GET("/ROOT%23%3F/nojs/download/zimfile_raycharles_uncategorized");
|
||||
r = zfs1_->GET("/ROOT%23%3F/nojs/download/zimfile");
|
||||
EXPECT_EQ(r->status, 200);
|
||||
EXPECT_EQ(r->body, RAY_CHARLES_UNCTZ_DOWNLOAD);
|
||||
}
|
||||
|
||||
@@ -25,23 +25,11 @@ TEST(ManagerTest, addBookFromPathAndGetIdTest)
|
||||
EXPECT_EQ(book.getUrl(), url);
|
||||
}
|
||||
|
||||
|
||||
|
||||
#if _WIN32
|
||||
# define UNITTEST_ZIM_PATH "zimfiles\\unittest.zim"
|
||||
# define LIB_ABS_PATH "C:\\data\\lib.xml"
|
||||
# define ZIM_ABS_PATH "C:\\data\\zimfiles\\unittest.zim"
|
||||
#else
|
||||
# define UNITTEST_ZIM_PATH "zimfiles/unittest.zim"
|
||||
# define LIB_ABS_PATH "/data/lib.xml"
|
||||
# define ZIM_ABS_PATH "/data/zimfiles/unittest.zim"
|
||||
#endif
|
||||
|
||||
const char sampleLibraryXML[] = R"(
|
||||
<library version="1.0">
|
||||
<book
|
||||
id="0d0bcd57-d3f6-cb22-44cc-a723ccb4e1b2"
|
||||
path=")" UNITTEST_ZIM_PATH R"("
|
||||
path="zimfiles/unittest.zim"
|
||||
url="https://example.com/zimfiles/unittest.zim"
|
||||
title="Unit Test"
|
||||
description="Wikipedia articles about unit testing"
|
||||
@@ -63,9 +51,9 @@ TEST(ManagerTest, readXml)
|
||||
auto lib = kiwix::Library::create();
|
||||
kiwix::Manager manager = kiwix::Manager(lib);
|
||||
|
||||
EXPECT_EQ(true, manager.readXml(sampleLibraryXML, true, LIB_ABS_PATH, true));
|
||||
EXPECT_EQ(true, manager.readXml(sampleLibraryXML, true, "/data/lib.xml", true));
|
||||
kiwix::Book book = lib->getBookById("0d0bcd57-d3f6-cb22-44cc-a723ccb4e1b2");
|
||||
EXPECT_EQ(ZIM_ABS_PATH, book.getPath());
|
||||
EXPECT_EQ("/data/zimfiles/unittest.zim", book.getPath());
|
||||
EXPECT_EQ("https://example.com/zimfiles/unittest.zim", book.getUrl());
|
||||
EXPECT_EQ("Unit Test", book.getTitle());
|
||||
EXPECT_EQ("Wikipedia articles about unit testing", book.getDescription());
|
||||
|
||||
@@ -38,8 +38,6 @@ if gtest_dep.found() and not meson.is_cross_build()
|
||||
'example.zim',
|
||||
'zimfile.zim',
|
||||
'zimfile&other.zim',
|
||||
'zimfile_raycharles.zim',
|
||||
'zimfile_raycharles_uncategorized.zim',
|
||||
'corner_cases#&.zim',
|
||||
'poor.zim',
|
||||
'library.xml',
|
||||
@@ -77,7 +75,7 @@ if gtest_dep.found() and not meson.is_cross_build()
|
||||
implicit_include_directories: false,
|
||||
include_directories : inc,
|
||||
link_with : libkiwix,
|
||||
link_args: extra_libs,
|
||||
link_args: extra_link_args,
|
||||
dependencies : all_deps + [gtest_dep],
|
||||
build_rpath : '$ORIGIN')
|
||||
test(test_name, test_exe, timeout : 160)
|
||||
|
||||
@@ -7,23 +7,6 @@
|
||||
namespace
|
||||
{
|
||||
|
||||
#if _WIN32
|
||||
const char libraryXML[] = R"(
|
||||
<library version="1.0">
|
||||
<book id="01" path="C:\data\zero_one.zim"> </book>
|
||||
<book id="02" path="C:\data\zero two.zim"> </book>
|
||||
<book id="03" path="C:\data\ZERO thrêë.zim"> </book>
|
||||
<book id="04-2021-10" path="C:\data\zero_four_2021-10.zim"></book>
|
||||
<book id="04-2021-11" path="C:\data\zero_four_2021-11.zim"></book>
|
||||
<book id="05-a" path="C:\data\zero_five-a.zim" name="zero_five"></book>
|
||||
<book id="05-b" path="C:\data\zero_five-b.zim" name="zero_five"></book>
|
||||
<book id="06+" path="C:\data\zërô + SIX.zim"></book>
|
||||
<book id="06plus" path="C:\data\zero_plus_six.zim"></book>
|
||||
<book id="07-super" path="C:\data\zero_seven.zim"></book>
|
||||
<book id="07-sub" path="C:\data\subdir\zero_seven.zim"></book>
|
||||
</library>
|
||||
)";
|
||||
#else
|
||||
const char libraryXML[] = R"(
|
||||
<library version="1.0">
|
||||
<book id="01" path="/data/zero_one.zim"> </book>
|
||||
@@ -31,15 +14,8 @@ const char libraryXML[] = R"(
|
||||
<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>
|
||||
<book id="05-a" path="/data/zero_five-a.zim" name="zero_five"></book>
|
||||
<book id="05-b" path="/data/zero_five-b.zim" name="zero_five"></book>
|
||||
<book id="06+" path="/data/zërô + SIX.zim"></book>
|
||||
<book id="06plus" path="/data/zero_plus_six.zim"></book>
|
||||
<book id="07-super" path="/data/zero_seven.zim"></book>
|
||||
<book id="07-sub" path="/data/subdir/zero_seven.zim"></book>
|
||||
</library>
|
||||
)";
|
||||
#endif
|
||||
|
||||
class NameMapperTest : public ::testing::Test {
|
||||
public:
|
||||
@@ -79,47 +55,6 @@ public:
|
||||
operator std::string() const { return buffer.str(); }
|
||||
};
|
||||
|
||||
#if _WIN32
|
||||
const std::string ZERO_FOUR_NAME_CONFLICT_MSG =
|
||||
"Path collision: 'C:\\data\\zero_four_2021-10.zim' and"
|
||||
" 'C:\\data\\zero_four_2021-11.zim' can't share the same URL path 'zero_four'."
|
||||
" Therefore, only 'C:\\data\\zero_four_2021-10.zim' will be served.\n";
|
||||
|
||||
const std::string ZERO_SIX_NAME_CONFLICT_MSG =
|
||||
"Path collision: 'C:\\data\\zërô + SIX.zim' and "
|
||||
"'C:\\data\\zero_plus_six.zim' can't share the same URL path 'zero_plus_six'."
|
||||
" Therefore, only 'C:\\data\\zërô + SIX.zim' will be served.\n";
|
||||
|
||||
const std::string ZERO_SEVEN_NAME_CONFLICT_MSG =
|
||||
"Path collision: 'C:\\data\\subdir\\zero_seven.zim' and"
|
||||
" 'C:\\data\\zero_seven.zim' can't share the same URL path 'zero_seven'."
|
||||
" Therefore, only 'C:\\data\\subdir\\zero_seven.zim' will be served.\n";
|
||||
#else
|
||||
const std::string ZERO_FOUR_NAME_CONFLICT_MSG =
|
||||
"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";
|
||||
|
||||
const std::string ZERO_SIX_NAME_CONFLICT_MSG =
|
||||
"Path collision: '/data/zërô + SIX.zim' and "
|
||||
"'/data/zero_plus_six.zim' can't share the same URL path 'zero_plus_six'."
|
||||
" Therefore, only '/data/zërô + SIX.zim' will be served.\n";
|
||||
|
||||
const std::string ZERO_SEVEN_NAME_CONFLICT_MSG =
|
||||
"Path collision: '/data/subdir/zero_seven.zim' and"
|
||||
" '/data/zero_seven.zim' can't share the same URL path 'zero_seven'."
|
||||
" Therefore, only '/data/subdir/zero_seven.zim' will be served.\n";
|
||||
#endif
|
||||
|
||||
// Name conflicts in the default mode (without the --nodatealiases is off
|
||||
const std::string DEFAULT_NAME_CONFLICTS = ZERO_SIX_NAME_CONFLICT_MSG
|
||||
+ ZERO_SEVEN_NAME_CONFLICT_MSG;
|
||||
|
||||
// Name conflicts in --nodatealiases mode
|
||||
const std::string ALL_NAME_CONFLICTS = ZERO_FOUR_NAME_CONFLICT_MSG
|
||||
+ ZERO_SIX_NAME_CONFLICT_MSG
|
||||
+ ZERO_SEVEN_NAME_CONFLICT_MSG;
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
void checkUnaliasedEntriesInNameMapper(const kiwix::NameMapper& nm)
|
||||
@@ -129,37 +64,19 @@ void checkUnaliasedEntriesInNameMapper(const kiwix::NameMapper& nm)
|
||||
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("zero_five-a", nm.getNameForId("05-a"));
|
||||
EXPECT_EQ("zero_five-b", nm.getNameForId("05-b"));
|
||||
|
||||
// unreported conflict
|
||||
EXPECT_EQ("zero_plus_six", nm.getNameForId("06+"));
|
||||
EXPECT_EQ("zero_plus_six", nm.getNameForId("06plus"));
|
||||
|
||||
// unreported conflict
|
||||
EXPECT_EQ("zero_seven", nm.getNameForId("07-super"));
|
||||
EXPECT_EQ("zero_seven", nm.getNameForId("07-sub"));
|
||||
|
||||
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"));
|
||||
|
||||
// book name doesn't participate in name mapping
|
||||
EXPECT_THROW(nm.getIdForName("zero_five"), std::out_of_range);
|
||||
EXPECT_EQ("05-a", nm.getIdForName("zero_five-a"));
|
||||
EXPECT_EQ("05-b", nm.getIdForName("zero_five-b"));
|
||||
|
||||
EXPECT_EQ("06+", nm.getIdForName("zero_plus_six"));
|
||||
EXPECT_EQ("07-sub", nm.getIdForName("zero_seven"));
|
||||
}
|
||||
|
||||
TEST_F(NameMapperTest, HumanReadableNameMapperWithoutAliases)
|
||||
{
|
||||
CapturedStderr stderror;
|
||||
kiwix::HumanReadableNameMapper nm(*lib, false);
|
||||
EXPECT_EQ(DEFAULT_NAME_CONFLICTS, std::string(stderror));
|
||||
EXPECT_EQ("", std::string(stderror));
|
||||
|
||||
checkUnaliasedEntriesInNameMapper(nm);
|
||||
EXPECT_THROW(nm.getIdForName("zero_four"), std::out_of_range);
|
||||
@@ -174,7 +91,12 @@ TEST_F(NameMapperTest, HumanReadableNameMapperWithAliases)
|
||||
{
|
||||
CapturedStderr stderror;
|
||||
kiwix::HumanReadableNameMapper nm(*lib, true);
|
||||
EXPECT_EQ(ALL_NAME_CONFLICTS, std::string(stderror));
|
||||
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"));
|
||||
@@ -189,7 +111,7 @@ TEST_F(NameMapperTest, UpdatableNameMapperWithoutAliases)
|
||||
{
|
||||
CapturedStderr stderror;
|
||||
kiwix::UpdatableNameMapper nm(lib, false);
|
||||
EXPECT_EQ(DEFAULT_NAME_CONFLICTS, std::string(stderror));
|
||||
EXPECT_EQ("", std::string(stderror));
|
||||
|
||||
checkUnaliasedEntriesInNameMapper(nm);
|
||||
EXPECT_THROW(nm.getIdForName("zero_four"), std::out_of_range);
|
||||
@@ -205,7 +127,12 @@ TEST_F(NameMapperTest, UpdatableNameMapperWithAliases)
|
||||
{
|
||||
CapturedStderr stderror;
|
||||
kiwix::UpdatableNameMapper nm(lib, true);
|
||||
EXPECT_EQ(ALL_NAME_CONFLICTS, std::string(stderror));
|
||||
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"));
|
||||
@@ -214,7 +141,7 @@ TEST_F(NameMapperTest, UpdatableNameMapperWithAliases)
|
||||
CapturedStderr nmUpdateStderror;
|
||||
lib->removeBookById("04-2021-10");
|
||||
nm.update();
|
||||
EXPECT_EQ(DEFAULT_NAME_CONFLICTS, std::string(nmUpdateStderror));
|
||||
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);
|
||||
|
||||
@@ -18,10 +18,9 @@
|
||||
*/
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "../include/tools.h"
|
||||
#include "../src/tools/otherTools.h"
|
||||
#include "zim/suggestion_iterator.h"
|
||||
#include "../src/server/i18n_utils.h"
|
||||
#include "../src/server/i18n.h"
|
||||
|
||||
#include <regex>
|
||||
|
||||
@@ -234,33 +233,3 @@ TEST(I18n, parseUserLanguagePreferences)
|
||||
"{fr, 1}{en, 0.5}"
|
||||
);
|
||||
}
|
||||
|
||||
#include "../include/tools.h"
|
||||
|
||||
TEST(networkTools, getNetworkInterfacesIPv4Or6)
|
||||
{
|
||||
for ( const auto& kv : kiwix::getNetworkInterfacesIPv4Or6() ) {
|
||||
std::cout << kv.first << " : IPv4 addr = " << kv.second.addr
|
||||
<< " ; IPv6 addr = " << kv.second.addr6
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(networkTools, getNetworkInterfaces)
|
||||
{
|
||||
for ( const auto& kv : kiwix::getNetworkInterfaces() ) {
|
||||
std::cout << kv.first << " : IPv4 addr = " << kv.second << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(networkTools, getBestPublicIps)
|
||||
{
|
||||
using kiwix::getBestPublicIps;
|
||||
using kiwix::getBestPublicIp;
|
||||
using kiwix::IpMode;
|
||||
|
||||
std::cout << "getBestPublicIps(true) : [" << getBestPublicIps(IpMode::ipv4).addr << ", " << getBestPublicIps(IpMode::ipv4).addr6 << "]" << std::endl;
|
||||
std::cout << "getBestPublicIps(false) : [" << getBestPublicIps(IpMode::ipv6).addr << ", " << getBestPublicIps(IpMode::ipv6).addr6 << "]" << std::endl;
|
||||
std::cout << "getBestPublicIps(false) : [" << getBestPublicIps(IpMode::all).addr << ", " << getBestPublicIps(IpMode::all).addr6 << "]" << std::endl;
|
||||
std::cout << "getBestPublicIp() : " << getBestPublicIp() << std::endl;
|
||||
}
|
||||
|
||||
153
test/server.cpp
153
test/server.cpp
@@ -61,9 +61,9 @@ const ResourceCollection resources200Compressible{
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/i18n.js" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/i18n.js?cacheid=071abc9a" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/index.css" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.css?cacheid=ae79e41a" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.css?cacheid=1e78e7cf" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/index.js" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.js?cacheid=8f4b6a1e" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.js?cacheid=ce19da2a" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/iso6391To3.js" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/iso6391To3.js?cacheid=ecde2bb3" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/isotope.pkgd.min.js" },
|
||||
@@ -73,9 +73,9 @@ const ResourceCollection resources200Compressible{
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/mustache.min.js" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/mustache.min.js?cacheid=bd23c4fb" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/taskbar.css" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/taskbar.css?cacheid=80d56607" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/taskbar.css?cacheid=e014a885" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/viewer.js" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/viewer.js?cacheid=aca897b0" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/viewer.js?cacheid=5fc4badf" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/fonts/Poppins.ttf" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/fonts/Poppins.ttf?cacheid=af705837" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/fonts/Roboto.ttf" },
|
||||
@@ -84,7 +84,7 @@ const ResourceCollection resources200Compressible{
|
||||
// TODO: implement cache management of i18n resources
|
||||
//{ STATIC_CONTENT, "/ROOT%23%3F/skin/i18n/test.json?cacheid=unknown" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/languages.js" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/languages.js?cacheid=ee7d95b5" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/languages.js?cacheid=9ccd43fd" },
|
||||
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/catalog/search" },
|
||||
|
||||
@@ -114,8 +114,6 @@ const ResourceCollection resources200Uncompressible{
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/caret.png?cacheid=22b942b4" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/download.png" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/download.png?cacheid=a39aa502" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/download-white.svg" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/download-white.svg?cacheid=079ab989"},
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/favicon/android-chrome-192x192.png" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/favicon/android-chrome-192x192.png?cacheid=bfac158b" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/favicon/android-chrome-512x512.png" },
|
||||
@@ -148,8 +146,6 @@ const ResourceCollection resources200Uncompressible{
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/hash.png?cacheid=f836e872" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/magnet.png" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/magnet.png?cacheid=73b6bddf" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/polyfills.js" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/polyfills.js?cacheid=a0e0343d" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/search-icon.svg" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/search-icon.svg?cacheid=b10ae7ed" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/search_results.css" },
|
||||
@@ -281,7 +277,7 @@ TEST_F(ServerTest, CacheIdsOfStaticResources)
|
||||
{
|
||||
/* url */ "/ROOT%23%3F/",
|
||||
R"EXPECTEDRESULT( href="/ROOT%23%3F/skin/kiwix.css?cacheid=2158fad9"
|
||||
href="/ROOT%23%3F/skin/index.css?cacheid=ae79e41a"
|
||||
href="/ROOT%23%3F/skin/index.css?cacheid=1e78e7cf"
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/ROOT%23%3F/skin/favicon/apple-touch-icon.png?cacheid=f86f8df3">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/ROOT%23%3F/skin/favicon/favicon-32x32.png?cacheid=79ded625">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/ROOT%23%3F/skin/favicon/favicon-16x16.png?cacheid=a986fedc">
|
||||
@@ -289,12 +285,11 @@ R"EXPECTEDRESULT( href="/ROOT%23%3F/skin/kiwix.css?cacheid=2158fad9"
|
||||
<link rel="mask-icon" href="/ROOT%23%3F/skin/favicon/safari-pinned-tab.svg?cacheid=8d487e95" color="#5bbad5">
|
||||
<link rel="shortcut icon" href="/ROOT%23%3F/skin/favicon/favicon.ico?cacheid=92663314">
|
||||
<meta name="msapplication-config" content="/ROOT%23%3F/skin/favicon/browserconfig.xml?cacheid=f29a7c4a">
|
||||
<script type="text/javascript" src="./skin/polyfills.js?cacheid=a0e0343d"></script>
|
||||
<script type="module" src="/ROOT%23%3F/skin/i18n.js?cacheid=071abc9a" defer></script>
|
||||
<script type="text/javascript" src="/ROOT%23%3F/skin/languages.js?cacheid=ee7d95b5" defer></script>
|
||||
<script type="text/javascript" src="/ROOT%23%3F/skin/languages.js?cacheid=9ccd43fd" defer></script>
|
||||
<script src="/ROOT%23%3F/skin/isotope.pkgd.min.js?cacheid=2e48d392" defer></script>
|
||||
<script src="/ROOT%23%3F/skin/iso6391To3.js?cacheid=ecde2bb3"></script>
|
||||
<script type="text/javascript" src="/ROOT%23%3F/skin/index.js?cacheid=8f4b6a1e" defer></script>
|
||||
<script type="text/javascript" src="/ROOT%23%3F/skin/index.js?cacheid=ce19da2a" defer></script>
|
||||
<img src="/ROOT%23%3F/skin/feed.svg?cacheid=055b333f"
|
||||
<img src="/ROOT%23%3F/skin/langSelector.svg?cacheid=00b59961"
|
||||
)EXPECTEDRESULT"
|
||||
@@ -312,8 +307,7 @@ R"EXPECTEDRESULT( background-image: url('../skin/search-icon.svg?cacheid=b10a
|
||||
},
|
||||
{
|
||||
/* url */ "/ROOT%23%3F/skin/index.js",
|
||||
R"EXPECTEDRESULT( <img src="${root}/skin/download-white.svg?cacheid=079ab989">
|
||||
<img src="${root}/skin/download.png?cacheid=a39aa502" alt="${$t("direct-download-alt-text")}" />
|
||||
R"EXPECTEDRESULT( <img src="${root}/skin/download.png?cacheid=a39aa502" alt="${$t("direct-download-alt-text")}" />
|
||||
<img src="${root}/skin/hash.png?cacheid=f836e872" alt="${$t("hash-download-alt-text")}" />
|
||||
<img src="${root}/skin/magnet.png?cacheid=73b6bddf" alt="${$t("magnet-alt-text")}" />
|
||||
<img src="${root}/skin/bittorrent.png?cacheid=4f5c6882" alt="${$t("torrent-download-alt-text")}" />
|
||||
@@ -322,12 +316,11 @@ R"EXPECTEDRESULT( <img src="${root}/skin/download-white.svg?cac
|
||||
{
|
||||
/* url */ "/ROOT%23%3F/viewer",
|
||||
R"EXPECTEDRESULT( <link type="text/css" href="./skin/kiwix.css?cacheid=2158fad9" rel="Stylesheet" />
|
||||
<link type="text/css" href="./skin/taskbar.css?cacheid=80d56607" rel="Stylesheet" />
|
||||
<link type="text/css" href="./skin/taskbar.css?cacheid=e014a885" rel="Stylesheet" />
|
||||
<link type="text/css" href="./skin/autoComplete/css/autoComplete.css?cacheid=ef30cd42" rel="Stylesheet" />
|
||||
<script type="text/javascript" src="./skin/polyfills.js?cacheid=a0e0343d"></script>
|
||||
<script type="module" src="./skin/i18n.js?cacheid=071abc9a" defer></script>
|
||||
<script type="text/javascript" src="./skin/languages.js?cacheid=ee7d95b5" defer></script>
|
||||
<script type="text/javascript" src="./skin/viewer.js?cacheid=aca897b0" defer></script>
|
||||
<script type="text/javascript" src="./skin/languages.js?cacheid=9ccd43fd" defer></script>
|
||||
<script type="text/javascript" src="./skin/viewer.js?cacheid=5fc4badf" defer></script>
|
||||
<script type="text/javascript" src="./skin/autoComplete/autoComplete.min.js?cacheid=1191aaaf"></script>
|
||||
const blankPageUrl = root + "/skin/blank.html?cacheid=6b1fa032";
|
||||
<label for="kiwix_button_show_toggle"><img src="./skin/caret.png?cacheid=22b942b4" alt=""></label>
|
||||
@@ -359,57 +352,6 @@ R"EXPECTEDRESULT( <link type="text/css" href="/ROOT%23%3F/skin/search_results
|
||||
}
|
||||
}
|
||||
|
||||
std::string getCacheIdFromUrl(const std::string& url)
|
||||
{
|
||||
const std::string q("?cacheid=");
|
||||
const auto i = url.find(q);
|
||||
return i == std::string::npos ? "" : url.substr(i + q.size());
|
||||
}
|
||||
|
||||
std::string runExternalCmdAndGetItsOutput(const std::string& cmd)
|
||||
{
|
||||
std::string cmdOutput;
|
||||
|
||||
#ifdef _WIN32
|
||||
#define popen _popen
|
||||
#define pclose _pclose
|
||||
#endif
|
||||
|
||||
if (FILE* pPipe = popen(cmd.c_str(), "r"))
|
||||
{
|
||||
char buf[128];
|
||||
while (fgets(buf, 128, pPipe)) {
|
||||
cmdOutput += std::string(buf, buf+128);
|
||||
}
|
||||
|
||||
pclose(pPipe);
|
||||
}
|
||||
|
||||
return cmdOutput;
|
||||
}
|
||||
|
||||
std::string getSha1OfResponseData(const std::string& url)
|
||||
{
|
||||
const std::string pythonScript =
|
||||
"import urllib.request as req; "
|
||||
"import hashlib; "
|
||||
"print(hashlib.sha1(req.urlopen('" + url + "').read()).hexdigest())";
|
||||
const std::string cmd = "python3 -c \"" + pythonScript + "\"";
|
||||
return runExternalCmdAndGetItsOutput(cmd);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, CacheIdsOfStaticResourcesMatchTheSha1HashOfResourceContent)
|
||||
{
|
||||
for ( const Resource& res : all200Resources() ) {
|
||||
if ( res.kind == STATIC_CONTENT ) {
|
||||
const TestContext ctx{ {"url", res.url} };
|
||||
const std::string fullUrl = "http://localhost:" + std::to_string(SERVER_PORT) + res.url;
|
||||
const std::string sha1 = getSha1OfResponseData(fullUrl);
|
||||
ASSERT_EQ(sha1.substr(0, 8), getCacheIdFromUrl(res.url)) << ctx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char* urls400[] = {
|
||||
"/ROOT%23%3F/search",
|
||||
"/ROOT%23%3F/search?content=zimfile",
|
||||
@@ -1191,32 +1133,17 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "bn",
|
||||
"self_name": "বাংলা",
|
||||
"translation_count": 14
|
||||
},
|
||||
{
|
||||
"iso_code": "br",
|
||||
"self_name": "brezhoneg",
|
||||
"translation_count": 35
|
||||
"translation_count": 12
|
||||
},
|
||||
{
|
||||
"iso_code": "cs",
|
||||
"self_name": "Čeština",
|
||||
"translation_count": 25
|
||||
},
|
||||
{
|
||||
"iso_code": "dag",
|
||||
"self_name": "Silimiinsili",
|
||||
"translation_count": 48
|
||||
},
|
||||
{
|
||||
"iso_code": "de",
|
||||
"self_name": "Deutsch",
|
||||
"translation_count": 57
|
||||
},
|
||||
{
|
||||
"iso_code": "el",
|
||||
"self_name": "Αγγλικά",
|
||||
"translation_count": 23
|
||||
"translation_count": 49
|
||||
},
|
||||
{
|
||||
"iso_code": "en",
|
||||
@@ -1226,27 +1153,22 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "es",
|
||||
"self_name": "español",
|
||||
"translation_count": 49
|
||||
"translation_count": 48
|
||||
},
|
||||
{
|
||||
"iso_code": "fi",
|
||||
"self_name": "suomi",
|
||||
"translation_count": 29
|
||||
"translation_count": 22
|
||||
},
|
||||
{
|
||||
"iso_code": "fr",
|
||||
"self_name": "Français",
|
||||
"translation_count": 57
|
||||
},
|
||||
{
|
||||
"iso_code": "ha",
|
||||
"self_name": "Turanci",
|
||||
"translation_count": 57
|
||||
"translation_count": 52
|
||||
},
|
||||
{
|
||||
"iso_code": "he",
|
||||
"self_name": "עברית",
|
||||
"translation_count": 57
|
||||
"translation_count": 52
|
||||
},
|
||||
{
|
||||
"iso_code": "hi",
|
||||
@@ -1261,17 +1183,12 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "ia",
|
||||
"self_name": "interlingua",
|
||||
"translation_count": 57
|
||||
},
|
||||
{
|
||||
"iso_code": "ig",
|
||||
"self_name": "Bekee",
|
||||
"translation_count": 57
|
||||
"translation_count": 49
|
||||
},
|
||||
{
|
||||
"iso_code": "it",
|
||||
"self_name": "italiano",
|
||||
"translation_count": 38
|
||||
"translation_count": 29
|
||||
},
|
||||
{
|
||||
"iso_code": "ja",
|
||||
@@ -1281,7 +1198,7 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "ko",
|
||||
"self_name": "한국어",
|
||||
"translation_count": 15
|
||||
"translation_count": 13
|
||||
},
|
||||
{
|
||||
"iso_code": "ku-latn",
|
||||
@@ -1296,7 +1213,7 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "mk",
|
||||
"self_name": "македонски",
|
||||
"translation_count": 57
|
||||
"translation_count": 52
|
||||
},
|
||||
{
|
||||
"iso_code": "ms",
|
||||
@@ -1321,17 +1238,12 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "pl",
|
||||
"self_name": "Polski",
|
||||
"translation_count": 31
|
||||
},
|
||||
{
|
||||
"iso_code": "pt-br",
|
||||
"self_name": "Português",
|
||||
"translation_count": 35
|
||||
"translation_count": 24
|
||||
},
|
||||
{
|
||||
"iso_code": "ru",
|
||||
"self_name": "русский",
|
||||
"translation_count": 57
|
||||
"translation_count": 45
|
||||
},
|
||||
{
|
||||
"iso_code": "sc",
|
||||
@@ -1351,7 +1263,7 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "sl",
|
||||
"self_name": "slovenščina",
|
||||
"translation_count": 57
|
||||
"translation_count": 52
|
||||
},
|
||||
{
|
||||
"iso_code": "sq",
|
||||
@@ -1361,12 +1273,7 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "sv",
|
||||
"self_name": "Svenska",
|
||||
"translation_count": 57
|
||||
},
|
||||
{
|
||||
"iso_code": "sw",
|
||||
"self_name": "Kiswahili",
|
||||
"translation_count": 57
|
||||
"translation_count": 52
|
||||
},
|
||||
{
|
||||
"iso_code": "te",
|
||||
@@ -1376,17 +1283,17 @@ R"EXPECTEDRESPONSE(const uiLanguages = [
|
||||
{
|
||||
"iso_code": "tr",
|
||||
"self_name": "Türkçe",
|
||||
"translation_count": 57
|
||||
"translation_count": 25
|
||||
},
|
||||
{
|
||||
"iso_code": "zh-hans",
|
||||
"self_name": "简体中文",
|
||||
"translation_count": 57
|
||||
"self_name": "英语",
|
||||
"translation_count": 16
|
||||
},
|
||||
{
|
||||
"iso_code": "zh-hant",
|
||||
"self_name": "繁體中文",
|
||||
"translation_count": 57
|
||||
"translation_count": 52
|
||||
}
|
||||
])EXPECTEDRESPONSE");
|
||||
}
|
||||
|
||||
@@ -194,23 +194,6 @@ struct SearchResult
|
||||
SearchResult{LINK, TITLE, SNIPPET, BOOK_TITLE, WORDCOUNT}
|
||||
|
||||
|
||||
const SearchResult SEARCH_RESULT_FOR_TRAVEL_IN_RAYCHARLESZIM {
|
||||
/*link*/ "/ROOT%23%3F/content/zimfile/A/If_You_Go_Away",
|
||||
/*title*/ "If You Go Away",
|
||||
/*snippet*/ R"SNIPPET(...<b>Travel</b> On" (1965) "If You Go Away" (1966) "Walk Away" (1967) Damita Jo reached #10 on the Adult Contemporary chart and #68 on the Billboard Hot 100 in 1966 for her version of the song. Terry Jacks recorded a version of the song which was released as a single in 1974 and reached #29 on the Adult Contemporary chart, #68 on the Billboard Hot 100, and went to #8 in the UK. The complex melody is partly derivative of classical music - the poignant "But if you stay..." passage comes from Franz Liszt's......)SNIPPET",
|
||||
/*bookTitle*/ "Ray Charles",
|
||||
/*wordCount*/ "204"
|
||||
};
|
||||
|
||||
|
||||
const SearchResult SEARCH_RESULT_FOR_TRAVEL_IN_EXAMPLEZIM {
|
||||
/*link*/ "/ROOT%23%3F/content/example/Wikibooks.html",
|
||||
/*title*/ "Wikibooks",
|
||||
/*snippet*/ R"SNIPPET(...<b>Travel</b> guide Wikidata Knowledge database Commons Media repository Meta Coordination MediaWiki MediaWiki software Phabricator MediaWiki bug tracker Wikimedia Labs MediaWiki development The Wikimedia Foundation is a non-profit organization that depends on your voluntarism and donations to operate. If you find Wikibooks or other projects hosted by the Wikimedia Foundation useful, please volunteer or make a donation. Your donations primarily helps to purchase server equipment, launch new projects......)SNIPPET",
|
||||
/*bookTitle*/ "Wikibooks",
|
||||
/*wordCount*/ "538"
|
||||
};
|
||||
|
||||
|
||||
const std::vector<SearchResult> LARGE_SEARCH_RESULTS = {
|
||||
SEARCH_RESULT(
|
||||
@@ -1359,8 +1342,21 @@ TEST(ServerSearchTest, searchResults)
|
||||
/* totalResultCount */ 2,
|
||||
/* firstResultIndex */ 1,
|
||||
/* results */ {
|
||||
SEARCH_RESULT_FOR_TRAVEL_IN_RAYCHARLESZIM,
|
||||
SEARCH_RESULT_FOR_TRAVEL_IN_EXAMPLEZIM
|
||||
SEARCH_RESULT(
|
||||
/*link*/ "/ROOT%23%3F/content/zimfile/A/If_You_Go_Away",
|
||||
/*title*/ "If You Go Away",
|
||||
/*snippet*/ R"SNIPPET(...<b>Travel</b> On" (1965) "If You Go Away" (1966) "Walk Away" (1967) Damita Jo reached #10 on the Adult Contemporary chart and #68 on the Billboard Hot 100 in 1966 for her version of the song. Terry Jacks recorded a version of the song which was released as a single in 1974 and reached #29 on the Adult Contemporary chart, #68 on the Billboard Hot 100, and went to #8 in the UK. The complex melody is partly derivative of classical music - the poignant "But if you stay..." passage comes from Franz Liszt's......)SNIPPET",
|
||||
/*bookTitle*/ "Ray Charles",
|
||||
/*wordCount*/ "204"
|
||||
),
|
||||
|
||||
SEARCH_RESULT(
|
||||
/*link*/ "/ROOT%23%3F/content/example/Wikibooks.html",
|
||||
/*title*/ "Wikibooks",
|
||||
/*snippet*/ R"SNIPPET(...<b>Travel</b> guide Wikidata Knowledge database Commons Media repository Meta Coordination MediaWiki MediaWiki software Phabricator MediaWiki bug tracker Wikimedia Labs MediaWiki development The Wikimedia Foundation is a non-profit organization that depends on your voluntarism and donations to operate. If you find Wikibooks or other projects hosted by the Wikimedia Foundation useful, please volunteer or make a donation. Your donations primarily helps to purchase server equipment, launch new projects......)SNIPPET",
|
||||
/*bookTitle*/ "Wikibooks",
|
||||
/*wordCount*/ "538"
|
||||
)
|
||||
},
|
||||
/* pagination */ {}
|
||||
},
|
||||
@@ -1373,72 +1369,25 @@ TEST(ServerSearchTest, searchResults)
|
||||
/* totalResultCount */ 2,
|
||||
/* firstResultIndex */ 1,
|
||||
/* results */ {
|
||||
SEARCH_RESULT_FOR_TRAVEL_IN_RAYCHARLESZIM,
|
||||
SEARCH_RESULT_FOR_TRAVEL_IN_EXAMPLEZIM
|
||||
SEARCH_RESULT(
|
||||
/*link*/ "/ROOT%23%3F/content/zimfile/A/If_You_Go_Away",
|
||||
/*title*/ "If You Go Away",
|
||||
/*snippet*/ R"SNIPPET(...<b>Travel</b> On" (1965) "If You Go Away" (1966) "Walk Away" (1967) Damita Jo reached #10 on the Adult Contemporary chart and #68 on the Billboard Hot 100 in 1966 for her version of the song. Terry Jacks recorded a version of the song which was released as a single in 1974 and reached #29 on the Adult Contemporary chart, #68 on the Billboard Hot 100, and went to #8 in the UK. The complex melody is partly derivative of classical music - the poignant "But if you stay..." passage comes from Franz Liszt's......)SNIPPET",
|
||||
/*bookTitle*/ "Ray Charles",
|
||||
/*wordCount*/ "204"
|
||||
),
|
||||
|
||||
SEARCH_RESULT(
|
||||
/*link*/ "/ROOT%23%3F/content/example/Wikibooks.html",
|
||||
/*title*/ "Wikibooks",
|
||||
/*snippet*/ R"SNIPPET(...<b>Travel</b> guide Wikidata Knowledge database Commons Media repository Meta Coordination MediaWiki MediaWiki software Phabricator MediaWiki bug tracker Wikimedia Labs MediaWiki development The Wikimedia Foundation is a non-profit organization that depends on your voluntarism and donations to operate. If you find Wikibooks or other projects hosted by the Wikimedia Foundation useful, please volunteer or make a donation. Your donations primarily helps to purchase server equipment, launch new projects......)SNIPPET",
|
||||
/*bookTitle*/ "Wikibooks",
|
||||
/*wordCount*/ "538"
|
||||
)
|
||||
},
|
||||
/* pagination */ {}
|
||||
},
|
||||
|
||||
// books.name filters by the name of the ZIM file
|
||||
{
|
||||
/* query */ "pattern=travel"
|
||||
"&books.name=zimfile",
|
||||
/* start */ 0,
|
||||
/* resultsPerPage */ 10,
|
||||
/* totalResultCount */ 1,
|
||||
/* firstResultIndex */ 1,
|
||||
/* results */ {
|
||||
SEARCH_RESULT_FOR_TRAVEL_IN_RAYCHARLESZIM
|
||||
},
|
||||
|
||||
/* pagination */ {}
|
||||
},
|
||||
|
||||
// books.name filters by the name of the ZIM file
|
||||
{
|
||||
/* query */ "pattern=travel"
|
||||
"&books.name=example",
|
||||
/* start */ 0,
|
||||
/* resultsPerPage */ 10,
|
||||
/* totalResultCount */ 1,
|
||||
/* firstResultIndex */ 1,
|
||||
/* results */ {
|
||||
SEARCH_RESULT_FOR_TRAVEL_IN_EXAMPLEZIM
|
||||
},
|
||||
|
||||
/* pagination */ {}
|
||||
},
|
||||
|
||||
// books.filter.name filters by the book name
|
||||
{
|
||||
/* query */ "pattern=travel"
|
||||
"&books.filter.name=wikipedia_en_ray_charles",
|
||||
/* start */ 0,
|
||||
/* resultsPerPage */ 10,
|
||||
/* totalResultCount */ 1,
|
||||
/* firstResultIndex */ 1,
|
||||
/* results */ {
|
||||
SEARCH_RESULT_FOR_TRAVEL_IN_RAYCHARLESZIM
|
||||
},
|
||||
|
||||
/* pagination */ {}
|
||||
},
|
||||
|
||||
// books.filter.name filters by the book name
|
||||
{
|
||||
/* query */ "pattern=travel"
|
||||
"&books.filter.name=bookname_of_example_zim",
|
||||
/* start */ 0,
|
||||
/* resultsPerPage */ 10,
|
||||
/* totalResultCount */ 1,
|
||||
/* firstResultIndex */ 1,
|
||||
/* results */ {
|
||||
SEARCH_RESULT_FOR_TRAVEL_IN_EXAMPLEZIM
|
||||
},
|
||||
|
||||
/* pagination */ {}
|
||||
},
|
||||
|
||||
// Adding a book (without match) doesn't change the results
|
||||
{
|
||||
/* query */ "pattern=jazz"
|
||||
@@ -1572,10 +1521,7 @@ TEST(ServerSearchTest, searchResults)
|
||||
}
|
||||
}
|
||||
|
||||
std::string invalidRequestErrorHtml(std::string url,
|
||||
std::string errorMsgId,
|
||||
std::string errorMsgParamsJSON,
|
||||
std::string errorText)
|
||||
std::string expectedConfusionOfTonguesErrorHtml(std::string url)
|
||||
{
|
||||
return R"(<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
@@ -1584,7 +1530,7 @@ std::string invalidRequestErrorHtml(std::string url,
|
||||
<title>Invalid request</title>
|
||||
<script>
|
||||
window.KIWIX_RESPONSE_TEMPLATE = )" + ERROR_HTML_TEMPLATE_JS_STRING + R"(;
|
||||
window.KIWIX_RESPONSE_DATA = { "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "400-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "400-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "invalid-request", "params" : { "url" : ")" + url + R"(" } } }, { "p" : { "msgid" : ")" + errorMsgId + R"(", "params" : )" + errorMsgParamsJSON + R"( } } ] };
|
||||
window.KIWIX_RESPONSE_DATA = { "CSS_URL" : false, "PAGE_HEADING" : { "msgid" : "400-page-heading", "params" : { } }, "PAGE_TITLE" : { "msgid" : "400-page-title", "params" : { } }, "details" : [ { "p" : { "msgid" : "invalid-request", "params" : { "url" : ")" + url + R"(" } } }, { "p" : { "msgid" : "confusion-of-tongues", "params" : { } } } ] };
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
@@ -1593,30 +1539,19 @@ std::string invalidRequestErrorHtml(std::string url,
|
||||
The requested URL ")" + url + R"(" is not a valid request.
|
||||
</p>
|
||||
<p>
|
||||
)" + errorText + R"(
|
||||
Two or more books in different languages would participate in search, which may lead to confusing results.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
)";
|
||||
}
|
||||
|
||||
const char CONFUSION_OF_TONGUES_ERROR_TEXT[] = "Two or more books in different languages would participate in search, which may lead to confusing results.";
|
||||
|
||||
std::string expectedConfusionOfTonguesErrorHtml(std::string url)
|
||||
{
|
||||
return invalidRequestErrorHtml(url,
|
||||
/* errorMsgId */ "confusion-of-tongues",
|
||||
/* errorMsgParamsJSON */ "{ }",
|
||||
/* errorText */ CONFUSION_OF_TONGUES_ERROR_TEXT
|
||||
);
|
||||
}
|
||||
|
||||
std::string expectedConfusionOfTonguesErrorXml(std::string url)
|
||||
{
|
||||
return R"(<?xml version="1.0" encoding="UTF-8">
|
||||
<error>Invalid request</error>
|
||||
<detail>The requested URL ")" + url + R"(" is not a valid request.</detail>
|
||||
<detail>)" + CONFUSION_OF_TONGUES_ERROR_TEXT + R"(</detail>
|
||||
<detail>Two or more books in different languages would participate in search, which may lead to confusing results.</detail>
|
||||
)";
|
||||
}
|
||||
|
||||
@@ -1656,50 +1591,3 @@ TEST(ServerSearchTest, searchInMultilanguageBookSetIsDenied)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string noSuchBookErrorHtml(std::string url, std::string bookName)
|
||||
{
|
||||
return invalidRequestErrorHtml(url,
|
||||
/* errorMsgId */ "no-such-book",
|
||||
/* errorMsgParamsJSON */ "{ \"BOOK_NAME\" : \"" + bookName + "\" }",
|
||||
/* errorText */ "No such book: " + bookName
|
||||
);
|
||||
}
|
||||
|
||||
std::string noBookFoundErrorHtml(std::string url)
|
||||
{
|
||||
return invalidRequestErrorHtml(url,
|
||||
/* errorMsgId */ "no-book-found",
|
||||
/* errorMsgParamsJSON */ "{ }",
|
||||
/* errorText */ "No book matches selection criteria"
|
||||
);
|
||||
}
|
||||
|
||||
TEST(ServerSearchTest, bookSelectionNegativeTests)
|
||||
{
|
||||
ZimFileServer zfs(SERVER_PORT, ZimFileServer::DEFAULT_OPTIONS,
|
||||
"./test/lib_for_server_search_test.xml");
|
||||
|
||||
{
|
||||
// books.name (unlike books.filter.name) DOESN'T consider the book name
|
||||
// and reports an error (surprise!)
|
||||
const std::string bookName = "wikipedia_en_ray_charles";
|
||||
const std::string q = "pattern=travel&books.name=" + bookName;
|
||||
const std::string url = "/ROOT%23%3F/search?" + q;
|
||||
|
||||
const auto r = zfs.GET(url.c_str());
|
||||
EXPECT_EQ(r->status, 400);
|
||||
EXPECT_EQ(r->body, noSuchBookErrorHtml(url, bookName));
|
||||
}
|
||||
|
||||
{
|
||||
// books.filter.name (unlike books.name) DOESN'T consider the ZIM file name
|
||||
// and reports an error (differently from books.name)
|
||||
const std::string q = "pattern=travel&books.filter.name=zimfile";
|
||||
const std::string url = "/ROOT%23%3F/search?" + q;
|
||||
|
||||
const auto r = zfs.GET(url.c_str());
|
||||
EXPECT_EQ(r->status, 400);
|
||||
EXPECT_EQ(r->body, noBookFoundErrorHtml(url));
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user