mirror of
https://github.com/kiwix/libkiwix.git
synced 2025-12-24 06:57:59 -05:00
Compare commits
217 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3bf70f4315 | ||
|
|
a8130bd4f2 | ||
|
|
a35d95207e | ||
|
|
b18c8e079e | ||
|
|
4e9f563b45 | ||
|
|
7ece383004 | ||
|
|
4ca9558e30 | ||
|
|
89582d526e | ||
|
|
5913f7efab | ||
|
|
1367c5630f | ||
|
|
0ca27d8edf | ||
|
|
60c6cc35d5 | ||
|
|
6b783b3998 | ||
|
|
55515f2fc6 | ||
|
|
7fe07c65fd | ||
|
|
ee204a9b5e | ||
|
|
e743e04b94 | ||
|
|
cf8e8b94eb | ||
|
|
d9557da813 | ||
|
|
c19b983914 | ||
|
|
f997fdb232 | ||
|
|
f0b037f37f | ||
|
|
4d307e18eb | ||
|
|
e05bd8efd6 | ||
|
|
71462696bd | ||
|
|
fb79cde729 | ||
|
|
c986290d83 | ||
|
|
af9afab821 | ||
|
|
14af7b756e | ||
|
|
ff605873ed | ||
|
|
6c49c7ee0a | ||
|
|
157d1664cf | ||
|
|
6f92b7e120 | ||
|
|
fd62acd232 | ||
|
|
1cdf830217 | ||
|
|
0b48ab20bb | ||
|
|
081a2b2fa6 | ||
|
|
bf93d10cde | ||
|
|
05ef5d5f51 | ||
|
|
4cdae3ca98 | ||
|
|
7dcaeed33a | ||
|
|
f52b220d01 | ||
|
|
50a850f3a9 | ||
|
|
886ae17274 | ||
|
|
a9b6d481cc | ||
|
|
85d6daabac | ||
|
|
5f1918d005 | ||
|
|
16bd79fa1b | ||
|
|
c2ebdefe8d | ||
|
|
37032892a4 | ||
|
|
6b43438b74 | ||
|
|
7301bf89bb | ||
|
|
ff23b28e7c | ||
|
|
931e95f391 | ||
|
|
f7571b5b69 | ||
|
|
801ad18a89 | ||
|
|
67a347c0c4 | ||
|
|
693905eb68 | ||
|
|
f3e79c6b4c | ||
|
|
52f207eaa6 | ||
|
|
67294217a8 | ||
|
|
d111a40ce8 | ||
|
|
0c5bb3fcfe | ||
|
|
3fba8c20a0 | ||
|
|
54db6049b7 | ||
|
|
81c38d6b2b | ||
|
|
e6a86c02ae | ||
|
|
a0f7f32570 | ||
|
|
c39fce8839 | ||
|
|
bd2d0bc489 | ||
|
|
de37489c53 | ||
|
|
2a35a86de6 | ||
|
|
0a30a77c08 | ||
|
|
1a99bacfe3 | ||
|
|
2bf35f1651 | ||
|
|
48cc277468 | ||
|
|
d8498fd655 | ||
|
|
7ec8e33b83 | ||
|
|
8eb2d0c2a1 | ||
|
|
5995cc276d | ||
|
|
94c2ab4395 | ||
|
|
15179db945 | ||
|
|
0f07cab920 | ||
|
|
4f8b397081 | ||
|
|
2df74d9755 | ||
|
|
0b25492edc | ||
|
|
5f0a9d0b08 | ||
|
|
54f5dbbd35 | ||
|
|
95a5cde359 | ||
|
|
3d08ef43f2 | ||
|
|
381ff186cb | ||
|
|
e32fa28a6c | ||
|
|
1842e8f51c | ||
|
|
bfa51c2d87 | ||
|
|
81e781133d | ||
|
|
9ec7757efe | ||
|
|
7bd7ec4937 | ||
|
|
14d8583c83 | ||
|
|
a004d96cd7 | ||
|
|
21c6de2f80 | ||
|
|
a8e78f27e1 | ||
|
|
6c7ab6ff54 | ||
|
|
659ee6ba71 | ||
|
|
83ee8dec15 | ||
|
|
87cbbed9e3 | ||
|
|
a058520628 | ||
|
|
1ef5ebfb52 | ||
|
|
bbc06931ad | ||
|
|
2d3bf9b981 | ||
|
|
fd80f2a89f | ||
|
|
abb3dec700 | ||
|
|
80fcbceeb3 | ||
|
|
9a893a854e | ||
|
|
b0f65a02f2 | ||
|
|
9bf6d0621f | ||
|
|
fcadacb0ad | ||
|
|
8f07689c57 | ||
|
|
0630298fb9 | ||
|
|
9f61301423 | ||
|
|
9c101daad7 | ||
|
|
0586ef6d41 | ||
|
|
8fc42558d3 | ||
|
|
5d98a6964e | ||
|
|
9d8bf8ddcb | ||
|
|
4c8aad0e68 | ||
|
|
eb6f0f710c | ||
|
|
d9d2a702ef | ||
|
|
cbf5bd57a8 | ||
|
|
2bf6b04726 | ||
|
|
12a0660342 | ||
|
|
ab478ed4cf | ||
|
|
533541cf45 | ||
|
|
bab0fd3d4f | ||
|
|
375792a686 | ||
|
|
7155c788e2 | ||
|
|
3ac926b39b | ||
|
|
ea3444e2fe | ||
|
|
083c06d170 | ||
|
|
286fbba9f6 | ||
|
|
778603e1bf | ||
|
|
411d4b0779 | ||
|
|
bce0e8c37c | ||
|
|
507002d34e | ||
|
|
34a8144f51 | ||
|
|
4709a42f4f | ||
|
|
d04d9bf7f3 | ||
|
|
412f0d9c61 | ||
|
|
0ad8bf45fc | ||
|
|
3ab3ffe3ea | ||
|
|
064d5f3fa6 | ||
|
|
46626a3f98 | ||
|
|
90ba27fbab | ||
|
|
76c293e403 | ||
|
|
78e57c1a51 | ||
|
|
7c49dc6af9 | ||
|
|
2e60a088ab | ||
|
|
9ab0f825f4 | ||
|
|
411ca28598 | ||
|
|
cc2b5e63ca | ||
|
|
ea29557a33 | ||
|
|
b53f531f2b | ||
|
|
bd74ebed1e | ||
|
|
1632e9c55b | ||
|
|
6dd6a5dd80 | ||
|
|
b65b90d78c | ||
|
|
6a975994cc | ||
|
|
3ae596783d | ||
|
|
ce6e956434 | ||
|
|
f560a1f815 | ||
|
|
d14ba0c2e8 | ||
|
|
34257cfc1f | ||
|
|
8f990feabb | ||
|
|
fff2524ee2 | ||
|
|
a756e7f8f3 | ||
|
|
bc257d2d6d | ||
|
|
7275f9b8e3 | ||
|
|
b63827196c | ||
|
|
2881face70 | ||
|
|
77ba09c310 | ||
|
|
49aa0fbb9f | ||
|
|
7846b45bef | ||
|
|
7e26f3502d | ||
|
|
47a866400c | ||
|
|
4b6c26bd0b | ||
|
|
94c47383a9 | ||
|
|
6bcecc2677 | ||
|
|
e20c3520dc | ||
|
|
24fb1868d7 | ||
|
|
82afb804e1 | ||
|
|
0951546356 | ||
|
|
f09c739c1f | ||
|
|
df9ddd5451 | ||
|
|
fe513951d3 | ||
|
|
7f0d509a88 | ||
|
|
54f671b2f1 | ||
|
|
c2c89c6c86 | ||
|
|
75652d0e9f | ||
|
|
6535dc2e38 | ||
|
|
6b2f768c8f | ||
|
|
6099c3113f | ||
|
|
21d8a6952f | ||
|
|
b84f4e03ea | ||
|
|
ff21a095cb | ||
|
|
fa091a19c6 | ||
|
|
e3ba9fa5cc | ||
|
|
560f67522f | ||
|
|
11118efd84 | ||
|
|
9c2bc6affc | ||
|
|
4fe31a20e3 | ||
|
|
0f99c1ad20 | ||
|
|
91db055d86 | ||
|
|
5540149e2b | ||
|
|
7c7e351d34 | ||
|
|
c5051b343e | ||
|
|
02a0d592f9 | ||
|
|
071e9e3fec | ||
|
|
4a01303438 |
4
.github/FUNDING.yml
vendored
4
.github/FUNDING.yml
vendored
@@ -1,6 +1,6 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
github: kiwix
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
@@ -9,4 +9,4 @@ community_bridge: # Replace with a single Community Bridge project-name e.g., cl
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: https://kiwix.org/support-us/
|
||||
custom: # https://kiwix.org/support-us/
|
||||
|
||||
163
.github/workflows/ci.yml
vendored
Normal file
163
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,163 @@
|
||||
name: CI
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
Macos:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v1
|
||||
- name: Setup python 3.5
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: '3.5'
|
||||
- name: Install packages
|
||||
uses: mstksg/get-package@v1
|
||||
with:
|
||||
brew: gcovr pkg-config ninja
|
||||
- name: Install python modules
|
||||
run: pip3 install meson==0.49.2 pytest
|
||||
- name: Install deps
|
||||
shell: bash
|
||||
run: |
|
||||
ARCHIVE_NAME=deps2_osx_native_dyn_kiwix-lib.tar.xz
|
||||
wget -O- http://tmp.kiwix.org/ci/${ARCHIVE_NAME} | tar -xJ -C $HOME
|
||||
- name: Compile
|
||||
shell: bash
|
||||
run: |
|
||||
export PKG_CONFIG_PATH=$HOME/BUILD_native_dyn/INSTALL/lib/pkgconfig
|
||||
export CPPFLAGS="-I$HOME/BUILD_native_dyn/INSTALL/include"
|
||||
meson . build --default-library=shared -Db_coverage=true
|
||||
cd build
|
||||
ninja
|
||||
- name: Test
|
||||
shell: bash
|
||||
run: |
|
||||
export LD_LIBRARY_PATH=$HOME/BUILD_native_dyn/INSTALL/lib:$HOME/BUILD_native_dyn/INSTALL/lib64
|
||||
cd build
|
||||
meson test --verbose
|
||||
ninja coverage
|
||||
env:
|
||||
SKIP_BIG_MEMORY_TEST: 1
|
||||
- name: Publish coverage
|
||||
shell: bash
|
||||
run: |
|
||||
curl https://codecov.io/bash -o codecov.sh
|
||||
bash codecov.sh -n osx_native_dyn -Z
|
||||
rm codecov.sh
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
Linux:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
name:
|
||||
- native_static
|
||||
- native_dyn
|
||||
- native_dyn_bionic
|
||||
- android_arm
|
||||
- android_arm64
|
||||
- win32_static
|
||||
- win32_dyn
|
||||
include:
|
||||
- name: native_static
|
||||
target: native_static
|
||||
image_variant: xenial
|
||||
lib_postfix: '/x86_64-linux-gnu'
|
||||
- name: native_dyn
|
||||
target: native_dyn
|
||||
image_variant: xenial
|
||||
lib_postfix: '/x86_64-linux-gnu'
|
||||
- name: native_dyn_bionic
|
||||
target: native_dyn
|
||||
image_variant: bionic
|
||||
lib_postfix: '/x86_64-linux-gnu'
|
||||
- name: android_arm
|
||||
target: android_arm
|
||||
image_variant: xenial
|
||||
lib_postfix: '/x86_64-linux-gnu'
|
||||
- name: android_arm64
|
||||
target: android_arm64
|
||||
image_variant: xenial
|
||||
lib_postfix: '/x86_64-linux-gnu'
|
||||
- name: win32_static
|
||||
target: win32_static
|
||||
image_variant: f31
|
||||
lib_postfix: '64'
|
||||
- name: win32_dyn
|
||||
target: win32_dyn
|
||||
image_variant: f31
|
||||
lib_postfix: '64'
|
||||
env:
|
||||
HOME: /home/runner
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: "kiwix/kiwix-build_ci:${{matrix.image_variant}}-26"
|
||||
steps:
|
||||
- name: Extract branch name
|
||||
shell: bash
|
||||
run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})"
|
||||
id: extract_branch
|
||||
- name: Checkout code
|
||||
shell: python
|
||||
run: |
|
||||
from subprocess import check_call
|
||||
from os import environ
|
||||
command = [
|
||||
'git', 'clone',
|
||||
'https://github.com/${{github.repository}}',
|
||||
'--depth=1',
|
||||
'--branch', '${{steps.extract_branch.outputs.branch}}'
|
||||
]
|
||||
check_call(command, cwd=environ['HOME'])
|
||||
- name: Install deps
|
||||
shell: bash
|
||||
run: |
|
||||
ARCHIVE_NAME=deps2_${OS_NAME}_${{matrix.target}}_kiwix-lib.tar.xz
|
||||
wget -O- http://tmp.kiwix.org/ci/${ARCHIVE_NAME} | tar -xJ -C /home/runner
|
||||
- name: Compile
|
||||
shell: bash
|
||||
run: |
|
||||
meson --version
|
||||
if [[ "${{matrix.target}}" =~ .*_dyn ]]; then
|
||||
MESON_OPTION="--default-library=shared"
|
||||
else
|
||||
MESON_OPTION="--default-library=static"
|
||||
fi
|
||||
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
|
||||
MESON_OPTION="$MESON_OPTION -Dandroid=true"
|
||||
fi
|
||||
cd $HOME/kiwix-lib
|
||||
meson . build ${MESON_OPTION}
|
||||
cd build
|
||||
ninja
|
||||
env:
|
||||
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: startsWith(matrix.target, 'native_')
|
||||
shell: bash
|
||||
run: |
|
||||
cd $HOME/kiwix-lib/build
|
||||
meson test --verbose
|
||||
ninja coverage
|
||||
env:
|
||||
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
|
||||
shell: bash
|
||||
run: |
|
||||
cd $HOME/kiwix-lib
|
||||
curl https://codecov.io/bash -o codecov.sh
|
||||
bash codecov.sh -n "${OS_NAME}_${{matrix.target}}" -Z
|
||||
rm codecov.sh
|
||||
if: startsWith(matrix.target, 'native_') && matrix.image_variant == 'xenial'
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
78
.github/workflows/package.yml
vendored
Normal file
78
.github/workflows/package.yml
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
name: Packages
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build-deb:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
distro: [ubuntu-groovy, ubuntu-focal, ubuntu-eoan]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
# Determine which PPA we should upload to
|
||||
- name: PPA
|
||||
id: ppa
|
||||
run: |
|
||||
if [[ $REF == refs/tags* ]]
|
||||
then
|
||||
echo "::set-output name=ppa::kiwixteam/release"
|
||||
else
|
||||
echo "::set-output name=ppa::kiwixteam/dev"
|
||||
fi
|
||||
env:
|
||||
REF: ${{ github.ref }}
|
||||
|
||||
- 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@ubuntu-groovy
|
||||
if: matrix.distro == 'ubuntu-groovy'
|
||||
name: Build package for ubuntu-groovy
|
||||
id: build-ubuntu-groovy
|
||||
with:
|
||||
args: --no-sign
|
||||
ppa: ${{ steps.ppa.outputs.ppa }}
|
||||
|
||||
- uses: legoktm/gh-action-build-deb@ubuntu-focal
|
||||
if: matrix.distro == 'ubuntu-focal'
|
||||
name: Build package for ubuntu-focal
|
||||
id: build-ubuntu-focal
|
||||
with:
|
||||
args: --no-sign
|
||||
ppa: ${{ steps.ppa.outputs.ppa }}
|
||||
|
||||
- uses: legoktm/gh-action-build-deb@ubuntu-eoan
|
||||
if: matrix.distro == 'ubuntu-eoan'
|
||||
name: Build package for ubuntu-eoan
|
||||
id: build-ubuntu-eoan
|
||||
with:
|
||||
args: --no-sign
|
||||
ppa: ${{ steps.ppa.outputs.ppa }}
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Packages for ${{ matrix.distro }}
|
||||
path: output
|
||||
|
||||
- uses: legoktm/gh-action-dput@master
|
||||
name: Upload dev package
|
||||
# Only upload on pushes to master
|
||||
if: github.event_name == 'push' && github.event.ref == 'refs/heads/master' && startswith(matrix.distro, 'ubuntu-')
|
||||
with:
|
||||
gpg_key: ${{ secrets.LAUNCHPAD_GPG }}
|
||||
repository: ppa:kiwixteam/dev
|
||||
packages: output/*_source.changes
|
||||
|
||||
- uses: legoktm/gh-action-dput@master
|
||||
name: Upload release package
|
||||
# Only upload on pushes to master or tag
|
||||
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') && startswith(matrix.distro, 'ubuntu-')
|
||||
with:
|
||||
gpg_key: ${{ secrets.LAUNCHPAD_GPG }}
|
||||
repository: ppa:kiwixteam/release
|
||||
packages: output/*_source.changes
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
.idea/
|
||||
*.swp
|
||||
subprojects/googletest-release*
|
||||
*.class
|
||||
|
||||
42
.travis.yml
42
.travis.yml
@@ -1,42 +0,0 @@
|
||||
language: cpp
|
||||
dist: xenial
|
||||
sudo: true
|
||||
cache: ccache
|
||||
before_install:
|
||||
- PATH=$PATH:$HOME/bin
|
||||
install: travis/install_deps.sh
|
||||
script: travis/compile.sh
|
||||
env:
|
||||
matrix:
|
||||
- PLATFORM="native_static"
|
||||
- PLATFORM="native_dyn"
|
||||
- PLATFORM="win32_static"
|
||||
- PLATFORM="win32_dyn"
|
||||
- PLATFORM="android_arm"
|
||||
- PLATFORM="android_arm64"
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- cmake
|
||||
- python3.5
|
||||
- python3-pip
|
||||
- libbz2-dev
|
||||
- ccache
|
||||
- zlib1g-dev
|
||||
- uuid-dev
|
||||
- libctpp2-dev
|
||||
- ctpp2-utils
|
||||
- libmicrohttpd-dev
|
||||
- g++-mingw-w64-i686
|
||||
- gcc-mingw-w64-i686
|
||||
- gcc-mingw-w64-base
|
||||
- mingw-w64-tools
|
||||
- gcovr
|
||||
homebrew:
|
||||
update: true
|
||||
packages:
|
||||
- gcovr
|
||||
matrix:
|
||||
include:
|
||||
- env: PLATFORM="native_dyn"
|
||||
os: osx
|
||||
94
ChangeLog
94
ChangeLog
@@ -1,3 +1,97 @@
|
||||
kiwix-lib 9.3.1
|
||||
===============
|
||||
|
||||
* Fix handling of samba path on windows.
|
||||
* Do not include `kiwix_config.h` in public header.
|
||||
* Fix compilation with libmicrohttpd v0.97.1
|
||||
* Increase default test timeout to 160seconds/test.
|
||||
* Add automatic debian packaging.
|
||||
* Use non-minified version of jquery-ui.js
|
||||
* Pass `-latomic` compile option for sh4 architecture.
|
||||
* Make mesion install `kiwix-compile-resources` man page.
|
||||
|
||||
kiwix-lib 9.3.0
|
||||
===============
|
||||
|
||||
* Add a thread safe method to search suggestions.
|
||||
Old methods are now deprecated.
|
||||
|
||||
kiwix-lib 9.2.3
|
||||
===============
|
||||
|
||||
* Add test on byte-range
|
||||
* Fix compilation on bionic and windows.
|
||||
* Allow building using debian packaged kainjow-mustache
|
||||
* Pass `-latomic` compile option for architectures that need it
|
||||
|
||||
kiwix-lib 9.2.2
|
||||
===============
|
||||
|
||||
* Fix handling on empty content in byte range management (wrong assert)
|
||||
|
||||
kiwix-lib 9.2.1
|
||||
===============
|
||||
|
||||
* Fix support of byte range request.
|
||||
|
||||
kiwix-lib 9.2
|
||||
=============
|
||||
|
||||
* Add tests
|
||||
* Refactoring server code.
|
||||
* [SERVER] Add HEAD, Etag and If-None-Match support.
|
||||
* [SERVER] Compress opds catalog answers.
|
||||
|
||||
kiwix-lib 9.1.2
|
||||
===============
|
||||
|
||||
* Do not use the pathToSave if it is empty.
|
||||
|
||||
kiwix-lib 9.1.1
|
||||
===============
|
||||
|
||||
* Fix the detection of the dataDirectory on windows.
|
||||
|
||||
kiwix-lib 9.1.0
|
||||
===============
|
||||
|
||||
* [JAVA] Add a method to get the size of an article.
|
||||
* Add a new method to the libray to get the book by path.
|
||||
* Add a option to make the server blocks external link.
|
||||
Links are intercepted by js an redirected to a "portail" page.
|
||||
* [ODPS] Correctly handle book's articleCount and mediaCount.
|
||||
|
||||
kiwix-lib 9.0.1
|
||||
===============
|
||||
|
||||
* [JAVA] Use a long to store the offset of an article in the zim file instead
|
||||
of an int.
|
||||
|
||||
kiwix-lib 9.0.0
|
||||
===============
|
||||
|
||||
* [OPDS] Correctly set the id of the OPDS stream.
|
||||
* [OPDS] Do not try to filter the catalog if no filter field is given in the
|
||||
request.
|
||||
* [WINDOWS] Correctly convert path to wide chars when opening the library.xml
|
||||
* [LIBRARY] Remove the function the read file using a native path.
|
||||
All path must be utf8, no need to pass a native path along the utf8 path.
|
||||
* [TEST] Fix tests using the main function of gtest instead of custom one.
|
||||
* [CI] Move to github CI instead of Travis.
|
||||
* The `Book::update` method always update the book's fields. Even if they are
|
||||
not empty.
|
||||
* [JAVA] Add wrapping around the library manager (opds parsing)
|
||||
* [ARIA2] Add api option to start download with option (destination folder)
|
||||
* [OPDS] Fixes about opds parsing, generation (missing attributes)
|
||||
and requesting (server)
|
||||
* Add methods on `Book` to get specific tag values (was on `Reader` only)
|
||||
* Add flavour attribute to `Book`
|
||||
* Fix opensearch description.
|
||||
* Trust the given library.xml (by default) instead of reading the value from
|
||||
the zim files.
|
||||
* [OPDS] Be able to filter the content by name or size.
|
||||
* [WINDOWS] Fix launching subcommand when there is spaces in the path.
|
||||
|
||||
kiwix-lib 8.2.2
|
||||
===============
|
||||
|
||||
|
||||
13
README.md
13
README.md
@@ -7,7 +7,7 @@ GNU/Linux, macOS, Android, iOS, ...).
|
||||
|
||||
[](https://bintray.com/kiwix/kiwix/kiwixlib/_latestVersion)
|
||||
[](https://aur.archlinux.org/packages/kiwix-lib/)
|
||||
[](https://travis-ci.com/kiwix/kiwix-lib)
|
||||
[](https://github.com/kiwix/kiwix-lib/actions?query=branch%3Amaster)
|
||||
[](https://www.codefactor.io/repository/github/kiwix/kiwix-lib)
|
||||
[](https://codecov.io/gh/kiwix/kiwix-lib)
|
||||
[](https://www.gnu.org/licenses/gpl-3.0)
|
||||
@@ -41,7 +41,7 @@ libraries need to be available:
|
||||
* [Aria2](https://aria2.github.io/) (package `aria2` on Ubuntu)
|
||||
* [Mustache](https://github.com/kainjow/Mustache) (Just copy the
|
||||
header `mustache.hpp` somewhere it can be found by the compiler and/or
|
||||
set CPPFLAGS with correct `-I` option)
|
||||
set CPPFLAGS with correct `-I` option). Use Mustache version 3 only.
|
||||
|
||||
These dependencies may or may not be packaged by your operating
|
||||
system. They may also be packaged but only in an older version. The
|
||||
@@ -85,6 +85,15 @@ Meson. If you want statically linked libraries, you can add
|
||||
|
||||
Depending of you system, `ninja` may be called `ninja-build`.
|
||||
|
||||
Testing
|
||||
-------
|
||||
|
||||
To run the automated tests:
|
||||
```bash
|
||||
cd build
|
||||
meson test
|
||||
```
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ task writePom {
|
||||
project {
|
||||
groupId 'org.kiwix.kiwixlib'
|
||||
artifactId 'kiwixlib'
|
||||
version '8.2.2' + (System.env.KIWIXLIB_BUILDVERSION == null ? '' : '-'+System.env.KIWIXLIB_BUILDVERSION)
|
||||
version '9.3.1' + (System.env.KIWIXLIB_BUILDVERSION == null ? '' : '-'+System.env.KIWIXLIB_BUILDVERSION)
|
||||
packaging 'aar'
|
||||
name 'kiwixlib'
|
||||
url 'https://github.com/kiwix/kiwix-lib'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="kiwix.org.kiwixlib">
|
||||
package="org.kiwix.kiwixlib">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
|
||||
5
debian/changelog
vendored
Normal file
5
debian/changelog
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
libkiwix (0.0.0) unstable; urgency=medium
|
||||
|
||||
* Initial release
|
||||
|
||||
-- Kunal Mehta <legoktm@debian.org> Wed, 08 Jul 2020 18:12:32 -0700
|
||||
45
debian/control
vendored
Normal file
45
debian/control
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
Source: libkiwix
|
||||
Priority: optional
|
||||
Maintainer: Kiwix team <kiwix@kiwix.org>
|
||||
Build-Depends: debhelper-compat (= 12),
|
||||
meson,
|
||||
pkg-config,
|
||||
libzim-dev (>= 6.0.0),
|
||||
libcurl4-gnutls-dev,
|
||||
libicu-dev,
|
||||
libgtest-dev,
|
||||
libkainjow-mustache-dev,
|
||||
liblzma-dev,
|
||||
libmicrohttpd-dev,
|
||||
libpugixml-dev,
|
||||
zlib1g-dev
|
||||
Standards-Version: 4.5.0
|
||||
Section: libs
|
||||
Homepage: https://github.com/kiwix/kiwix-lib
|
||||
Rules-Requires-Root: no
|
||||
|
||||
Package: libkiwix-dev
|
||||
Section: libdevel
|
||||
Architecture: any
|
||||
Multi-Arch: same
|
||||
Depends: libkiwix9 (= ${binary:Version}), ${misc:Depends}, python3, aria2,
|
||||
libzim-dev (>= 6.0.0),
|
||||
libicu-dev,
|
||||
libpugixml-dev,
|
||||
libcurl4-gnutls-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
|
||||
Kiwix ports (Windows, Linux, OSX, Android, etc.).
|
||||
.
|
||||
This package contains development files.
|
||||
|
||||
Package: libkiwix9
|
||||
Architecture: any
|
||||
Multi-Arch: same
|
||||
Depends: ${shlibs:Depends}, ${misc:Depends}
|
||||
Conflicts: libkiwix0, libkiwix3
|
||||
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
|
||||
Kiwix ports (Windows, Linux, OSX, Android, etc.).
|
||||
1
debian/copyright
vendored
Normal file
1
debian/copyright
vendored
Normal file
@@ -0,0 +1 @@
|
||||
See COPYING in the repository root.
|
||||
4
debian/libkiwix-dev.install
vendored
Normal file
4
debian/libkiwix-dev.install
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
usr/include
|
||||
usr/lib/*/libkiwix.so
|
||||
usr/lib/*/pkgconfig
|
||||
usr/bin
|
||||
1
debian/libkiwix9.install
vendored
Normal file
1
debian/libkiwix9.install
vendored
Normal file
@@ -0,0 +1 @@
|
||||
usr/lib/*/libkiwix.so.*
|
||||
5
debian/rules
vendored
Executable file
5
debian/rules
vendored
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/make -f
|
||||
export DEB_BUILD_MAINT_OPTIONS = hardening=+all
|
||||
|
||||
%:
|
||||
dh $@ --buildsystem=meson
|
||||
1
debian/source/format
vendored
Normal file
1
debian/source/format
vendored
Normal file
@@ -0,0 +1 @@
|
||||
3.0 (native)
|
||||
@@ -60,6 +60,9 @@ class Book
|
||||
const std::string& getUrl() const { return m_url; }
|
||||
const std::string& getName() const { return m_name; }
|
||||
const std::string& getTags() const { return m_tags; }
|
||||
std::string getTagStr(const std::string& tagName) const;
|
||||
bool getTagBool(const std::string& tagName) const;
|
||||
const std::string& getFlavour() const { return m_flavour; }
|
||||
const std::string& getOrigId() const { return m_origId; }
|
||||
const uint64_t& getArticleCount() const { return m_articleCount; }
|
||||
const uint64_t& getMediaCount() const { return m_mediaCount; }
|
||||
@@ -81,6 +84,7 @@ class Book
|
||||
void setDate(const std::string& date) { m_date = date; }
|
||||
void setUrl(const std::string& url) { m_url = url; }
|
||||
void setName(const std::string& name) { m_name = name; }
|
||||
void setFlavour(const std::string& flavour) { m_flavour = flavour; }
|
||||
void setTags(const std::string& tags) { m_tags = tags; }
|
||||
void setOrigId(const std::string& origId) { m_origId = origId; }
|
||||
void setArticleCount(uint64_t articleCount) { m_articleCount = articleCount; }
|
||||
@@ -94,7 +98,7 @@ class Book
|
||||
std::string m_id;
|
||||
std::string m_downloadId;
|
||||
std::string m_path;
|
||||
bool m_pathValid;
|
||||
bool m_pathValid = false;
|
||||
std::string m_title;
|
||||
std::string m_description;
|
||||
std::string m_language;
|
||||
@@ -103,12 +107,13 @@ class Book
|
||||
std::string m_date;
|
||||
std::string m_url;
|
||||
std::string m_name;
|
||||
std::string m_flavour;
|
||||
std::string m_tags;
|
||||
std::string m_origId;
|
||||
uint64_t m_articleCount;
|
||||
uint64_t m_mediaCount;
|
||||
bool m_readOnly;
|
||||
uint64_t m_size;
|
||||
uint64_t m_articleCount = 0;
|
||||
uint64_t m_mediaCount = 0;
|
||||
bool m_readOnly = false;
|
||||
uint64_t m_size = 0;
|
||||
mutable std::string m_favicon;
|
||||
std::string m_faviconUrl;
|
||||
std::string m_faviconMimeType;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <map>
|
||||
#include <pthread.h>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
@@ -92,12 +93,11 @@ class Downloader
|
||||
|
||||
void close();
|
||||
|
||||
Download* startDownload(const std::string& uri);
|
||||
Download* startDownload(const std::string& uri, const std::vector<std::pair<std::string, std::string>>& options = {});
|
||||
Download* getDownload(const std::string& did);
|
||||
|
||||
size_t getNbDownload() { return m_knownDownloads.size(); }
|
||||
std::vector<std::string> getDownloadIds();
|
||||
const std::string &getAria2LaunchCmd();
|
||||
|
||||
private:
|
||||
std::map<std::string, std::unique_ptr<Download>> m_knownDownloads;
|
||||
|
||||
@@ -57,6 +57,7 @@ class Filter {
|
||||
std::string _creator;
|
||||
size_t _maxSize;
|
||||
std::string _query;
|
||||
std::string _name;
|
||||
|
||||
public:
|
||||
Filter();
|
||||
@@ -100,6 +101,7 @@ class Filter {
|
||||
Filter& creator(std::string creator);
|
||||
Filter& maxSize(size_t size);
|
||||
Filter& query(std::string query);
|
||||
Filter& name(std::string name);
|
||||
|
||||
bool accept(const Book& book) const;
|
||||
};
|
||||
@@ -147,6 +149,7 @@ class Library
|
||||
bool removeBookmark(const std::string& zimId, const std::string& url);
|
||||
|
||||
Book& getBookById(const std::string& id);
|
||||
Book& getBookByPath(const std::string& path);
|
||||
std::shared_ptr<Reader> getReaderById(const std::string& id);
|
||||
|
||||
/**
|
||||
|
||||
@@ -69,26 +69,12 @@ class Manager
|
||||
/**
|
||||
* Read a `library.xml` and add book in the file to the library.
|
||||
*
|
||||
* @param path The path to the `library.xml`.
|
||||
* @param path The (utf8) path to the `library.xml`.
|
||||
* @param readOnly Set if the libray path could be overwritten latter with
|
||||
* updated content.
|
||||
* @return True if file has been properly parsed.
|
||||
*/
|
||||
bool readFile(const std::string& path, const bool readOnly = true);
|
||||
|
||||
/**
|
||||
* Read a `library.xml` and add book in the file to the library.
|
||||
*
|
||||
* @param nativePath The path of the `library.xml`
|
||||
* @param UTF8Path The utf8 version (?) of the path. Also the path where the
|
||||
* library will be writen i readOnly is False.
|
||||
* @param readOnly Set if the libray path could be overwritten latter with
|
||||
* updated content.
|
||||
* @return True if file has been properly parsed.
|
||||
*/
|
||||
bool readFile(const std::string& nativePath,
|
||||
const std::string& UTF8Path,
|
||||
const bool readOnly = true);
|
||||
bool readFile(const std::string& path, bool readOnly = true, bool trustLibrary = true);
|
||||
|
||||
/**
|
||||
* Load a library content store in the string.
|
||||
@@ -101,7 +87,8 @@ class Manager
|
||||
*/
|
||||
bool readXml(const std::string& xml,
|
||||
const bool readOnly = true,
|
||||
const std::string& libraryPath = "");
|
||||
const std::string& libraryPath = "",
|
||||
bool trustLibrary = true);
|
||||
|
||||
/**
|
||||
* Load a library content stored in a OPDS stream.
|
||||
@@ -168,8 +155,9 @@ class Manager
|
||||
|
||||
bool readBookFromPath(const std::string& path, Book* book);
|
||||
bool parseXmlDom(const pugi::xml_document& doc,
|
||||
const bool readOnly,
|
||||
const std::string& libraryPath);
|
||||
bool readOnly,
|
||||
const std::string& libraryPath,
|
||||
bool trustLibrary);
|
||||
bool parseOpdsDom(const pugi::xml_document& doc,
|
||||
const std::string& urlHost);
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@ namespace kiwix
|
||||
* The Reader class is the class who allow to get an entry content from a zim
|
||||
* file.
|
||||
*/
|
||||
|
||||
using SuggestionsList_t = std::vector<std::vector<std::string>>;
|
||||
class Reader
|
||||
{
|
||||
public:
|
||||
@@ -419,6 +421,10 @@ class Reader
|
||||
*
|
||||
* Suggestions are stored in an internal vector and can be retrieved using
|
||||
* `getNextSuggestion` method.
|
||||
* This method is not thread safe and is deprecated. Use :
|
||||
* bool searchSuggestions(const string& prefix,
|
||||
* unsigned int suggestionsCount,
|
||||
* SuggestionsList_t& results);
|
||||
*
|
||||
* @param prefix The prefix to search.
|
||||
* @param suggestionsCount How many suggestions to search for.
|
||||
@@ -426,12 +432,49 @@ class Reader
|
||||
* If false, add suggestions to the internal vector
|
||||
* (until internal vector size is suggestionCount (or no more
|
||||
* suggestion))
|
||||
* @return True if some suggestions where added to the internal vector.
|
||||
* @return True if some suggestions have been added to the internal vector.
|
||||
*/
|
||||
bool searchSuggestions(const string& prefix,
|
||||
DEPRECATED bool searchSuggestions(const string& prefix,
|
||||
unsigned int suggestionsCount,
|
||||
const bool reset = true);
|
||||
|
||||
/**
|
||||
* Search for entries with title starting with prefix (case sensitive).
|
||||
*
|
||||
* Suggestions are added to the `result` vector.
|
||||
*
|
||||
* @param prefix The prefix to search.
|
||||
* @param suggestionsCount How many suggestions to search for.
|
||||
* @param result The vector where to store the suggestions.
|
||||
* @return True if some suggestions have been added to the vector.
|
||||
*/
|
||||
|
||||
bool searchSuggestions(const string& prefix,
|
||||
unsigned int suggestionsCount,
|
||||
SuggestionsList_t& resuls);
|
||||
|
||||
/**
|
||||
* Search for entries for the given prefix.
|
||||
*
|
||||
* If the zim file has a internal fulltext index, the suggestions will be
|
||||
* searched using it.
|
||||
* Else the suggestions will be search using `searchSuggestions` while trying
|
||||
* to be smart about case sensitivity (using `getTitleVariants`).
|
||||
*
|
||||
* In any case, suggestions are stored in an internal vector and can be
|
||||
* retrieved using `getNextSuggestion` method.
|
||||
* The internal vector will be reset.
|
||||
* This method is not thread safe and is deprecated. Use :
|
||||
* bool searchSuggestionsSmart(const string& prefix,
|
||||
* unsigned int suggestionsCount,
|
||||
* SuggestionsList_t& results);
|
||||
*
|
||||
* @param prefix The prefix to search for.
|
||||
* @param suggestionsCount How many suggestions to search for.
|
||||
*/
|
||||
DEPRECATED bool searchSuggestionsSmart(const string& prefix,
|
||||
unsigned int suggestionsCount);
|
||||
|
||||
/**
|
||||
* Search for entries for the given prefix.
|
||||
*
|
||||
@@ -446,9 +489,13 @@ class Reader
|
||||
*
|
||||
* @param prefix The prefix to search for.
|
||||
* @param suggestionsCount How many suggestions to search for.
|
||||
* @param results The vector where to store the suggestions
|
||||
* @return True if some suggestions have been added to the results.
|
||||
*/
|
||||
bool searchSuggestionsSmart(const string& prefix,
|
||||
unsigned int suggestionsCount);
|
||||
bool searchSuggestionsSmart(const string& prefix,
|
||||
unsigned int suggestionsCount,
|
||||
SuggestionsList_t& results);
|
||||
|
||||
|
||||
/**
|
||||
* Check if the url exists in the zim file.
|
||||
@@ -490,7 +537,7 @@ class Reader
|
||||
* @param[out] title the title of the suggestion.
|
||||
* @return True if title has been set.
|
||||
*/
|
||||
bool getNextSuggestion(string& title);
|
||||
DEPRECATED bool getNextSuggestion(string& title);
|
||||
|
||||
/**
|
||||
* Get the next suggestion title and url.
|
||||
@@ -499,7 +546,7 @@ class Reader
|
||||
* @param[out] url the url of the suggestion.
|
||||
* @return True if title and url have been set.
|
||||
*/
|
||||
bool getNextSuggestion(string& title, string& url);
|
||||
DEPRECATED bool getNextSuggestion(string& title, string& url);
|
||||
|
||||
/**
|
||||
* Get if we can check zim file integrity (has a checksum).
|
||||
@@ -559,8 +606,8 @@ class Reader
|
||||
zim::size_type nsICount;
|
||||
std::string zimFilePath;
|
||||
|
||||
std::vector<std::vector<std::string>> suggestions;
|
||||
std::vector<std::vector<std::string>>::iterator suggestionsOffset;
|
||||
SuggestionsList_t suggestions;
|
||||
SuggestionsList_t::iterator suggestionsOffset;
|
||||
|
||||
private:
|
||||
std::map<const std::string, unsigned int> parseCounterMetadata() const;
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
#include <vector>
|
||||
#include "tools/pathTools.h"
|
||||
#include "tools/stringTools.h"
|
||||
#include "kiwix_config.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
@@ -89,7 +88,7 @@ class Searcher
|
||||
* @param resultEnd the end offset of the search results (used for pagination).
|
||||
* @param verbose print some info on stdout if true.
|
||||
*/
|
||||
void search(std::string& search,
|
||||
void search(const std::string& search,
|
||||
unsigned int resultStart,
|
||||
unsigned int resultEnd,
|
||||
const bool verbose = false);
|
||||
|
||||
@@ -56,7 +56,9 @@ namespace kiwix
|
||||
void setNbThreads(int threads) { m_nbThreads = threads; }
|
||||
void setVerbose(bool verbose) { m_verbose = verbose; }
|
||||
void setTaskbar(bool withTaskbar, bool withLibraryButton)
|
||||
{ m_withTaskbar = withTaskbar; m_withLibraryButton = withLibraryButton; }
|
||||
{ m_withTaskbar = withTaskbar; m_withLibraryButton = withLibraryButton; }
|
||||
void setBlockExternalLinks(bool blockExternalLinks)
|
||||
{ m_blockExternalLinks = blockExternalLinks; }
|
||||
|
||||
protected:
|
||||
Library* mp_library;
|
||||
@@ -68,6 +70,7 @@ namespace kiwix
|
||||
bool m_verbose = false;
|
||||
bool m_withTaskbar = true;
|
||||
bool m_withLibraryButton = true;
|
||||
bool m_blockExternalLinks = false;
|
||||
std::unique_ptr<InternalServer> mp_server;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#define KIWIX_OTHERTOOLS_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace pugi {
|
||||
class xml_node;
|
||||
@@ -28,9 +29,18 @@ namespace pugi {
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
void sleep(unsigned int milliseconds);
|
||||
std::string nodeToString(const pugi::xml_node& node);
|
||||
std::string converta2toa3(const std::string& a2code);
|
||||
void sleep(unsigned int milliseconds);
|
||||
std::string nodeToString(const pugi::xml_node& node);
|
||||
std::string converta2toa3(const std::string& a2code);
|
||||
|
||||
/*
|
||||
* Convert all format tag string to new format
|
||||
*/
|
||||
std::vector<std::string> convertTags(const std::string& tags_str);
|
||||
std::string getTagValueFromTagList(const std::vector<std::string>& tagList,
|
||||
const std::string& tagName);
|
||||
bool convertStrToBool(const std::string& value);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -22,6 +22,10 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#ifdef _WIN32
|
||||
std::string WideToUtf8(const std::wstring& wstr);
|
||||
std::wstring Utf8ToWide(const std::string& str);
|
||||
#endif
|
||||
bool isRelativePath(const std::string& path);
|
||||
std::string computeAbsolutePath(const std::string& path, const std::string& relativePath);
|
||||
std::string computeRelativePath(const std::string& path, const std::string& absolutePath);
|
||||
|
||||
29
meson.build
29
meson.build
@@ -1,25 +1,40 @@
|
||||
project('kiwix-lib', 'cpp',
|
||||
version : '8.2.2', # Also change this in android-kiwix-lib-publisher/kiwixLibAndroid/build.gradle
|
||||
license : 'GPL',
|
||||
version : '9.3.1', # Also change this in android-kiwix-lib-publisher/kiwixLibAndroid/build.gradle
|
||||
license : 'GPLv3+',
|
||||
default_options : ['c_std=c11', 'cpp_std=c++11', 'werror=true'])
|
||||
|
||||
compiler = meson.get_compiler('cpp')
|
||||
|
||||
static_deps = get_option('android') or get_option('default_library') == 'static'
|
||||
if get_option('android')
|
||||
wrapper = get_option('wrapper')
|
||||
|
||||
static_deps = 'android' in wrapper or 'java' in wrapper or get_option('default_library') == 'static'
|
||||
if 'android' in wrapper
|
||||
extra_libs = ['-llog']
|
||||
else
|
||||
extra_libs = []
|
||||
endif
|
||||
|
||||
if 'java' in wrapper
|
||||
add_languages('java')
|
||||
endif
|
||||
|
||||
# See https://github.com/kiwix/kiwix-lib/issues/371
|
||||
if target_machine.cpu_family() in ['arm', 'mips', 'm68k', 'ppc', 'sh4']
|
||||
extra_libs += '-latomic'
|
||||
endif
|
||||
|
||||
thread_dep = dependency('threads')
|
||||
libicu_dep = dependency('icu-i18n', static:static_deps)
|
||||
libzim_dep = dependency('libzim', version : '>=5.0.0', static:static_deps)
|
||||
libzim_dep = dependency('libzim', version : '>=6.1.8', static:static_deps)
|
||||
pugixml_dep = dependency('pugixml', static:static_deps)
|
||||
libcurl_dep = dependency('libcurl', static:static_deps)
|
||||
microhttpd_dep = dependency('libmicrohttpd', static:static_deps)
|
||||
|
||||
if not compiler.has_header('mustache.hpp')
|
||||
if compiler.has_header('mustache.hpp')
|
||||
extra_include = []
|
||||
elif compiler.has_header('mustache.hpp', args: '-I/usr/include/kainjow')
|
||||
extra_include = ['/usr/include/kainjow']
|
||||
else
|
||||
error('Cannot found header mustache.hpp')
|
||||
endif
|
||||
|
||||
@@ -31,7 +46,7 @@ endif
|
||||
|
||||
all_deps = [thread_dep, libicu_dep, libzim_dep, pugixml_dep, libcurl_dep, microhttpd_dep]
|
||||
|
||||
inc = include_directories('include')
|
||||
inc = include_directories('include', extra_include)
|
||||
|
||||
conf = configuration_data()
|
||||
conf.set('VERSION', '"@0@"'.format(meson.project_version()))
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
option('android', type : 'boolean', value : false,
|
||||
description : 'Do we make a kiwix-lib for android')
|
||||
option('wrapper', type:'array', choices:['java', 'android'], value:[],
|
||||
description: 'The wrapper to generate.')
|
||||
|
||||
20
scripts/kiwix-compile-resources.1
Normal file
20
scripts/kiwix-compile-resources.1
Normal file
@@ -0,0 +1,20 @@
|
||||
.TH KIWIX-COMPILE-RESOURCES "1" "August 2017" "Kiwix" "User Commands"
|
||||
.SH NAME
|
||||
kiwix-compile-resources \- helper to compile and generate some Kiwix resources
|
||||
.SH SYNOPSIS
|
||||
\fBkiwix\-compile\-resources\fR [\-h] [\-\-cxxfile CXXFILE] [\-\-hfile HFILE] resource_file\fR
|
||||
.SH DESCRIPTION
|
||||
.TP
|
||||
resource_file
|
||||
The list of resources to compile.
|
||||
.TP
|
||||
\fB\-h\fR, \fB\-\-help\fR
|
||||
show a help message and exit
|
||||
.TP
|
||||
\fB\-\-cxxfile\fR CXXFILE
|
||||
The Cpp file name to generate
|
||||
.TP
|
||||
\fB\-\-hfile\fR HFILE
|
||||
The h file name to generate
|
||||
.SH AUTHOR
|
||||
Matthieu Gautier <mgautier@kymeria.fr>
|
||||
@@ -2,3 +2,5 @@
|
||||
res_compiler = find_program('kiwix-compile-resources')
|
||||
|
||||
install_data(res_compiler.path(), install_dir:get_option('bindir'))
|
||||
|
||||
install_man('kiwix-compile-resources.1')
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
* Copyright (C) 2017 Matthieu Gautier <mgautier@kymeria.fr>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
|
||||
#include <jni.h>
|
||||
#include <android/log.h>
|
||||
#include "org_kiwix_kiwixlib_JNIKiwixLibrary.h"
|
||||
|
||||
#include "library.h"
|
||||
#include "reader.h"
|
||||
#include "utils.h"
|
||||
|
||||
/* Kiwix Reader JNI functions */
|
||||
JNIEXPORT jlong JNICALL Java_org_kiwix_kiwixlib_JNIKiwixLibrary_getNativeLibrary(
|
||||
JNIEnv* env, jobject obj)
|
||||
{
|
||||
__android_log_print(ANDROID_LOG_INFO, "kiwix", "Attempting to create library");
|
||||
Lock l;
|
||||
try {
|
||||
kiwix::Library* library = new kiwix::Library();
|
||||
return reinterpret_cast<jlong>(new Handle<kiwix::Library>(library));
|
||||
} catch (std::exception& e) {
|
||||
__android_log_print(ANDROID_LOG_WARN, "kiwix", "Error creating ZIM library");
|
||||
__android_log_print(ANDROID_LOG_WARN, "kiwix", e.what());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixLibrary_dispose(JNIEnv* env, jobject obj)
|
||||
{
|
||||
Handle<kiwix::Library>::dispose(env, obj);
|
||||
}
|
||||
|
||||
#define LIBRARY (Handle<kiwix::Library>::getHandle(env, obj))
|
||||
|
||||
/* Kiwix library functions */
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixLibrary_addBook(JNIEnv* env, jobject obj, jstring path)
|
||||
{
|
||||
std::string cPath = jni2c(path, env);
|
||||
bool ret;
|
||||
|
||||
try {
|
||||
kiwix::Reader reader(cPath);
|
||||
kiwix::Book book;
|
||||
book.update(reader);
|
||||
ret = LIBRARY->addBook(book);
|
||||
} catch (std::exception& e) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", "Unable to add the book");
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", e.what());
|
||||
ret = false;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
|
||||
kiwix_jni = custom_target('jni',
|
||||
input: ['org/kiwix/kiwixlib/JNIICU.java',
|
||||
'org/kiwix/kiwixlib/JNIKiwixReader.java',
|
||||
'org/kiwix/kiwixlib/JNIKiwixLibrary.java',
|
||||
'org/kiwix/kiwixlib/JNIKiwixSearcher.java',
|
||||
'org/kiwix/kiwixlib/JNIKiwixServer.java',
|
||||
'org/kiwix/kiwixlib/JNIKiwixInt.java',
|
||||
'org/kiwix/kiwixlib/JNIKiwixString.java',
|
||||
'org/kiwix/kiwixlib/JNIKiwixBool.java',
|
||||
'org/kiwix/kiwixlib/JNIKiwixException.java',
|
||||
'org/kiwix/kiwixlib/Pair.java'],
|
||||
output: ['org_kiwix_kiwixlib_JNIKiwix.h',
|
||||
'org_kiwix_kiwixlib_JNIKiwixReader.h',
|
||||
'org_kiwix_kiwixlib_JNIKiwixLibrary.h',
|
||||
'org_kiwix_kiwixlib_JNIKiwixServer.h',
|
||||
'org_kiwix_kiwixlib_JNIKiwixSearcher.h',
|
||||
'org_kiwix_kiwixlib_JNIKiwixSearcher_Result.h'],
|
||||
command:['javac', '-d', '@OUTDIR@', '-h', '@OUTDIR@', '@INPUT@']
|
||||
)
|
||||
|
||||
kiwix_sources += [
|
||||
'android/kiwixicu.cpp',
|
||||
'android/kiwixreader.cpp',
|
||||
'android/kiwixlibrary.cpp',
|
||||
'android/kiwixsearcher.cpp',
|
||||
'android/kiwixserver.cpp',
|
||||
kiwix_jni]
|
||||
|
||||
install_subdir('org', install_dir: 'kiwix-lib/java')
|
||||
install_subdir('res', install_dir: 'kiwix-lib')
|
||||
install_data('AndroidManifest.xml', install_dir: 'kiwix-lib')
|
||||
@@ -78,8 +78,9 @@ Aria2::Aria2():
|
||||
callCmd.push_back("--max-concurrent-downloads=42");
|
||||
callCmd.push_back("--rpc-max-request-size=6M");
|
||||
callCmd.push_back("--file-allocation=none");
|
||||
std::string launchCmd;
|
||||
for (auto &cmd : callCmd) {
|
||||
m_launchCmd.append(cmd).append(" ");
|
||||
launchCmd.append(cmd).append(" ");
|
||||
}
|
||||
mp_aria = Subprocess::run(callCmd);
|
||||
mp_curl = curl_easy_init();
|
||||
@@ -108,7 +109,7 @@ Aria2::Aria2():
|
||||
}
|
||||
if (!watchdog) {
|
||||
curl_easy_cleanup(mp_curl);
|
||||
throw std::runtime_error("Cannot connect to aria2c rpc");
|
||||
throw std::runtime_error("Cannot connect to aria2c rpc. Aria2c launch cmd : " + launchCmd);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,13 +160,16 @@ std::string Aria2::doRequest(const MethodCall& methodCall)
|
||||
throw std::runtime_error("Cannot perform request");
|
||||
}
|
||||
|
||||
std::string Aria2::addUri(const std::vector<std::string>& uris)
|
||||
std::string Aria2::addUri(const std::vector<std::string>& uris, const std::vector<std::pair<std::string, std::string>>& options)
|
||||
{
|
||||
MethodCall methodCall("aria2.addUri", m_secret);
|
||||
auto uriParams = methodCall.newParamValue().getArray();
|
||||
for (auto& uri : uris) {
|
||||
uriParams.addValue().set(uri);
|
||||
}
|
||||
for (auto& option : options) {
|
||||
methodCall.newParamValue().getStruct().addMember(option.first).getValue().set(option.second);
|
||||
}
|
||||
auto ret = doRequest(methodCall);
|
||||
MethodResponse response(ret);
|
||||
return response.getParamValue(0).getAsS();
|
||||
|
||||
@@ -26,7 +26,6 @@ class Aria2
|
||||
std::string m_downloadDir;
|
||||
CURL* mp_curl;
|
||||
pthread_mutex_t m_lock;
|
||||
std::string m_launchCmd;
|
||||
|
||||
std::string doRequest(const MethodCall& methodCall);
|
||||
|
||||
@@ -35,7 +34,7 @@ class Aria2
|
||||
virtual ~Aria2();
|
||||
void close();
|
||||
|
||||
std::string addUri(const std::vector<std::string>& uri);
|
||||
std::string addUri(const std::vector<std::string>& uri, const std::vector<std::pair<std::string, std::string>>& options = {});
|
||||
std::string tellStatus(const std::string& gid, const std::vector<std::string>& statusKey);
|
||||
std::vector<std::string> tellActive();
|
||||
std::vector<std::string> tellWaiting();
|
||||
@@ -44,7 +43,6 @@ class Aria2
|
||||
void pause(const std::string& gid);
|
||||
void unpause(const std::string& gid);
|
||||
void remove(const std::string& gid);
|
||||
const std::string &getLaunchCmd() { return m_launchCmd; };
|
||||
};
|
||||
|
||||
}; //end namespace kiwix
|
||||
|
||||
77
src/book.cpp
77
src/book.cpp
@@ -23,6 +23,7 @@
|
||||
#include "tools/base64.h"
|
||||
#include "tools/regexTools.h"
|
||||
#include "tools/networkTools.h"
|
||||
#include "tools/otherTools.h"
|
||||
|
||||
#include <pugixml.hpp>
|
||||
|
||||
@@ -44,43 +45,48 @@ bool Book::update(const kiwix::Book& other)
|
||||
if (m_readOnly)
|
||||
return false;
|
||||
|
||||
if (m_id != other.m_id)
|
||||
return false;
|
||||
|
||||
m_readOnly = other.m_readOnly;
|
||||
m_path = other.m_path;
|
||||
m_pathValid = other.m_pathValid;
|
||||
m_title = other.m_title;
|
||||
m_description = other.m_description;
|
||||
m_language = other.m_language;
|
||||
m_creator = other.m_creator;
|
||||
m_publisher = other.m_publisher;
|
||||
m_date = other.m_date;
|
||||
m_url = other.m_url;
|
||||
m_name = other.m_name;
|
||||
m_flavour = other.m_flavour;
|
||||
m_tags = other.m_tags;
|
||||
m_origId = other.m_origId;
|
||||
m_articleCount = other.m_articleCount;
|
||||
m_mediaCount = other.m_mediaCount;
|
||||
m_size = other.m_size;
|
||||
m_favicon = other.m_favicon;
|
||||
m_faviconMimeType = other.m_faviconMimeType;
|
||||
m_faviconUrl = other.m_faviconUrl;
|
||||
|
||||
if (m_path.empty()) {
|
||||
m_path = other.m_path;
|
||||
m_pathValid = other.m_pathValid;
|
||||
}
|
||||
m_downloadId = other.m_downloadId;
|
||||
|
||||
if (m_url.empty()) {
|
||||
m_url = other.m_url;
|
||||
}
|
||||
|
||||
if (m_tags.empty()) {
|
||||
m_tags = other.m_tags;
|
||||
}
|
||||
|
||||
if (m_name.empty()) {
|
||||
m_name = other.m_name;
|
||||
}
|
||||
|
||||
if (m_faviconMimeType.empty()) {
|
||||
m_favicon = other.m_favicon;
|
||||
m_faviconMimeType = other.m_faviconMimeType;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Book::update(const kiwix::Reader& reader)
|
||||
{
|
||||
m_path = reader.getZimFilePath();
|
||||
m_pathValid = true;
|
||||
m_id = reader.getId();
|
||||
m_title = reader.getTitle();
|
||||
m_description = reader.getDescription();
|
||||
m_language = reader.getLanguage();
|
||||
m_date = reader.getDate();
|
||||
m_creator = reader.getCreator();
|
||||
m_publisher = reader.getPublisher();
|
||||
m_title = reader.getTitle();
|
||||
m_date = reader.getDate();
|
||||
m_name = reader.getName();
|
||||
m_flavour = reader.getFlavour();
|
||||
m_tags = reader.getTags();
|
||||
m_origId = reader.getOrigId();
|
||||
m_articleCount = reader.getArticleCount();
|
||||
@@ -100,21 +106,24 @@ void Book::updateFromXml(const pugi::xml_node& node, const std::string& baseDir)
|
||||
path = computeAbsolutePath(baseDir, path);
|
||||
}
|
||||
m_path = path;
|
||||
m_pathValid = fileExists(path);
|
||||
m_title = ATTR("title");
|
||||
m_name = ATTR("name");
|
||||
m_tags = ATTR("tags");
|
||||
m_description = ATTR("description");
|
||||
m_language = ATTR("language");
|
||||
m_date = ATTR("date");
|
||||
m_creator = ATTR("creator");
|
||||
m_publisher = ATTR("publisher");
|
||||
m_date = ATTR("date");
|
||||
m_url = ATTR("url");
|
||||
m_name = ATTR("name");
|
||||
m_flavour = ATTR("flavour");
|
||||
m_tags = ATTR("tags");
|
||||
m_origId = ATTR("origId");
|
||||
m_articleCount = strtoull(ATTR("articleCount"), 0, 0);
|
||||
m_mediaCount = strtoull(ATTR("mediaCount"), 0, 0);
|
||||
m_size = strtoull(ATTR("size"), 0, 0) << 10;
|
||||
m_favicon = base64_decode(ATTR("favicon"));
|
||||
m_faviconMimeType = ATTR("faviconMimeType");
|
||||
m_faviconUrl = ATTR("faviconUrl");
|
||||
try {
|
||||
m_downloadId = ATTR("downloadId");
|
||||
} catch(...) {}
|
||||
@@ -137,12 +146,18 @@ void Book::updateFromOpds(const pugi::xml_node& node, const std::string& urlHost
|
||||
if (!m_id.compare(0, 9, "urn:uuid:")) {
|
||||
m_id.erase(0, 9);
|
||||
}
|
||||
// No path on opds.
|
||||
m_title = VALUE("title");
|
||||
m_description = VALUE("description");
|
||||
m_description = VALUE("summary");
|
||||
m_language = VALUE("language");
|
||||
m_date = fromOpdsDate(VALUE("updated"));
|
||||
m_creator = node.child("author").child("name").child_value();
|
||||
m_publisher = node.child("publisher").child("name").child_value();
|
||||
m_date = fromOpdsDate(VALUE("updated"));
|
||||
m_name = VALUE("name");
|
||||
m_flavour = VALUE("flavour");
|
||||
m_tags = VALUE("tags");
|
||||
m_articleCount = strtoull(VALUE("articleCount"), 0, 0);
|
||||
m_mediaCount = strtoull(VALUE("mediaCount"), 0, 0);
|
||||
for(auto linkNode = node.child("link"); linkNode;
|
||||
linkNode = linkNode.next_sibling("link")) {
|
||||
std::string rel = linkNode.attribute("rel").value();
|
||||
@@ -197,4 +212,12 @@ const std::string& Book::getFavicon() const {
|
||||
return m_favicon;
|
||||
}
|
||||
|
||||
std::string Book::getTagStr(const std::string& tagName) const {
|
||||
return getTagValueFromTagList(convertTags(m_tags), tagName);
|
||||
}
|
||||
|
||||
bool Book::getTagBool(const std::string& tagName) const {
|
||||
return convertStrToBool(getTagStr(tagName));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -163,12 +163,7 @@ std::vector<std::string> Downloader::getDownloadIds() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
const std::string &Downloader::getAria2LaunchCmd()
|
||||
{
|
||||
return mp_aria->getLaunchCmd();
|
||||
}
|
||||
|
||||
Download* Downloader::startDownload(const std::string& uri)
|
||||
Download* Downloader::startDownload(const std::string& uri, const std::vector<std::pair<std::string, std::string>>& options)
|
||||
{
|
||||
for (auto& p: m_knownDownloads) {
|
||||
auto& d = p.second;
|
||||
@@ -177,7 +172,7 @@ Download* Downloader::startDownload(const std::string& uri)
|
||||
return d.get();
|
||||
}
|
||||
std::vector<std::string> uris = {uri};
|
||||
auto gid = mp_aria->addUri(uris);
|
||||
auto gid = mp_aria->addUri(uris, options);
|
||||
m_knownDownloads[gid] = std::unique_ptr<Download>(new Download(mp_aria, gid));
|
||||
return m_knownDownloads[gid].get();
|
||||
}
|
||||
|
||||
@@ -85,6 +85,18 @@ Book& Library::getBookById(const std::string& id)
|
||||
return m_books.at(id);
|
||||
}
|
||||
|
||||
Book& Library::getBookByPath(const std::string& path)
|
||||
{
|
||||
for(auto& it: m_books) {
|
||||
auto& book = it.second;
|
||||
if (book.getPath() == path)
|
||||
return book;
|
||||
}
|
||||
std::ostringstream ss;
|
||||
ss << "No book with path " << path << " in the library." << std::endl;
|
||||
throw std::out_of_range(ss.str());
|
||||
}
|
||||
|
||||
std::shared_ptr<Reader> Library::getReaderById(const std::string& id)
|
||||
{
|
||||
try {
|
||||
@@ -378,6 +390,7 @@ enum filterTypes {
|
||||
_CREATOR = FLAG(10),
|
||||
MAXSIZE = FLAG(11),
|
||||
QUERY = FLAG(12),
|
||||
NAME = FLAG(13),
|
||||
};
|
||||
|
||||
Filter& Filter::local(bool accept)
|
||||
@@ -465,24 +478,35 @@ Filter& Filter::query(std::string query)
|
||||
return *this;
|
||||
}
|
||||
|
||||
Filter& Filter::name(std::string name)
|
||||
{
|
||||
_name = name;
|
||||
activeFilters |= NAME;
|
||||
return *this;
|
||||
}
|
||||
|
||||
#define ACTIVE(X) (activeFilters & (X))
|
||||
#define FILTER(TAG, TEST) if (ACTIVE(TAG) && !(TEST)) { return false; }
|
||||
bool Filter::accept(const Book& book) const
|
||||
{
|
||||
auto local = !book.getPath().empty();
|
||||
if (ACTIVE(_LOCAL) && !local)
|
||||
return false;
|
||||
if (ACTIVE(_NOLOCAL) && local)
|
||||
return false;
|
||||
FILTER(_LOCAL, local)
|
||||
FILTER(_NOLOCAL, !local)
|
||||
|
||||
auto valid = book.isPathValid();
|
||||
if (ACTIVE(_VALID) && !valid)
|
||||
return false;
|
||||
if (ACTIVE(_NOVALID) && valid)
|
||||
return false;
|
||||
FILTER(_VALID, valid)
|
||||
FILTER(_NOVALID, !valid)
|
||||
|
||||
auto remote = !book.getUrl().empty();
|
||||
if (ACTIVE(_REMOTE) && !remote)
|
||||
return false;
|
||||
if (ACTIVE(_NOREMOTE) && remote)
|
||||
return false;
|
||||
FILTER(_REMOTE, remote)
|
||||
FILTER(_NOREMOTE, !remote)
|
||||
|
||||
FILTER(MAXSIZE, book.getSize() <= _maxSize)
|
||||
FILTER(LANG, book.getLanguage() == _lang)
|
||||
FILTER(_PUBLISHER, book.getPublisher() == _publisher)
|
||||
FILTER(_CREATOR, book.getCreator() == _creator)
|
||||
FILTER(NAME, book.getName() == _name)
|
||||
|
||||
if (ACTIVE(ACCEPTTAGS)) {
|
||||
if (!_acceptTags.empty()) {
|
||||
auto vBookTags = split(book.getTags(), ";");
|
||||
@@ -505,18 +529,6 @@ bool Filter::accept(const Book& book) const
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ACTIVE(MAXSIZE) && book.getSize() > _maxSize)
|
||||
return false;
|
||||
|
||||
if (ACTIVE(LANG) && book.getLanguage() != _lang)
|
||||
return false;
|
||||
|
||||
if (ACTIVE(_PUBLISHER) && book.getPublisher() != _publisher)
|
||||
return false;
|
||||
|
||||
if (ACTIVE(_CREATOR) && book.getCreator() != _creator)
|
||||
return false;
|
||||
|
||||
if ( ACTIVE(QUERY)
|
||||
&& !(matchRegex(book.getTitle(), "\\Q" + _query + "\\E")
|
||||
|| matchRegex(book.getDescription(), "\\Q" + _query + "\\E")))
|
||||
|
||||
@@ -53,13 +53,15 @@ void LibXMLDumper::handleBook(Book book, pugi::xml_node root_node) {
|
||||
|
||||
if (book.getOrigId().empty()) {
|
||||
ADD_ATTR_NOT_EMPTY(entry_node, "title", book.getTitle());
|
||||
ADD_ATTR_NOT_EMPTY(entry_node, "name", book.getName());
|
||||
ADD_ATTR_NOT_EMPTY(entry_node, "tags", book.getTags());
|
||||
ADD_ATTR_NOT_EMPTY(entry_node, "description", book.getDescription());
|
||||
ADD_ATTR_NOT_EMPTY(entry_node, "language", book.getLanguage());
|
||||
ADD_ATTR_NOT_EMPTY(entry_node, "creator", book.getCreator());
|
||||
ADD_ATTR_NOT_EMPTY(entry_node, "publisher", book.getPublisher());
|
||||
ADD_ATTR_NOT_EMPTY(entry_node, "name", book.getName());
|
||||
ADD_ATTR_NOT_EMPTY(entry_node, "flavour", book.getFlavour());
|
||||
ADD_ATTR_NOT_EMPTY(entry_node, "tags", book.getTags());
|
||||
ADD_ATTR_NOT_EMPTY(entry_node, "faviconMimeType", book.getFaviconMimeType());
|
||||
ADD_ATTR_NOT_EMPTY(entry_node, "faviconUrl", book.getFaviconUrl());
|
||||
if (!book.getFavicon().empty())
|
||||
ADD_ATTRIBUTE(entry_node, "favicon", base64_encode(book.getFavicon()));
|
||||
} else {
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
#include "manager.h"
|
||||
|
||||
#include "tools/pathTools.h"
|
||||
|
||||
#include <pugixml.hpp>
|
||||
|
||||
namespace kiwix
|
||||
@@ -46,8 +48,9 @@ Manager::~Manager()
|
||||
}
|
||||
}
|
||||
bool Manager::parseXmlDom(const pugi::xml_document& doc,
|
||||
const bool readOnly,
|
||||
const std::string& libraryPath)
|
||||
bool readOnly,
|
||||
const std::string& libraryPath,
|
||||
bool trustLibrary)
|
||||
{
|
||||
pugi::xml_node libraryNode = doc.child("library");
|
||||
|
||||
@@ -61,12 +64,8 @@ bool Manager::parseXmlDom(const pugi::xml_document& doc,
|
||||
book.updateFromXml(bookNode,
|
||||
removeLastPathElement(libraryPath));
|
||||
|
||||
/* Update the book properties with the new importer */
|
||||
if (libraryVersion.empty()
|
||||
|| atoi(libraryVersion.c_str()) <= atoi(KIWIX_LIBRARY_VERSION)) {
|
||||
if (!book.getPath().empty()) {
|
||||
this->readBookFromPath(book.getPath(), &book);
|
||||
}
|
||||
if (!trustLibrary && !book.getPath().empty()) {
|
||||
this->readBookFromPath(book.getPath(), &book);
|
||||
}
|
||||
manipulator->addBookToLibrary(book);
|
||||
}
|
||||
@@ -75,15 +74,16 @@ bool Manager::parseXmlDom(const pugi::xml_document& doc,
|
||||
}
|
||||
|
||||
bool Manager::readXml(const std::string& xml,
|
||||
const bool readOnly,
|
||||
const std::string& libraryPath)
|
||||
bool readOnly,
|
||||
const std::string& libraryPath,
|
||||
bool trustLibrary)
|
||||
{
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result
|
||||
= doc.load_buffer_inplace((void*)xml.data(), xml.size());
|
||||
|
||||
if (result) {
|
||||
this->parseXmlDom(doc, readOnly, libraryPath);
|
||||
this->parseXmlDom(doc, readOnly, libraryPath, trustLibrary);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -134,21 +134,22 @@ bool Manager::readOpds(const std::string& content, const std::string& urlHost)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Manager::readFile(const std::string& path, const bool readOnly)
|
||||
{
|
||||
return this->readFile(path, path, readOnly);
|
||||
}
|
||||
|
||||
bool Manager::readFile(const std::string& nativePath,
|
||||
const std::string& UTF8Path,
|
||||
const bool readOnly)
|
||||
bool Manager::readFile(
|
||||
const std::string& path,
|
||||
bool readOnly,
|
||||
bool trustLibrary)
|
||||
{
|
||||
bool retVal = true;
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result = doc.load_file(nativePath.c_str());
|
||||
|
||||
#ifdef _WIN32
|
||||
pugi::xml_parse_result result = doc.load_file(Utf8ToWide(path).c_str());
|
||||
#else
|
||||
pugi::xml_parse_result result = doc.load_file(path.c_str());
|
||||
#endif
|
||||
|
||||
if (result) {
|
||||
this->parseXmlDom(doc, readOnly, UTF8Path);
|
||||
this->parseXmlDom(doc, readOnly, path, trustLibrary);
|
||||
} else {
|
||||
retVal = false;
|
||||
}
|
||||
@@ -157,7 +158,7 @@ bool Manager::readFile(const std::string& nativePath,
|
||||
* able to know where to save the library if new content are
|
||||
* available */
|
||||
if (!readOnly) {
|
||||
this->writableLibraryPath = UTF8Path;
|
||||
this->writableLibraryPath = path;
|
||||
}
|
||||
|
||||
return retVal;
|
||||
@@ -174,7 +175,7 @@ std::string Manager::addBookFromPathAndGetId(const std::string& pathToOpen,
|
||||
kiwix::Book book;
|
||||
|
||||
if (this->readBookFromPath(pathToOpen, &book)) {
|
||||
if (pathToSave != pathToOpen) {
|
||||
if (!pathToSave.empty() && pathToSave != pathToOpen) {
|
||||
book.setPath(isRelativePath(pathToSave)
|
||||
? computeAbsolutePath(
|
||||
removeLastPathElement(writableLibraryPath),
|
||||
|
||||
@@ -21,6 +21,8 @@ kiwix_sources = [
|
||||
'tools/otherTools.cpp',
|
||||
'kiwixserve.cpp',
|
||||
'name_mapper.cpp',
|
||||
'server/byte_range.cpp',
|
||||
'server/etag.cpp',
|
||||
'server/request_context.cpp',
|
||||
'server/response.cpp'
|
||||
]
|
||||
@@ -32,13 +34,16 @@ else
|
||||
kiwix_sources += 'subprocess_unix.cpp'
|
||||
endif
|
||||
|
||||
if get_option('android')
|
||||
subdir('android')
|
||||
if 'android' in wrapper
|
||||
install_dir = 'kiwix-lib/jniLibs/' + meson.get_cross_property('android_abi')
|
||||
else
|
||||
install_dir = get_option('libdir')
|
||||
endif
|
||||
|
||||
if 'android' in wrapper or 'java' in wrapper
|
||||
subdir('wrapper/java')
|
||||
endif
|
||||
|
||||
config_h = configure_file(output : 'kiwix_config.h',
|
||||
configuration : conf,
|
||||
input : 'config.h.in')
|
||||
@@ -48,6 +53,7 @@ kiwixlib = library('kiwix',
|
||||
kiwix_sources,
|
||||
include_directories : inc,
|
||||
dependencies : all_deps,
|
||||
link_args: extra_libs,
|
||||
version: meson.project_version(),
|
||||
install: true,
|
||||
install_dir: install_dir)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
* Copyright (C) 2017 Matthieu Gautier <mgautier@kymeria.fr>
|
||||
* Copyright 2020 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -18,21 +17,8 @@
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package org.kiwix.kiwixlib;
|
||||
#include <microhttpd.h>
|
||||
|
||||
import org.kiwix.kiwixlib.JNIKiwixException;
|
||||
|
||||
public class JNIKiwixLibrary
|
||||
{
|
||||
public native boolean addBook(String path) throws JNIKiwixException;
|
||||
|
||||
public JNIKiwixLibrary()
|
||||
{
|
||||
nativeHandle = getNativeLibrary();
|
||||
}
|
||||
|
||||
public native void dispose();
|
||||
|
||||
private native long getNativeLibrary();
|
||||
private long nativeHandle;
|
||||
}
|
||||
#if MHD_VERSION < 0x00097002
|
||||
typedef int MHD_Result;
|
||||
#endif
|
||||
@@ -70,12 +70,17 @@ void OPDSDumper::setOpenSearchInfo(int totalResults, int startIndex, int count)
|
||||
|
||||
pugi::xml_node OPDSDumper::handleBook(Book book, pugi::xml_node root_node) {
|
||||
auto entry_node = root_node.append_child("entry");
|
||||
ADD_TEXT_ENTRY(entry_node, "title", book.getTitle());
|
||||
ADD_TEXT_ENTRY(entry_node, "id", "urn:uuid:"+book.getId());
|
||||
ADD_TEXT_ENTRY(entry_node, "icon", rootLocation + "/meta?name=favicon&content=" + book.getHumanReadableIdFromPath());
|
||||
ADD_TEXT_ENTRY(entry_node, "updated", gen_date_from_yyyy_mm_dd(book.getDate()));
|
||||
ADD_TEXT_ENTRY(entry_node, "title", book.getTitle());
|
||||
ADD_TEXT_ENTRY(entry_node, "summary", book.getDescription());
|
||||
ADD_TEXT_ENTRY(entry_node, "language", book.getLanguage());
|
||||
ADD_TEXT_ENTRY(entry_node, "updated", gen_date_from_yyyy_mm_dd(book.getDate()));
|
||||
ADD_TEXT_ENTRY(entry_node, "name", book.getName());
|
||||
ADD_TEXT_ENTRY(entry_node, "flavour", book.getFlavour());
|
||||
ADD_TEXT_ENTRY(entry_node, "tags", book.getTags());
|
||||
ADD_TEXT_ENTRY(entry_node, "articleCount", to_string(book.getArticleCount()));
|
||||
ADD_TEXT_ENTRY(entry_node, "mediaCount", to_string(book.getMediaCount()));
|
||||
ADD_TEXT_ENTRY(entry_node, "icon", rootLocation + "/meta?name=favicon&content=" + book.getHumanReadableIdFromPath());
|
||||
|
||||
auto content_node = entry_node.append_child("link");
|
||||
content_node.append_attribute("type") = "text/html";
|
||||
@@ -84,6 +89,9 @@ pugi::xml_node OPDSDumper::handleBook(Book book, pugi::xml_node root_node) {
|
||||
auto author_node = entry_node.append_child("author");
|
||||
ADD_TEXT_ENTRY(author_node, "name", book.getCreator());
|
||||
|
||||
auto publisher_node = entry_node.append_child("publisher");
|
||||
ADD_TEXT_ENTRY(publisher_node, "name", book.getPublisher());
|
||||
|
||||
if (! book.getUrl().empty()) {
|
||||
auto acquisition_link = entry_node.append_child("link");
|
||||
acquisition_link.append_attribute("rel") = "http://opds-spec.org/acquisition/open-access";
|
||||
|
||||
126
src/reader.cpp
126
src/reader.cpp
@@ -22,6 +22,8 @@
|
||||
|
||||
#include <zim/search.h>
|
||||
|
||||
#include "tools/otherTools.h"
|
||||
|
||||
inline char hi(char v)
|
||||
{
|
||||
char hex[] = "0123456789abcdef";
|
||||
@@ -352,43 +354,6 @@ string Reader::getLicense() const
|
||||
METADATA("License")
|
||||
}
|
||||
|
||||
std::vector<std::string> convertTags(const std::string& tags_str)
|
||||
{
|
||||
auto tags = split(tags_str, ";");
|
||||
std::vector<std::string> tagsList;
|
||||
bool picSeen(false), vidSeen(false), detSeen(false), indexSeen(false);
|
||||
for (auto tag: tags) {
|
||||
picSeen |= (tag == "nopic" || startsWith(tag, "_pictures:"));
|
||||
vidSeen |= (tag == "novid" || startsWith(tag, "_videos:"));
|
||||
detSeen |= (tag == "nodet" || startsWith(tag, "_details:"));
|
||||
indexSeen |= startsWith(tag, "_ftindex");
|
||||
if (tag == "nopic") {
|
||||
tagsList.push_back("_pictures:no");
|
||||
} else if (tag == "novid") {
|
||||
tagsList.push_back("_videos:no");
|
||||
} else if (tag == "nodet") {
|
||||
tagsList.push_back("_details:no");
|
||||
} else if (tag == "_ftindex") {
|
||||
tagsList.push_back("_ftindex:yes");
|
||||
} else {
|
||||
tagsList.push_back(tag);
|
||||
}
|
||||
}
|
||||
if (!indexSeen) {
|
||||
tagsList.push_back("_ftindex:no");
|
||||
}
|
||||
if (!picSeen) {
|
||||
tagsList.push_back("_pictures:yes");
|
||||
}
|
||||
if (!vidSeen) {
|
||||
tagsList.push_back("_videos:yes");
|
||||
}
|
||||
if (!detSeen) {
|
||||
tagsList.push_back("_details:yes");
|
||||
}
|
||||
return tagsList;
|
||||
}
|
||||
|
||||
string Reader::getTags(bool original) const
|
||||
{
|
||||
string tags_str;
|
||||
@@ -400,26 +365,6 @@ string Reader::getTags(bool original) const
|
||||
return join(tags, ";");
|
||||
}
|
||||
|
||||
string getTagValueFromTagList(const std::vector<std::string>& tagList, const std::string& tagName)
|
||||
{
|
||||
for (auto tag: tagList) {
|
||||
if (tag[0] == '_') {
|
||||
auto delimPos = tag.find(':');
|
||||
if (delimPos == string::npos) {
|
||||
// No delimiter... what to do ?
|
||||
continue;
|
||||
}
|
||||
auto cTagName = tag.substr(1, delimPos-1);
|
||||
auto cTagValue = tag.substr(delimPos+1);
|
||||
if (cTagName == tagName) {
|
||||
return cTagValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::stringstream ss;
|
||||
ss << tagName << " cannot be found";
|
||||
throw std::out_of_range(ss.str());
|
||||
}
|
||||
|
||||
string Reader::getTagStr(const std::string& tagName) const
|
||||
{
|
||||
@@ -430,16 +375,7 @@ string Reader::getTagStr(const std::string& tagName) const
|
||||
|
||||
bool Reader::getTagBool(const std::string& tagName) const
|
||||
{
|
||||
auto tagValue = getTagStr(tagName);
|
||||
if (tagValue == "yes") {
|
||||
return true;
|
||||
} else if (tagValue == "no") {
|
||||
return false;
|
||||
} else {
|
||||
std::stringstream ss;
|
||||
ss << "Tag value '" << tagValue << "' for " << tagName << " cannot be converted to bool.";
|
||||
throw std::domain_error(ss.str());
|
||||
}
|
||||
return convertStrToBool(getTagStr(tagName));
|
||||
}
|
||||
|
||||
string Reader::getRelation() const
|
||||
@@ -773,13 +709,11 @@ bool Reader::hasFulltextIndex() const
|
||||
}
|
||||
|
||||
/* Search titles by prefix */
|
||||
|
||||
bool Reader::searchSuggestions(const string& prefix,
|
||||
unsigned int suggestionsCount,
|
||||
const bool reset)
|
||||
{
|
||||
bool retVal = false;
|
||||
zim::File::const_iterator articleItr;
|
||||
|
||||
/* Reset the suggestions otherwise check if the suggestions number is less
|
||||
* than the suggestionsCount */
|
||||
if (reset) {
|
||||
@@ -791,15 +725,30 @@ bool Reader::searchSuggestions(const string& prefix,
|
||||
}
|
||||
}
|
||||
|
||||
auto ret = searchSuggestions(prefix, suggestionsCount, this->suggestions);
|
||||
|
||||
/* Set the cursor to the begining */
|
||||
this->suggestionsOffset = this->suggestions.begin();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
bool Reader::searchSuggestions(const string& prefix,
|
||||
unsigned int suggestionsCount,
|
||||
SuggestionsList_t& results)
|
||||
{
|
||||
bool retVal = false;
|
||||
|
||||
/* Return if no prefix */
|
||||
if (prefix.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (articleItr = zimFileHandler->findByTitle('A', prefix);
|
||||
for (auto articleItr = zimFileHandler->findByTitle('A', prefix);
|
||||
articleItr != zimFileHandler->end()
|
||||
&& articleItr->getTitle().compare(0, prefix.size(), prefix) == 0
|
||||
&& this->suggestions.size() < suggestionsCount;
|
||||
&& results.size() < suggestionsCount;
|
||||
++articleItr) {
|
||||
/* Extract the interesting part of article title & url */
|
||||
std::string normalizedArticleTitle
|
||||
@@ -819,8 +768,8 @@ bool Reader::searchSuggestions(const string& prefix,
|
||||
title) */
|
||||
bool insert = true;
|
||||
std::vector<std::vector<std::string>>::iterator suggestionItr;
|
||||
for (suggestionItr = this->suggestions.begin();
|
||||
suggestionItr != this->suggestions.end();
|
||||
for (suggestionItr = results.begin();
|
||||
suggestionItr != results.end();
|
||||
suggestionItr++) {
|
||||
int result = normalizedArticleTitle.compare((*suggestionItr)[2]);
|
||||
if (result == 0 && articleFinalUrl.compare((*suggestionItr)[1]) == 0) {
|
||||
@@ -837,16 +786,13 @@ bool Reader::searchSuggestions(const string& prefix,
|
||||
suggestion.push_back(articleItr->getTitle());
|
||||
suggestion.push_back(articleFinalUrl);
|
||||
suggestion.push_back(normalizedArticleTitle);
|
||||
this->suggestions.insert(suggestionItr, suggestion);
|
||||
results.insert(suggestionItr, suggestion);
|
||||
}
|
||||
|
||||
/* Suggestions where found */
|
||||
retVal = true;
|
||||
}
|
||||
|
||||
/* Set the cursor to the begining */
|
||||
this->suggestionsOffset = this->suggestions.begin();
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@@ -861,15 +807,28 @@ std::vector<std::string> Reader::getTitleVariants(
|
||||
return variants;
|
||||
}
|
||||
|
||||
/* Try also a few variations of the prefix to have better results */
|
||||
|
||||
bool Reader::searchSuggestionsSmart(const string& prefix,
|
||||
unsigned int suggestionsCount)
|
||||
{
|
||||
this->suggestions.clear();
|
||||
this->suggestionsOffset = this->suggestions.begin();
|
||||
|
||||
auto ret = searchSuggestionsSmart(prefix, suggestionsCount, this->suggestions);
|
||||
|
||||
this->suggestionsOffset = this->suggestions.begin();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Try also a few variations of the prefix to have better results */
|
||||
bool Reader::searchSuggestionsSmart(const string& prefix,
|
||||
unsigned int suggestionsCount,
|
||||
SuggestionsList_t& results)
|
||||
{
|
||||
std::vector<std::string> variants = this->getTitleVariants(prefix);
|
||||
bool retVal = false;
|
||||
|
||||
this->suggestions.clear();
|
||||
this->suggestionsOffset = this->suggestions.begin();
|
||||
/* Try to search in the title using fulltext search database */
|
||||
const auto suggestionSearch
|
||||
= this->getZimFileHandler()->suggestions(prefix, 0, suggestionsCount);
|
||||
@@ -885,15 +844,14 @@ bool Reader::searchSuggestionsSmart(const string& prefix,
|
||||
suggestion.push_back(current->getTitle());
|
||||
suggestion.push_back("/A/" + current->getUrl());
|
||||
suggestion.push_back(kiwix::normalize(current->getTitle()));
|
||||
this->suggestions.push_back(suggestion);
|
||||
results.push_back(suggestion);
|
||||
}
|
||||
this->suggestionsOffset = this->suggestions.begin();
|
||||
retVal = true;
|
||||
} else {
|
||||
for (std::vector<std::string>::iterator variantsItr = variants.begin();
|
||||
variantsItr != variants.end();
|
||||
variantsItr++) {
|
||||
retVal = this->searchSuggestions(*variantsItr, suggestionsCount, false)
|
||||
retVal = this->searchSuggestions(*variantsItr, suggestionsCount, results)
|
||||
|| retVal;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ Reader* Searcher::get_reader(int readerIndex)
|
||||
}
|
||||
|
||||
/* Search strings in the database */
|
||||
void Searcher::search(std::string& search,
|
||||
void Searcher::search(const std::string& search,
|
||||
unsigned int resultStart,
|
||||
unsigned int resultEnd,
|
||||
const bool verbose)
|
||||
|
||||
348
src/server.cpp
348
src/server.cpp
@@ -36,7 +36,7 @@
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
#include <microhttpd.h>
|
||||
#include "microhttpd_wrapper.h"
|
||||
}
|
||||
|
||||
#include "tools/otherTools.h"
|
||||
@@ -75,14 +75,16 @@ namespace kiwix {
|
||||
|
||||
static IdNameMapper defaultNameMapper;
|
||||
|
||||
static int staticHandlerCallback(void* cls,
|
||||
struct MHD_Connection* connection,
|
||||
const char* url,
|
||||
const char* method,
|
||||
const char* version,
|
||||
const char* upload_data,
|
||||
size_t* upload_data_size,
|
||||
void** cont_cls);
|
||||
typedef kainjow::mustache::data MustacheData;
|
||||
|
||||
static MHD_Result staticHandlerCallback(void* cls,
|
||||
struct MHD_Connection* connection,
|
||||
const char* url,
|
||||
const char* method,
|
||||
const char* version,
|
||||
const char* upload_data,
|
||||
size_t* upload_data_size,
|
||||
void** cont_cls);
|
||||
|
||||
|
||||
class InternalServer {
|
||||
@@ -95,23 +97,26 @@ class InternalServer {
|
||||
int nbThreads,
|
||||
bool verbose,
|
||||
bool withTaskbar,
|
||||
bool withLibraryButton);
|
||||
bool withLibraryButton,
|
||||
bool blockExternalLinks);
|
||||
virtual ~InternalServer() = default;
|
||||
|
||||
int handlerCallback(struct MHD_Connection* connection,
|
||||
const char* url,
|
||||
const char* method,
|
||||
const char* version,
|
||||
const char* upload_data,
|
||||
size_t* upload_data_size,
|
||||
void** cont_cls);
|
||||
MHD_Result handlerCallback(struct MHD_Connection* connection,
|
||||
const char* url,
|
||||
const char* method,
|
||||
const char* version,
|
||||
const char* upload_data,
|
||||
size_t* upload_data_size,
|
||||
void** cont_cls);
|
||||
bool start();
|
||||
void stop();
|
||||
|
||||
private:
|
||||
private: // functions
|
||||
Response handle_request(const RequestContext& request);
|
||||
Response build_500(const std::string& msg);
|
||||
Response build_404(const RequestContext& request, const std::string& zimName);
|
||||
Response build_304(const RequestContext& request, const ETag& etag) const;
|
||||
Response build_redirect(const std::string& bookName, const kiwix::Entry& entry) const;
|
||||
Response build_homepage(const RequestContext& request);
|
||||
Response handle_skin(const RequestContext& request);
|
||||
Response handle_catalog(const RequestContext& request);
|
||||
@@ -119,11 +124,18 @@ class InternalServer {
|
||||
Response handle_search(const RequestContext& request);
|
||||
Response handle_suggest(const RequestContext& request);
|
||||
Response handle_random(const RequestContext& request);
|
||||
Response handle_captured_external(const RequestContext& request);
|
||||
Response handle_content(const RequestContext& request);
|
||||
|
||||
kainjow::mustache::data get_default_data();
|
||||
Response get_default_response();
|
||||
MustacheData get_default_data() const;
|
||||
MustacheData homepage_data() const;
|
||||
Response get_default_response() const;
|
||||
|
||||
std::shared_ptr<Reader> get_reader(const std::string& bookName) const;
|
||||
bool etag_not_needed(const RequestContext& r) const;
|
||||
ETag get_matching_if_none_match_etag(const RequestContext& request) const;
|
||||
|
||||
private: // data
|
||||
std::string m_addr;
|
||||
int m_port;
|
||||
std::string m_root;
|
||||
@@ -131,10 +143,13 @@ class InternalServer {
|
||||
std::atomic_bool m_verbose;
|
||||
bool m_withTaskbar;
|
||||
bool m_withLibraryButton;
|
||||
bool m_blockExternalLinks;
|
||||
struct MHD_Daemon* mp_daemon;
|
||||
|
||||
Library* mp_library;
|
||||
NameMapper* mp_nameMapper;
|
||||
|
||||
std::string m_server_id;
|
||||
};
|
||||
|
||||
|
||||
@@ -157,7 +172,8 @@ bool Server::start() {
|
||||
m_nbThreads,
|
||||
m_verbose,
|
||||
m_withTaskbar,
|
||||
m_withLibraryButton));
|
||||
m_withLibraryButton,
|
||||
m_blockExternalLinks));
|
||||
return mp_server->start();
|
||||
}
|
||||
|
||||
@@ -186,7 +202,8 @@ InternalServer::InternalServer(Library* library,
|
||||
int nbThreads,
|
||||
bool verbose,
|
||||
bool withTaskbar,
|
||||
bool withLibraryButton) :
|
||||
bool withLibraryButton,
|
||||
bool blockExternalLinks) :
|
||||
m_addr(addr),
|
||||
m_port(port),
|
||||
m_root(root),
|
||||
@@ -194,6 +211,7 @@ InternalServer::InternalServer(Library* library,
|
||||
m_verbose(verbose),
|
||||
m_withTaskbar(withTaskbar),
|
||||
m_withLibraryButton(withLibraryButton),
|
||||
m_blockExternalLinks(blockExternalLinks),
|
||||
mp_daemon(nullptr),
|
||||
mp_library(library),
|
||||
mp_nameMapper(nameMapper ? nameMapper : &defaultNameMapper)
|
||||
@@ -239,6 +257,8 @@ bool InternalServer::start() {
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
auto server_start_time = std::chrono::system_clock::now().time_since_epoch();
|
||||
m_server_id = kiwix::to_string(server_start_time.count());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -247,14 +267,14 @@ void InternalServer::stop()
|
||||
MHD_stop_daemon(mp_daemon);
|
||||
}
|
||||
|
||||
static int staticHandlerCallback(void* cls,
|
||||
struct MHD_Connection* connection,
|
||||
const char* url,
|
||||
const char* method,
|
||||
const char* version,
|
||||
const char* upload_data,
|
||||
size_t* upload_data_size,
|
||||
void** cont_cls)
|
||||
static MHD_Result staticHandlerCallback(void* cls,
|
||||
struct MHD_Connection* connection,
|
||||
const char* url,
|
||||
const char* method,
|
||||
const char* version,
|
||||
const char* upload_data,
|
||||
size_t* upload_data_size,
|
||||
void** cont_cls)
|
||||
{
|
||||
InternalServer* _this = static_cast<InternalServer*>(cls);
|
||||
|
||||
@@ -267,13 +287,13 @@ static int staticHandlerCallback(void* cls,
|
||||
cont_cls);
|
||||
}
|
||||
|
||||
int InternalServer::handlerCallback(struct MHD_Connection* connection,
|
||||
const char* url,
|
||||
const char* method,
|
||||
const char* version,
|
||||
const char* upload_data,
|
||||
size_t* upload_data_size,
|
||||
void** cont_cls)
|
||||
MHD_Result InternalServer::handlerCallback(struct MHD_Connection* connection,
|
||||
const char* url,
|
||||
const char* method,
|
||||
const char* version,
|
||||
const char* upload_data,
|
||||
size_t* upload_data_size,
|
||||
void** cont_cls)
|
||||
{
|
||||
auto start_time = std::chrono::steady_clock::now();
|
||||
if (m_verbose.load() ) {
|
||||
@@ -288,7 +308,8 @@ int InternalServer::handlerCallback(struct MHD_Connection* connection,
|
||||
}
|
||||
/* Unexpected method */
|
||||
if (request.get_method() != RequestMethod::GET
|
||||
&& request.get_method() != RequestMethod::POST) {
|
||||
&& request.get_method() != RequestMethod::POST
|
||||
&& request.get_method() != RequestMethod::HEAD) {
|
||||
printf("Reject request because of unhandled request method.\n");
|
||||
printf("----------------------\n");
|
||||
return MHD_NO;
|
||||
@@ -305,6 +326,9 @@ int InternalServer::handlerCallback(struct MHD_Connection* connection,
|
||||
}
|
||||
}
|
||||
|
||||
if (response.getReturnCode() == MHD_HTTP_OK && !etag_not_needed(request))
|
||||
response.set_server_id(m_server_id);
|
||||
|
||||
auto ret = response.send(request, connection);
|
||||
auto end_time = std::chrono::steady_clock::now();
|
||||
auto time_span = std::chrono::duration_cast<std::chrono::duration<double>>(end_time - start_time);
|
||||
@@ -315,6 +339,14 @@ int InternalServer::handlerCallback(struct MHD_Connection* connection,
|
||||
return ret;
|
||||
}
|
||||
|
||||
Response InternalServer::build_304(const RequestContext& request, const ETag& etag) const
|
||||
{
|
||||
auto response = get_default_response();
|
||||
response.set_code(MHD_HTTP_NOT_MODIFIED);
|
||||
response.set_etag(etag);
|
||||
response.set_content("");
|
||||
return response;
|
||||
}
|
||||
|
||||
Response InternalServer::handle_request(const RequestContext& request)
|
||||
{
|
||||
@@ -322,6 +354,10 @@ Response InternalServer::handle_request(const RequestContext& request)
|
||||
if (! request.is_valid_url())
|
||||
return build_404(request, "");
|
||||
|
||||
const ETag etag = get_matching_if_none_match_etag(request);
|
||||
if ( etag )
|
||||
return build_304(request, etag);
|
||||
|
||||
if (kiwix::startsWith(request.get_url(), "/skin/"))
|
||||
return handle_skin(request);
|
||||
|
||||
@@ -340,6 +376,9 @@ Response InternalServer::handle_request(const RequestContext& request)
|
||||
if (request.get_url() == "/random")
|
||||
return handle_random(request);
|
||||
|
||||
if (request.get_url() == "/catch/external")
|
||||
return handle_captured_external(request);
|
||||
|
||||
return handle_content(request);
|
||||
} catch (std::exception& e) {
|
||||
fprintf(stderr, "===== Unhandled error : %s\n", e.what());
|
||||
@@ -350,23 +389,23 @@ Response InternalServer::handle_request(const RequestContext& request)
|
||||
}
|
||||
}
|
||||
|
||||
kainjow::mustache::data InternalServer::get_default_data()
|
||||
MustacheData InternalServer::get_default_data() const
|
||||
{
|
||||
kainjow::mustache::data data;
|
||||
MustacheData data;
|
||||
data.set("root", m_root);
|
||||
return data;
|
||||
}
|
||||
|
||||
Response InternalServer::get_default_response()
|
||||
Response InternalServer::get_default_response() const
|
||||
{
|
||||
return Response(m_root, m_verbose.load(), m_withTaskbar, m_withLibraryButton);
|
||||
return Response(m_root, m_verbose.load(), m_withTaskbar, m_withLibraryButton, m_blockExternalLinks);
|
||||
}
|
||||
|
||||
|
||||
Response InternalServer::build_404(const RequestContext& request,
|
||||
const std::string& bookName)
|
||||
{
|
||||
kainjow::mustache::data results;
|
||||
MustacheData results;
|
||||
results.set("url", request.get_full_url());
|
||||
|
||||
auto response = get_default_response();
|
||||
@@ -381,24 +420,24 @@ Response InternalServer::build_404(const RequestContext& request,
|
||||
|
||||
Response InternalServer::build_500(const std::string& msg)
|
||||
{
|
||||
kainjow::mustache::data data;
|
||||
MustacheData data;
|
||||
data.set("error", msg);
|
||||
Response response(m_root, true, false, false);
|
||||
Response response(m_root, true, false, false, false);
|
||||
response.set_template(RESOURCE::templates::_500_html, data);
|
||||
response.set_mimeType("text/html");
|
||||
response.set_code(MHD_HTTP_INTERNAL_SERVER_ERROR);
|
||||
return response;
|
||||
}
|
||||
|
||||
Response InternalServer::build_homepage(const RequestContext& request)
|
||||
MustacheData InternalServer::homepage_data() const
|
||||
{
|
||||
auto data = get_default_data();
|
||||
|
||||
kainjow::mustache::data books{kainjow::mustache::data::type::list};
|
||||
MustacheData books{MustacheData::type::list};
|
||||
for (auto& bookId: mp_library->filter(kiwix::Filter().local(true).valid(true))) {
|
||||
auto& currentBook = mp_library->getBookById(bookId);
|
||||
|
||||
kainjow::mustache::data book;
|
||||
MustacheData book;
|
||||
book.set("name", mp_nameMapper->getNameForId(bookId));
|
||||
book.set("title", currentBook.getTitle());
|
||||
book.set("description", currentBook.getDescription());
|
||||
@@ -408,9 +447,34 @@ Response InternalServer::build_homepage(const RequestContext& request)
|
||||
}
|
||||
|
||||
data.set("books", books);
|
||||
return data;
|
||||
}
|
||||
|
||||
bool InternalServer::etag_not_needed(const RequestContext& request) const
|
||||
{
|
||||
const std::string url = request.get_url();
|
||||
return kiwix::startsWith(url, "/catalog")
|
||||
|| url == "/search"
|
||||
|| url == "/suggest"
|
||||
|| url == "/random"
|
||||
|| url == "/catch/external";
|
||||
}
|
||||
|
||||
ETag
|
||||
InternalServer::get_matching_if_none_match_etag(const RequestContext& r) const
|
||||
{
|
||||
try {
|
||||
const std::string etag_list = r.get_header(MHD_HTTP_HEADER_IF_NONE_MATCH);
|
||||
return ETag::match(etag_list, m_server_id);
|
||||
} catch (const std::out_of_range&) {
|
||||
return ETag();
|
||||
}
|
||||
}
|
||||
|
||||
Response InternalServer::build_homepage(const RequestContext& request)
|
||||
{
|
||||
auto response = get_default_response();
|
||||
response.set_template(RESOURCE::templates::index_html, data);
|
||||
response.set_template(RESOURCE::templates::index_html, homepage_data());
|
||||
response.set_mimeType("text/html; charset=utf-8");
|
||||
response.set_compress(true);
|
||||
response.set_taskbar("", "");
|
||||
@@ -465,7 +529,7 @@ Response InternalServer::handle_meta(const RequestContext& request)
|
||||
response.set_content(content);
|
||||
response.set_mimeType(mimeType);
|
||||
response.set_compress(false);
|
||||
response.set_cache(true);
|
||||
response.set_cacheable();
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -479,7 +543,6 @@ Response InternalServer::handle_suggest(const RequestContext& request)
|
||||
std::string mimeType;
|
||||
unsigned int maxSuggestionCount = 10;
|
||||
unsigned int suggestionCount = 0;
|
||||
std::string suggestion;
|
||||
|
||||
std::string bookName;
|
||||
std::string bookId;
|
||||
@@ -498,16 +561,17 @@ Response InternalServer::handle_suggest(const RequestContext& request)
|
||||
printf("Searching suggestions for: \"%s\"\n", term.c_str());
|
||||
}
|
||||
|
||||
kainjow::mustache::data results{kainjow::mustache::data::type::list};
|
||||
MustacheData results{MustacheData::type::list};
|
||||
|
||||
bool first = true;
|
||||
if (reader != nullptr) {
|
||||
/* Get the suggestions */
|
||||
reader->searchSuggestionsSmart(term, maxSuggestionCount);
|
||||
while (reader->getNextSuggestion(suggestion)) {
|
||||
kainjow::mustache::data result;
|
||||
result.set("label", suggestion);
|
||||
result.set("value", suggestion);
|
||||
SuggestionsList_t suggestions;
|
||||
reader->searchSuggestionsSmart(term, maxSuggestionCount, suggestions);
|
||||
for(auto& suggestion:suggestions) {
|
||||
MustacheData result;
|
||||
result.set("label", suggestion[0]);
|
||||
result.set("value", suggestion[0]);
|
||||
result.set("first", first);
|
||||
first = false;
|
||||
results.push_back(result);
|
||||
@@ -517,7 +581,7 @@ Response InternalServer::handle_suggest(const RequestContext& request)
|
||||
|
||||
/* Propose the fulltext search if possible */
|
||||
if (reader->hasFulltextIndex()) {
|
||||
kainjow::mustache::data result;
|
||||
MustacheData result;
|
||||
result.set("label", "containing '" + term + "'...");
|
||||
result.set("value", term + " ");
|
||||
result.set("first", first);
|
||||
@@ -549,7 +613,7 @@ Response InternalServer::handle_skin(const RequestContext& request)
|
||||
}
|
||||
response.set_mimeType(getMimeTypeForFile(resourceName));
|
||||
response.set_compress(true);
|
||||
response.set_cache(true);
|
||||
response.set_cacheable();
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -702,15 +766,32 @@ Response InternalServer::handle_random(const RequestContext& request)
|
||||
|
||||
try {
|
||||
auto entry = reader->getRandomPage();
|
||||
entry = entry.getFinalEntry();
|
||||
auto response = get_default_response();
|
||||
response.set_redirection(m_root + "/" + bookName + "/" + kiwix::urlEncode(entry.getPath()));
|
||||
return response;
|
||||
return build_redirect(bookName, entry.getFinalEntry());
|
||||
} catch(kiwix::NoEntry& e) {
|
||||
return build_404(request, bookName);
|
||||
}
|
||||
}
|
||||
|
||||
Response InternalServer::handle_captured_external(const RequestContext& request)
|
||||
{
|
||||
std::string source = "";
|
||||
try {
|
||||
source = kiwix::urlDecode(request.get_argument("source"));
|
||||
} catch (const std::out_of_range& e) {}
|
||||
|
||||
if (source.empty())
|
||||
return build_404(request, "");
|
||||
|
||||
auto data = get_default_data();
|
||||
data.set("source", source);
|
||||
auto response = get_default_response();
|
||||
response.set_template(RESOURCE::templates::captured_external_html, data);
|
||||
response.set_mimeType("text/html; charset=utf-8");
|
||||
response.set_compress(true);
|
||||
response.set_taskbar("", "");
|
||||
return response;
|
||||
}
|
||||
|
||||
Response InternalServer::handle_catalog(const RequestContext& request)
|
||||
{
|
||||
if (m_verbose.load()) {
|
||||
@@ -742,26 +823,30 @@ Response InternalServer::handle_catalog(const RequestContext& request)
|
||||
kiwix::OPDSDumper opdsDumper;
|
||||
opdsDumper.setRootLocation(m_root);
|
||||
opdsDumper.setSearchDescriptionUrl("catalog/searchdescription.xml");
|
||||
opdsDumper.setId(kiwix::to_string(uuid));
|
||||
opdsDumper.setLibrary(mp_library);
|
||||
response.set_mimeType("application/atom+xml;profile=opds-catalog;kind=acquisition; charset=utf-8");
|
||||
response.set_mimeType("application/atom+xml; profile=opds-catalog; kind=acquisition; charset=utf-8");
|
||||
std::vector<std::string> bookIdsToDump;
|
||||
if (url == "root.xml") {
|
||||
opdsDumper.setTitle("All zims");
|
||||
uuid = zim::Uuid::generate(host);
|
||||
bookIdsToDump = mp_library->filter(kiwix::Filter().valid(true).local(true).remote(true));
|
||||
} else if (url == "search") {
|
||||
std::string query;
|
||||
std::string language;
|
||||
std::vector<std::string> tags;
|
||||
std::vector<std::string> noTags;
|
||||
auto filter = kiwix::Filter().valid(true).local(true).remote(true);
|
||||
string query("<Empty query>");
|
||||
size_t count(10);
|
||||
size_t startIndex(0);
|
||||
try {
|
||||
query = request.get_argument("q");
|
||||
filter.query(query);
|
||||
} catch (const std::out_of_range&) {}
|
||||
try {
|
||||
language = request.get_argument("lang");
|
||||
filter.maxSize(extractFromString<unsigned long>(request.get_argument("maxsize")));
|
||||
} catch (...) {}
|
||||
try {
|
||||
filter.name(request.get_argument("name"));
|
||||
} catch (const std::out_of_range&) {}
|
||||
try {
|
||||
filter.lang(request.get_argument("lang"));
|
||||
} catch (const std::out_of_range&) {}
|
||||
try {
|
||||
count = extractFromString<unsigned long>(request.get_argument("count"));
|
||||
@@ -770,20 +855,14 @@ Response InternalServer::handle_catalog(const RequestContext& request)
|
||||
startIndex = extractFromString<unsigned long>(request.get_argument("start"));
|
||||
} catch (...) {}
|
||||
try {
|
||||
tags = kiwix::split(request.get_argument("notag"), ";");
|
||||
filter.acceptTags(kiwix::split(request.get_argument("tag"), ";"));
|
||||
} catch (...) {}
|
||||
try {
|
||||
noTags = kiwix::split(request.get_argument("notag"), ";");
|
||||
filter.rejectTags(kiwix::split(request.get_argument("notag"), ";"));
|
||||
} catch (...) {}
|
||||
opdsDumper.setTitle("Search result for " + query);
|
||||
uuid = zim::Uuid::generate();
|
||||
bookIdsToDump = mp_library->filter(
|
||||
kiwix::Filter().valid(true).local(true).remote(true)
|
||||
.query(query)
|
||||
.lang(language)
|
||||
.acceptTags(tags)
|
||||
.rejectTags(noTags)
|
||||
);
|
||||
bookIdsToDump = mp_library->filter(filter);
|
||||
auto totalResults = bookIdsToDump.size();
|
||||
bookIdsToDump.erase(bookIdsToDump.begin(), bookIdsToDump.begin()+startIndex);
|
||||
if (count>0 && bookIdsToDump.size() > count) {
|
||||
@@ -792,40 +871,57 @@ Response InternalServer::handle_catalog(const RequestContext& request)
|
||||
opdsDumper.setOpenSearchInfo(totalResults, startIndex, bookIdsToDump.size());
|
||||
}
|
||||
|
||||
opdsDumper.setId(kiwix::to_string(uuid));
|
||||
response.set_content(opdsDumper.dumpOPDSFeed(bookIdsToDump));
|
||||
return response;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
std::string get_book_name(const RequestContext& request)
|
||||
{
|
||||
try {
|
||||
return request.get_url_part(0);
|
||||
} catch (const std::out_of_range& e) {
|
||||
return std::string();
|
||||
}
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
std::shared_ptr<Reader>
|
||||
InternalServer::get_reader(const std::string& bookName) const
|
||||
{
|
||||
std::shared_ptr<Reader> reader;
|
||||
try {
|
||||
const std::string bookId = mp_nameMapper->getIdForName(bookName);
|
||||
reader = mp_library->getReaderById(bookId);
|
||||
} catch (const std::out_of_range& e) {
|
||||
}
|
||||
return reader;
|
||||
}
|
||||
|
||||
Response
|
||||
InternalServer::build_redirect(const std::string& bookName, const kiwix::Entry& entry) const
|
||||
{
|
||||
auto response = get_default_response();
|
||||
response.set_redirection(m_root + "/" + bookName + "/" +
|
||||
kiwix::urlEncode(entry.getPath()));
|
||||
return response;
|
||||
}
|
||||
|
||||
Response InternalServer::handle_content(const RequestContext& request)
|
||||
{
|
||||
if (m_verbose.load()) {
|
||||
printf("** running handle_content\n");
|
||||
}
|
||||
|
||||
std::string baseUrl;
|
||||
std::string content;
|
||||
std::string mimeType;
|
||||
|
||||
kiwix::Entry entry;
|
||||
|
||||
std::string bookName;
|
||||
try {
|
||||
bookName = request.get_url_part(0);
|
||||
} catch (const std::out_of_range& e) {
|
||||
return build_homepage(request);
|
||||
}
|
||||
const std::string bookName = get_book_name(request);
|
||||
if (bookName.empty())
|
||||
return build_homepage(request);
|
||||
|
||||
std::string bookId;
|
||||
std::shared_ptr<Reader> reader;
|
||||
try {
|
||||
bookId = mp_nameMapper->getIdForName(bookName);
|
||||
reader = mp_library->getReaderById(bookId);
|
||||
} catch (const std::out_of_range& e) {
|
||||
return build_404(request, bookName);
|
||||
}
|
||||
|
||||
const std::shared_ptr<Reader> reader = get_reader(bookName);
|
||||
if (reader == nullptr) {
|
||||
return build_404(request, bookName);
|
||||
}
|
||||
@@ -835,16 +931,14 @@ Response InternalServer::handle_content(const RequestContext& request)
|
||||
urlStr = urlStr.substr(1);
|
||||
}
|
||||
|
||||
kiwix::Entry entry;
|
||||
|
||||
try {
|
||||
entry = reader->getEntryFromPath(urlStr);
|
||||
if (entry.isRedirect() || urlStr.empty()) {
|
||||
// If urlStr is empty, we want to mainPage.
|
||||
// We must do a redirection to the real page.
|
||||
entry = entry.getFinalEntry();
|
||||
auto response = get_default_response();
|
||||
response.set_redirection(m_root + "/" + bookName + "/" +
|
||||
kiwix::urlEncode(entry.getPath()));
|
||||
return response;
|
||||
return build_redirect(bookName, entry.getFinalEntry());
|
||||
}
|
||||
} catch(kiwix::NoEntry& e) {
|
||||
if (m_verbose.load())
|
||||
@@ -853,47 +947,19 @@ Response InternalServer::handle_content(const RequestContext& request)
|
||||
return build_404(request, bookName);
|
||||
}
|
||||
|
||||
try {
|
||||
mimeType = entry.getMimetype();
|
||||
} catch (exception& e) {
|
||||
mimeType = "application/octet-stream";
|
||||
}
|
||||
auto response = get_default_response();
|
||||
|
||||
response.set_entry(entry, request);
|
||||
|
||||
if (m_verbose.load()) {
|
||||
printf("Found %s\n", urlStr.c_str());
|
||||
printf("mimeType: %s\n", mimeType.c_str());
|
||||
printf("Found %s\n", entry.getPath().c_str());
|
||||
printf("mimeType: %s\n", response.get_mimeType().c_str());
|
||||
}
|
||||
|
||||
if (mimeType.find("text/") != string::npos
|
||||
|| mimeType.find("application/javascript") != string::npos
|
||||
|| mimeType.find("application/json") != string::npos) {
|
||||
zim::Blob raw_content = entry.getBlob();
|
||||
content = string(raw_content.data(), raw_content.size());
|
||||
auto response = get_default_response();
|
||||
if (response.get_mimeType().find("text/html") != string::npos)
|
||||
response.set_taskbar(bookName, reader->getTitle());
|
||||
|
||||
if (mimeType.find("text/html") != string::npos)
|
||||
response.set_taskbar(bookName, reader->getTitle());
|
||||
|
||||
response.set_mimeType(mimeType);
|
||||
response.set_content(content);
|
||||
response.set_compress(true);
|
||||
response.set_cache(true);
|
||||
return response;
|
||||
} else {
|
||||
int range_len;
|
||||
if (request.get_range().second == -1) {
|
||||
range_len = entry.getSize() - request.get_range().first;
|
||||
} else {
|
||||
range_len = request.get_range().second - request.get_range().first;
|
||||
}
|
||||
auto response = get_default_response();
|
||||
response.set_entry(entry);
|
||||
response.set_mimeType(mimeType);
|
||||
response.set_range_first(request.get_range().first);
|
||||
response.set_range_len(range_len);
|
||||
response.set_cache(true);
|
||||
return response;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
127
src/server/byte_range.cpp
Normal file
127
src/server/byte_range.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
|
||||
|
||||
#include "byte_range.h"
|
||||
|
||||
#include "tools/stringTools.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
|
||||
namespace kiwix {
|
||||
|
||||
namespace {
|
||||
|
||||
ByteRange parseByteRange(const std::string& rangeStr)
|
||||
{
|
||||
std::istringstream iss(rangeStr);
|
||||
|
||||
int64_t start, end = INT64_MAX;
|
||||
if (iss >> start) {
|
||||
if ( start < 0 ) {
|
||||
if ( iss.eof() )
|
||||
return ByteRange(-start);
|
||||
} else {
|
||||
char c;
|
||||
if (iss >> c && c=='-') {
|
||||
iss >> end; // if this fails, end is not modified, which is OK
|
||||
if (iss.eof() && start <= end)
|
||||
return ByteRange(ByteRange::PARSED, start, end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ByteRange(ByteRange::INVALID, 0, INT64_MAX);
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
ByteRange::ByteRange()
|
||||
: kind_(NONE)
|
||||
, first_(0)
|
||||
, last_(INT64_MAX)
|
||||
{}
|
||||
|
||||
ByteRange::ByteRange(Kind kind, int64_t first, int64_t last)
|
||||
: kind_(kind)
|
||||
, first_(first)
|
||||
, last_(last)
|
||||
{
|
||||
assert(kind != NONE);
|
||||
assert(first >= 0);
|
||||
assert(last >= first || (first == 0 && last == -1));
|
||||
}
|
||||
|
||||
ByteRange::ByteRange(int64_t suffix_length)
|
||||
: kind_(PARSED)
|
||||
, first_(-suffix_length)
|
||||
, last_(INT64_MAX)
|
||||
{
|
||||
assert(suffix_length > 0);
|
||||
}
|
||||
|
||||
int64_t ByteRange::first() const
|
||||
{
|
||||
assert(kind_ > PARSED);
|
||||
return first_;
|
||||
}
|
||||
|
||||
int64_t ByteRange::last() const
|
||||
{
|
||||
assert(kind_ > PARSED);
|
||||
return last_;
|
||||
}
|
||||
|
||||
int64_t ByteRange::length() const
|
||||
{
|
||||
assert(kind_ > PARSED);
|
||||
return last_ + 1 - first_;
|
||||
}
|
||||
|
||||
ByteRange ByteRange::parse(const std::string& rangeStr)
|
||||
{
|
||||
const std::string byteUnitSpec("bytes=");
|
||||
if ( ! kiwix::startsWith(rangeStr, byteUnitSpec) )
|
||||
return ByteRange(INVALID, 0, INT64_MAX);
|
||||
|
||||
return parseByteRange(rangeStr.substr(byteUnitSpec.size()));
|
||||
}
|
||||
|
||||
ByteRange ByteRange::resolve(int64_t contentSize) const
|
||||
{
|
||||
if ( kind() == NONE )
|
||||
return ByteRange(RESOLVED_FULL_CONTENT, 0, contentSize-1);
|
||||
|
||||
if ( kind() == INVALID )
|
||||
return ByteRange(RESOLVED_UNSATISFIABLE, 0, contentSize-1);
|
||||
|
||||
const int64_t resolved_first = first_ < 0
|
||||
? std::max(int64_t(0), contentSize + first_)
|
||||
: first_;
|
||||
|
||||
const int64_t resolved_last = std::min(contentSize-1, last_);
|
||||
|
||||
if ( resolved_first > resolved_last )
|
||||
return ByteRange(RESOLVED_UNSATISFIABLE, 0, contentSize-1);
|
||||
|
||||
return ByteRange(RESOLVED_PARTIAL_CONTENT, resolved_first, resolved_last);
|
||||
}
|
||||
|
||||
} // namespace kiwix
|
||||
86
src/server/byte_range.h
Normal file
86
src/server/byte_range.h
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2020 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 KIWIXLIB_SERVER_BYTE_RANGE_H
|
||||
#define KIWIXLIB_SERVER_BYTE_RANGE_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace kiwix {
|
||||
|
||||
class ByteRange
|
||||
{
|
||||
public: // types
|
||||
// ByteRange is parsed in a request, then it must be resolved (taking
|
||||
// into account the actual size of the requested resource) before
|
||||
// being applied in the response.
|
||||
// The Kind enum represents possible states in such a lifecycle.
|
||||
enum Kind {
|
||||
// The request is not a range request (no Range header)
|
||||
NONE,
|
||||
|
||||
// The value of the Range header is not a valid continuous
|
||||
// range. Note that a valid (according to RFC7233) sequence of multiple
|
||||
// byte ranges is considered invalid in the current implementation
|
||||
// (i.e. only single-range partial requests are supported).
|
||||
INVALID,
|
||||
|
||||
// This byte-range has been successfully parsed from the request
|
||||
PARSED,
|
||||
|
||||
// This is a response to a regular (non-range) request
|
||||
RESOLVED_FULL_CONTENT,
|
||||
|
||||
// The range request is invalid or unsatisfiable
|
||||
RESOLVED_UNSATISFIABLE,
|
||||
|
||||
// This is a response to a (satisfiable) range request
|
||||
RESOLVED_PARTIAL_CONTENT,
|
||||
};
|
||||
|
||||
public: // functions
|
||||
// Constructs a ByteRange object of NONE kind
|
||||
ByteRange();
|
||||
|
||||
// Constructs a ByteRange object of the given kind (except NONE)
|
||||
ByteRange(Kind kind, int64_t first, int64_t last);
|
||||
|
||||
// Constructs a ByteRange object of PARSED kind corresponding to a
|
||||
// range request of the form "Range: bytes=-suffix_length"
|
||||
explicit ByteRange(int64_t suffix_length);
|
||||
|
||||
Kind kind() const { return kind_; }
|
||||
int64_t first() const;
|
||||
int64_t last() const;
|
||||
int64_t length() const;
|
||||
|
||||
static ByteRange parse(const std::string& rangeStr);
|
||||
ByteRange resolve(int64_t contentSize) const;
|
||||
|
||||
private: // data
|
||||
Kind kind_;
|
||||
int64_t first_;
|
||||
int64_t last_;
|
||||
};
|
||||
|
||||
} // namespace kiwix
|
||||
|
||||
#endif //KIWIXLIB_SERVER_BYTE_RANGE_H
|
||||
135
src/server/etag.cpp
Normal file
135
src/server/etag.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
|
||||
|
||||
#include "etag.h"
|
||||
|
||||
#include "tools/stringTools.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
namespace kiwix {
|
||||
|
||||
namespace {
|
||||
|
||||
// Characters in the options part of the ETag could in principle be picked up
|
||||
// from the latin alphabet in natural order (the character corresponding to
|
||||
// ETag::Option opt would be 'a'+opt; that would somewhat simplify the code in
|
||||
// this file). However it is better to have some mnemonics in the option names,
|
||||
// hence below variable: all_options[opt] corresponds to the character going
|
||||
// into the ETag for ETag::Option opt.
|
||||
// IMPORTANT: The characters in all_options must come in sorted order (so that
|
||||
// IMPORTANT: isValidOptionsString() works correctly).
|
||||
const char all_options[] = "cz";
|
||||
|
||||
static_assert(ETag::OPTION_COUNT == sizeof(all_options) - 1, "");
|
||||
|
||||
bool isValidServerId(const std::string& s)
|
||||
{
|
||||
return !s.empty() && s.find_first_of("\"/") == std::string::npos;
|
||||
}
|
||||
|
||||
bool isSubsequenceOf(const std::string& s, const std::string& sortedString)
|
||||
{
|
||||
std::string::size_type i = 0;
|
||||
for ( const char c : s )
|
||||
{
|
||||
const std::string::size_type j = sortedString.find(c, i);
|
||||
if ( j == std::string::npos )
|
||||
return false;
|
||||
i = j+1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isValidOptionsString(const std::string& s)
|
||||
{
|
||||
return isSubsequenceOf(s, all_options);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
void ETag::set_option(Option opt)
|
||||
{
|
||||
if ( ! get_option(opt) )
|
||||
{
|
||||
m_options.push_back(all_options[opt]);
|
||||
std::sort(m_options.begin(), m_options.end());
|
||||
}
|
||||
}
|
||||
|
||||
bool ETag::get_option(Option opt) const
|
||||
{
|
||||
return m_options.find(all_options[opt]) != std::string::npos;
|
||||
}
|
||||
|
||||
std::string ETag::get_etag() const
|
||||
{
|
||||
if ( m_serverId.empty() )
|
||||
return std::string();
|
||||
|
||||
return "\"" + m_serverId + "/" + m_options + "\"";
|
||||
}
|
||||
|
||||
ETag::ETag(const std::string& serverId, const std::string& options)
|
||||
{
|
||||
if ( isValidServerId(serverId) && isValidOptionsString(options) )
|
||||
{
|
||||
m_serverId = serverId;
|
||||
m_options = options;
|
||||
}
|
||||
}
|
||||
|
||||
ETag ETag::parse(std::string s)
|
||||
{
|
||||
if ( kiwix::startsWith("W/", s) )
|
||||
s = s.substr(2);
|
||||
|
||||
if ( s.front() != '"' || s.back() != '"' )
|
||||
return ETag();
|
||||
|
||||
s = s.substr(1, s.size()-2);
|
||||
|
||||
const std::string::size_type i = s.find('/');
|
||||
if ( i == std::string::npos )
|
||||
return ETag();
|
||||
|
||||
return ETag(s.substr(0, i), s.substr(i+1));
|
||||
}
|
||||
|
||||
ETag ETag::match(const std::string& etags, const std::string& server_id)
|
||||
{
|
||||
std::istringstream ss(etags);
|
||||
std::string etag_str;
|
||||
while ( ss >> etag_str )
|
||||
{
|
||||
if ( etag_str.back() == ',' )
|
||||
etag_str.pop_back();
|
||||
|
||||
const ETag etag = parse(etag_str);
|
||||
if ( etag && etag.m_serverId == server_id )
|
||||
return etag;
|
||||
}
|
||||
|
||||
return ETag();
|
||||
}
|
||||
|
||||
} // namespace kiwix
|
||||
85
src/server/etag.h
Normal file
85
src/server/etag.h
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2020 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 KIWIXLIB_SERVER_ETAG_H
|
||||
#define KIWIXLIB_SERVER_ETAG_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace kiwix {
|
||||
|
||||
// The ETag string used by Kiwix server (more precisely, its value inside the
|
||||
// double quotes) consists of two parts:
|
||||
//
|
||||
// 1. ServerId - The string obtained on server start up
|
||||
//
|
||||
// 2. Options - Zero or more characters encoding the values of some of the
|
||||
// headers of the response
|
||||
//
|
||||
// The two parts are separated with a slash (/) symbol (which is always present,
|
||||
// even when the the options part is empty). Neither portion of a Kiwix ETag
|
||||
// may contain the slash symbol.
|
||||
// Examples of valid Kiwix server ETags (including the double quotes):
|
||||
//
|
||||
// "abcdefghijklmn/"
|
||||
// "1234567890/z"
|
||||
// "1234567890/cz"
|
||||
//
|
||||
// The options part of the Kiwix ETag allows to correctly set the required
|
||||
// headers when responding to a conditional If-None-Match request with a 304
|
||||
// (Not Modified) response without following the full code path that would
|
||||
// discover the necessary options.
|
||||
|
||||
class ETag
|
||||
{
|
||||
public: // types
|
||||
enum Option {
|
||||
CACHEABLE_ENTITY,
|
||||
COMPRESSED_CONTENT,
|
||||
OPTION_COUNT
|
||||
};
|
||||
|
||||
public: // functions
|
||||
ETag() {}
|
||||
|
||||
void set_server_id(const std::string& id) { m_serverId = id; }
|
||||
void set_option(Option opt);
|
||||
|
||||
explicit operator bool() const { return !m_serverId.empty(); }
|
||||
|
||||
bool get_option(Option opt) const;
|
||||
std::string get_etag() const;
|
||||
|
||||
|
||||
static ETag match(const std::string& etags, const std::string& server_id);
|
||||
|
||||
private: // functions
|
||||
ETag(const std::string& serverId, const std::string& options);
|
||||
|
||||
static ETag parse(std::string s);
|
||||
|
||||
private: // data
|
||||
std::string m_serverId;
|
||||
std::string m_options;
|
||||
};
|
||||
|
||||
} // namespace kiwix
|
||||
|
||||
#endif // KIWIXLIB_SERVER_ETAG_H
|
||||
@@ -30,102 +30,78 @@ namespace kiwix {
|
||||
|
||||
static std::atomic_ullong s_requestIndex(0);
|
||||
|
||||
namespace {
|
||||
|
||||
RequestMethod str2RequestMethod(const std::string& method) {
|
||||
if (method == "GET") return RequestMethod::GET;
|
||||
else if (method == "HEAD") return RequestMethod::HEAD;
|
||||
else if (method == "POST") return RequestMethod::POST;
|
||||
else if (method == "PUT") return RequestMethod::PUT;
|
||||
else if (method == "DELETE") return RequestMethod::DELETE_;
|
||||
else if (method == "CONNECT") return RequestMethod::CONNECT;
|
||||
else if (method == "OPTIONS") return RequestMethod::OPTIONS;
|
||||
else if (method == "TRACE") return RequestMethod::TRACE;
|
||||
else if (method == "PATCH") return RequestMethod::PATCH;
|
||||
else return RequestMethod::OTHER;
|
||||
}
|
||||
|
||||
std::string
|
||||
fullURL2LocalURL(const std::string& full_url, const std::string& rootLocation)
|
||||
{
|
||||
if (rootLocation.empty()) {
|
||||
// nothing special to handle.
|
||||
return full_url;
|
||||
} else if (full_url == rootLocation) {
|
||||
return "/";
|
||||
} else if (full_url.size() > rootLocation.size() &&
|
||||
full_url.substr(0, rootLocation.size()+1) == rootLocation + "/") {
|
||||
return full_url.substr(rootLocation.size());
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
RequestContext::RequestContext(struct MHD_Connection* connection,
|
||||
std::string rootLocation,
|
||||
const std::string& _url,
|
||||
const std::string& method,
|
||||
const std::string& _method,
|
||||
const std::string& version) :
|
||||
full_url(_url),
|
||||
url(_url),
|
||||
valid_url(true),
|
||||
url(fullURL2LocalURL(_url, rootLocation)),
|
||||
method(str2RequestMethod(_method)),
|
||||
version(version),
|
||||
requestIndex(s_requestIndex++),
|
||||
acceptEncodingDeflate(false),
|
||||
accept_range(false),
|
||||
range_pair(0, -1)
|
||||
byteRange_()
|
||||
{
|
||||
if (method == "GET") {
|
||||
this->method = RequestMethod::GET;
|
||||
} else if (method == "HEAD") {
|
||||
this->method = RequestMethod::HEAD;
|
||||
} else if (method == "POST") {
|
||||
this->method = RequestMethod::POST;
|
||||
} else if (method == "PUT") {
|
||||
this->method = RequestMethod::PUT;
|
||||
} else if (method == "DELETE") {
|
||||
this->method = RequestMethod::DELETE_;
|
||||
} else if (method == "CONNECT") {
|
||||
this->method = RequestMethod::CONNECT;
|
||||
} else if (method == "OPTIONS") {
|
||||
this->method = RequestMethod::OPTIONS;
|
||||
} else if (method == "TRACE") {
|
||||
this->method = RequestMethod::TRACE;
|
||||
} else if (method == "PATCH") {
|
||||
this->method = RequestMethod::PATCH;
|
||||
} else {
|
||||
this->method = RequestMethod::OTHER;
|
||||
}
|
||||
|
||||
MHD_get_connection_values(connection, MHD_HEADER_KIND, &RequestContext::fill_header, this);
|
||||
MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, &RequestContext::fill_argument, this);
|
||||
|
||||
valid_url = true;
|
||||
if (rootLocation.empty()) {
|
||||
// nothing special to handle.
|
||||
url = full_url;
|
||||
} else {
|
||||
if (full_url == rootLocation) {
|
||||
url = "/";
|
||||
} else if (full_url.size() > rootLocation.size() &&
|
||||
full_url.substr(0, rootLocation.size()+1) == rootLocation + "/") {
|
||||
url = full_url.substr(rootLocation.size());
|
||||
} else {
|
||||
valid_url = false;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
acceptEncodingDeflate =
|
||||
(get_header(MHD_HTTP_HEADER_ACCEPT_ENCODING).find("deflate") != std::string::npos);
|
||||
} catch (const std::out_of_range&) {}
|
||||
|
||||
/*Check if range is requested. */
|
||||
try {
|
||||
auto range = get_header(MHD_HTTP_HEADER_RANGE);
|
||||
int start = 0;
|
||||
int end = -1;
|
||||
std::istringstream iss(range);
|
||||
char c;
|
||||
|
||||
iss >> start >> c;
|
||||
if (iss.good() && c=='-') {
|
||||
iss >> end;
|
||||
if (iss.fail()) {
|
||||
// Something went wrong will extracting.
|
||||
end = -1;
|
||||
}
|
||||
if (iss.eof()) {
|
||||
accept_range = true;
|
||||
range_pair = std::pair<int, int>(start, end);
|
||||
}
|
||||
}
|
||||
byteRange_ = ByteRange::parse(get_header(MHD_HTTP_HEADER_RANGE));
|
||||
} catch (const std::out_of_range&) {}
|
||||
}
|
||||
|
||||
RequestContext::~RequestContext()
|
||||
{}
|
||||
|
||||
|
||||
int RequestContext::fill_header(void *__this, enum MHD_ValueKind kind,
|
||||
const char *key, const char *value)
|
||||
MHD_Result RequestContext::fill_header(void *__this, enum MHD_ValueKind kind,
|
||||
const char *key, const char *value)
|
||||
{
|
||||
RequestContext *_this = static_cast<RequestContext*>(__this);
|
||||
_this->headers[key] = value;
|
||||
return MHD_YES;
|
||||
}
|
||||
|
||||
int RequestContext::fill_argument(void *__this, enum MHD_ValueKind kind,
|
||||
const char *key, const char* value)
|
||||
MHD_Result RequestContext::fill_argument(void *__this, enum MHD_ValueKind kind,
|
||||
const char *key, const char* value)
|
||||
{
|
||||
RequestContext *_this = static_cast<RequestContext*>(__this);
|
||||
_this->arguments[key] = value == nullptr ? "" : value;
|
||||
@@ -147,10 +123,11 @@ void RequestContext::print_debug_info() const {
|
||||
printf(" - %s : '%s'\n", it->first.c_str(), it->second.c_str());
|
||||
}
|
||||
printf("Parsed : \n");
|
||||
printf("full_url: %s\n", full_url.c_str());
|
||||
printf("url : %s\n", url.c_str());
|
||||
printf("acceptEncodingDeflate : %d\n", acceptEncodingDeflate);
|
||||
printf("has_range : %d\n", accept_range);
|
||||
printf("is_valid_url : %d\n", valid_url);
|
||||
printf("has_range : %d\n", byteRange_.kind() != ByteRange::NONE);
|
||||
printf("is_valid_url : %d\n", is_valid_url());
|
||||
printf(".............\n");
|
||||
}
|
||||
|
||||
@@ -188,15 +165,11 @@ std::string RequestContext::get_full_url() const {
|
||||
}
|
||||
|
||||
bool RequestContext::is_valid_url() const {
|
||||
return valid_url;
|
||||
return !url.empty();
|
||||
}
|
||||
|
||||
bool RequestContext::has_range() const {
|
||||
return accept_range;
|
||||
}
|
||||
|
||||
std::pair<int, int> RequestContext::get_range() const {
|
||||
return range_pair;
|
||||
ByteRange RequestContext::get_range() const {
|
||||
return byteRange_;
|
||||
}
|
||||
|
||||
template<>
|
||||
|
||||
@@ -27,8 +27,10 @@
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "byte_range.h"
|
||||
|
||||
extern "C" {
|
||||
#include <microhttpd.h>
|
||||
#include "microhttpd_wrapper.h"
|
||||
}
|
||||
|
||||
namespace kiwix {
|
||||
@@ -51,7 +53,7 @@ class IndexError: public std::runtime_error {};
|
||||
|
||||
|
||||
class RequestContext {
|
||||
public:
|
||||
public: // functions
|
||||
RequestContext(struct MHD_Connection* connection,
|
||||
std::string rootLocation,
|
||||
const std::string& url,
|
||||
@@ -78,28 +80,26 @@ class RequestContext {
|
||||
std::string get_url_part(int part) const;
|
||||
std::string get_full_url() const;
|
||||
|
||||
bool has_range() const;
|
||||
std::pair<int, int> get_range() const;
|
||||
ByteRange get_range() const;
|
||||
|
||||
bool can_compress() const { return acceptEncodingDeflate; }
|
||||
|
||||
private:
|
||||
private: // data
|
||||
std::string full_url;
|
||||
std::string url;
|
||||
bool valid_url;
|
||||
RequestMethod method;
|
||||
std::string version;
|
||||
unsigned long long requestIndex;
|
||||
|
||||
bool acceptEncodingDeflate;
|
||||
|
||||
bool accept_range;
|
||||
std::pair<int, int> range_pair;
|
||||
ByteRange byteRange_;
|
||||
std::map<std::string, std::string> headers;
|
||||
std::map<std::string, std::string> arguments;
|
||||
|
||||
static int fill_header(void *, enum MHD_ValueKind, const char*, const char*);
|
||||
static int fill_argument(void *, enum MHD_ValueKind, const char*, const char*);
|
||||
private: // functions
|
||||
static MHD_Result fill_header(void *, enum MHD_ValueKind, const char*, const char*);
|
||||
static MHD_Result fill_argument(void *, enum MHD_ValueKind, const char*, const char*);
|
||||
};
|
||||
|
||||
template<> std::string RequestContext::get_argument(const std::string& name) const;
|
||||
|
||||
@@ -17,7 +17,32 @@
|
||||
|
||||
namespace kiwix {
|
||||
|
||||
Response::Response(const std::string& root, bool verbose, bool withTaskbar, bool withLibraryButton)
|
||||
namespace
|
||||
{
|
||||
// some utilities
|
||||
|
||||
std::string get_mime_type(const kiwix::Entry& entry)
|
||||
{
|
||||
try {
|
||||
return entry.getMimetype();
|
||||
} catch (exception& e) {
|
||||
return "application/octet-stream";
|
||||
}
|
||||
}
|
||||
|
||||
bool is_compressible_mime_type(const std::string& mimeType)
|
||||
{
|
||||
return mimeType.find("text/") != string::npos
|
||||
|| mimeType.find("application/javascript") != string::npos
|
||||
|| mimeType.find("application/atom") != string::npos
|
||||
|| mimeType.find("application/opensearchdescription") != string::npos
|
||||
|| mimeType.find("application/json") != string::npos;
|
||||
}
|
||||
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
Response::Response(const std::string& root, bool verbose, bool withTaskbar, bool withLibraryButton, bool blockExternalLinks)
|
||||
: m_verbose(verbose),
|
||||
m_root(root),
|
||||
m_content(""),
|
||||
@@ -25,17 +50,15 @@ Response::Response(const std::string& root, bool verbose, bool withTaskbar, bool
|
||||
m_returnCode(MHD_HTTP_OK),
|
||||
m_withTaskbar(withTaskbar),
|
||||
m_withLibraryButton(withLibraryButton),
|
||||
m_useCache(false),
|
||||
m_blockExternalLinks(blockExternalLinks),
|
||||
m_addTaskbar(false),
|
||||
m_bookName(""),
|
||||
m_startRange(0),
|
||||
m_lenRange(0)
|
||||
m_bookName("")
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
static int print_key_value (void *cls, enum MHD_ValueKind kind,
|
||||
const char *key, const char *value)
|
||||
static MHD_Result print_key_value (void *cls, enum MHD_ValueKind kind,
|
||||
const char *key, const char *value)
|
||||
{
|
||||
printf (" - %s: '%s'\n", key, value);
|
||||
return MHD_YES;
|
||||
@@ -127,88 +150,158 @@ void Response::introduce_taskbar()
|
||||
}
|
||||
|
||||
|
||||
|
||||
int Response::send(const RequestContext& request, MHD_Connection* connection)
|
||||
void Response::inject_externallinks_blocker()
|
||||
{
|
||||
MHD_Response* response = nullptr;
|
||||
switch (m_mode) {
|
||||
case ResponseMode::RAW_CONTENT : {
|
||||
if (m_addTaskbar) {
|
||||
introduce_taskbar();
|
||||
}
|
||||
kainjow::mustache::data data;
|
||||
data.set("root", m_root);
|
||||
auto script_tag = render_template(RESOURCE::templates::external_blocker_part_html, data);
|
||||
m_content = appendToFirstOccurence(
|
||||
m_content,
|
||||
"<head>",
|
||||
script_tag);
|
||||
}
|
||||
|
||||
bool shouldCompress = m_compress && request.can_compress();
|
||||
shouldCompress &= m_mimeType.find("text/") != string::npos
|
||||
|| m_mimeType.find("application/javascript") != string::npos
|
||||
|| m_mimeType.find("application/json") != string::npos;
|
||||
bool
|
||||
Response::can_compress(const RequestContext& request) const
|
||||
{
|
||||
return request.can_compress()
|
||||
&& is_compressible_mime_type(m_mimeType)
|
||||
&& (m_content.size() > KIWIX_MIN_CONTENT_SIZE_TO_DEFLATE);
|
||||
}
|
||||
|
||||
shouldCompress &= (m_content.size() > KIWIX_MIN_CONTENT_SIZE_TO_DEFLATE);
|
||||
MHD_Response*
|
||||
Response::create_error_response(const RequestContext& request) const
|
||||
{
|
||||
MHD_Response* response = MHD_create_response_from_buffer(0, NULL, MHD_RESPMEM_PERSISTENT);
|
||||
if ( m_returnCode == 416 ) {
|
||||
std::ostringstream oss;
|
||||
oss << "bytes */" << m_byteRange.length();
|
||||
|
||||
if (shouldCompress) {
|
||||
std::vector<Bytef> compr_buffer(compressBound(m_content.size()));
|
||||
uLongf comprLen = compr_buffer.capacity();
|
||||
int err = compress(&compr_buffer[0],
|
||||
&comprLen,
|
||||
(const Bytef*)(m_content.data()),
|
||||
m_content.size());
|
||||
if (err == Z_OK && comprLen > 2 && comprLen < (m_content.size() + 2)) {
|
||||
/* /!\ Internet Explorer has a bug with deflate compression.
|
||||
It can not handle the first two bytes (compression headers)
|
||||
We need to chunk them off (move the content 2bytes)
|
||||
It has no incidence on other browsers
|
||||
See http://www.subbu.org/blog/2008/03/ie7-deflate-or-not and comments */
|
||||
m_content = string((char*)&compr_buffer[2], comprLen - 2);
|
||||
} else {
|
||||
shouldCompress = false;
|
||||
}
|
||||
}
|
||||
MHD_add_response_header(response,
|
||||
MHD_HTTP_HEADER_CONTENT_RANGE, oss.str().c_str());
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
response = MHD_create_response_from_buffer(
|
||||
m_content.size(), const_cast<char*>(m_content.data()), MHD_RESPMEM_MUST_COPY);
|
||||
MHD_Response*
|
||||
Response::create_raw_content_mhd_response(const RequestContext& request)
|
||||
{
|
||||
if (m_addTaskbar) {
|
||||
introduce_taskbar();
|
||||
}
|
||||
if ( m_blockExternalLinks ) {
|
||||
inject_externallinks_blocker();
|
||||
}
|
||||
|
||||
if (shouldCompress) {
|
||||
MHD_add_response_header(
|
||||
response, MHD_HTTP_HEADER_VARY, "Accept-Encoding");
|
||||
MHD_add_response_header(
|
||||
response, MHD_HTTP_HEADER_CONTENT_ENCODING, "deflate");
|
||||
}
|
||||
MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, m_mimeType.c_str());
|
||||
break;
|
||||
}
|
||||
|
||||
case ResponseMode::REDIRECTION : {
|
||||
response = MHD_create_response_from_buffer(0, nullptr, MHD_RESPMEM_MUST_COPY);
|
||||
MHD_add_response_header(response, MHD_HTTP_HEADER_LOCATION, m_content.c_str());
|
||||
break;
|
||||
}
|
||||
|
||||
case ResponseMode::ENTRY : {
|
||||
response = MHD_create_response_from_callback(m_entry.getSize(),
|
||||
16384,
|
||||
callback_reader_from_entry,
|
||||
new RunningResponse(m_entry, m_startRange),
|
||||
callback_free_response);
|
||||
MHD_add_response_header(response,
|
||||
MHD_HTTP_HEADER_CONTENT_TYPE, m_mimeType.c_str());
|
||||
MHD_add_response_header(response, MHD_HTTP_HEADER_ACCEPT_RANGES, "bytes");
|
||||
std::ostringstream oss;
|
||||
oss << "bytes " << m_startRange << "-" << m_startRange + m_lenRange - 1
|
||||
<< "/" << m_entry.getSize();
|
||||
|
||||
MHD_add_response_header(response,
|
||||
MHD_HTTP_HEADER_CONTENT_RANGE, oss.str().c_str());
|
||||
|
||||
MHD_add_response_header(response,
|
||||
MHD_HTTP_HEADER_CONTENT_LENGTH, kiwix::to_string(m_lenRange).c_str());
|
||||
break;
|
||||
bool shouldCompress = m_compress && can_compress(request);
|
||||
if (shouldCompress) {
|
||||
std::vector<Bytef> compr_buffer(compressBound(m_content.size()));
|
||||
uLongf comprLen = compr_buffer.capacity();
|
||||
int err = compress(&compr_buffer[0],
|
||||
&comprLen,
|
||||
(const Bytef*)(m_content.data()),
|
||||
m_content.size());
|
||||
if (err == Z_OK && comprLen > 2 && comprLen < (m_content.size() + 2)) {
|
||||
/* /!\ Internet Explorer has a bug with deflate compression.
|
||||
It can not handle the first two bytes (compression headers)
|
||||
We need to chunk them off (move the content 2bytes)
|
||||
It has no incidence on other browsers
|
||||
See http://www.subbu.org/blog/2008/03/ie7-deflate-or-not and comments */
|
||||
m_content = string((char*)&compr_buffer[2], comprLen - 2);
|
||||
m_etag.set_option(ETag::COMPRESSED_CONTENT);
|
||||
} else {
|
||||
shouldCompress = false;
|
||||
}
|
||||
}
|
||||
|
||||
MHD_add_response_header(response, "Access-Control-Allow-Origin", "*");
|
||||
MHD_add_response_header(response, MHD_HTTP_HEADER_CACHE_CONTROL,
|
||||
m_useCache ? "max-age=2723040, public" : "no-cache, no-store, must-revalidate");
|
||||
MHD_Response* response = MHD_create_response_from_buffer(
|
||||
m_content.size(), const_cast<char*>(m_content.data()), MHD_RESPMEM_MUST_COPY);
|
||||
|
||||
if (m_returnCode == MHD_HTTP_OK && request.has_range())
|
||||
// At shis point m_etag.get_option(ETag::COMPRESSED_CONTENT) and
|
||||
// shouldCompress can have different values. This can happen for a 304 (Not
|
||||
// Modified) response generated while handling a conditional If-None-Match
|
||||
// request. In that case the m_etag (together with its COMPRESSED_CONTENT
|
||||
// option) is obtained from the ETag list of the If-None-Match header and the
|
||||
// response has no body (which shouldn't be compressed).
|
||||
if ( m_etag.get_option(ETag::COMPRESSED_CONTENT) ) {
|
||||
MHD_add_response_header(
|
||||
response, MHD_HTTP_HEADER_VARY, "Accept-Encoding");
|
||||
}
|
||||
if (shouldCompress) {
|
||||
MHD_add_response_header(
|
||||
response, MHD_HTTP_HEADER_CONTENT_ENCODING, "deflate");
|
||||
}
|
||||
MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, m_mimeType.c_str());
|
||||
return response;
|
||||
}
|
||||
|
||||
MHD_Response*
|
||||
Response::create_redirection_mhd_response() const
|
||||
{
|
||||
MHD_Response* response = MHD_create_response_from_buffer(0, nullptr, MHD_RESPMEM_MUST_COPY);
|
||||
MHD_add_response_header(response, MHD_HTTP_HEADER_LOCATION, m_content.c_str());
|
||||
return response;
|
||||
}
|
||||
|
||||
MHD_Response*
|
||||
Response::create_entry_mhd_response() const
|
||||
{
|
||||
const auto content_length = m_byteRange.length();
|
||||
MHD_Response* response = MHD_create_response_from_callback(content_length,
|
||||
16384,
|
||||
callback_reader_from_entry,
|
||||
new RunningResponse(m_entry, m_byteRange.first()),
|
||||
callback_free_response);
|
||||
MHD_add_response_header(response,
|
||||
MHD_HTTP_HEADER_CONTENT_TYPE, m_mimeType.c_str());
|
||||
MHD_add_response_header(response, MHD_HTTP_HEADER_ACCEPT_RANGES, "bytes");
|
||||
if ( m_byteRange.kind() == ByteRange::RESOLVED_PARTIAL_CONTENT ) {
|
||||
std::ostringstream oss;
|
||||
oss << "bytes " << m_byteRange.first() << "-" << m_byteRange.last()
|
||||
<< "/" << m_entry.getSize();
|
||||
|
||||
MHD_add_response_header(response,
|
||||
MHD_HTTP_HEADER_CONTENT_RANGE, oss.str().c_str());
|
||||
}
|
||||
|
||||
MHD_add_response_header(response,
|
||||
MHD_HTTP_HEADER_CONTENT_LENGTH, kiwix::to_string(content_length).c_str());
|
||||
return response;
|
||||
}
|
||||
|
||||
MHD_Response*
|
||||
Response::create_mhd_response(const RequestContext& request)
|
||||
{
|
||||
switch (m_mode) {
|
||||
case ResponseMode::ERROR_RESPONSE:
|
||||
return create_error_response(request);
|
||||
|
||||
case ResponseMode::RAW_CONTENT :
|
||||
return create_raw_content_mhd_response(request);
|
||||
|
||||
case ResponseMode::REDIRECTION :
|
||||
return create_redirection_mhd_response();
|
||||
|
||||
case ResponseMode::ENTRY :
|
||||
return create_entry_mhd_response();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MHD_Result Response::send(const RequestContext& request, MHD_Connection* connection)
|
||||
{
|
||||
MHD_Response* response = create_mhd_response(request);
|
||||
|
||||
if ( m_mode != ResponseMode::ERROR_RESPONSE ) {
|
||||
MHD_add_response_header(response, "Access-Control-Allow-Origin", "*");
|
||||
MHD_add_response_header(response, MHD_HTTP_HEADER_CACHE_CONTROL,
|
||||
m_etag.get_option(ETag::CACHEABLE_ENTITY) ? "max-age=2723040, public" : "no-cache, no-store, must-revalidate");
|
||||
const std::string etag = m_etag.get_etag();
|
||||
if ( ! etag.empty() )
|
||||
MHD_add_response_header(response, MHD_HTTP_HEADER_ETAG, etag.c_str());
|
||||
}
|
||||
|
||||
if (m_returnCode == MHD_HTTP_OK && m_byteRange.kind() == ByteRange::RESOLVED_PARTIAL_CONTENT)
|
||||
m_returnCode = MHD_HTTP_PARTIAL_CONTENT;
|
||||
|
||||
if (m_verbose)
|
||||
@@ -234,9 +327,27 @@ void Response::set_redirection(const std::string& url) {
|
||||
m_returnCode = MHD_HTTP_FOUND;
|
||||
}
|
||||
|
||||
void Response::set_entry(const Entry& entry) {
|
||||
void Response::set_entry(const Entry& entry, const RequestContext& request) {
|
||||
m_entry = entry;
|
||||
m_mode = ResponseMode::ENTRY;
|
||||
|
||||
const std::string mimeType = get_mime_type(entry);
|
||||
set_mimeType(mimeType);
|
||||
set_cacheable();
|
||||
|
||||
m_byteRange = request.get_range().resolve(entry.getSize());
|
||||
const bool noRange = m_byteRange.kind() == ByteRange::RESOLVED_FULL_CONTENT;
|
||||
if ( noRange && is_compressible_mime_type(mimeType) ) {
|
||||
zim::Blob raw_content = entry.getBlob();
|
||||
const std::string content = string(raw_content.data(), raw_content.size());
|
||||
|
||||
set_content(content);
|
||||
set_compress(true);
|
||||
} else if ( m_byteRange.kind() == ByteRange::RESOLVED_UNSATISFIABLE ) {
|
||||
set_code(416);
|
||||
set_content("");
|
||||
m_mode = ResponseMode::ERROR_RESPONSE;
|
||||
}
|
||||
}
|
||||
|
||||
void Response::set_taskbar(const std::string& bookName, const std::string& bookTitle)
|
||||
|
||||
@@ -24,15 +24,18 @@
|
||||
#include <string>
|
||||
|
||||
#include <mustache.hpp>
|
||||
#include "byte_range.h"
|
||||
#include "entry.h"
|
||||
#include "etag.h"
|
||||
|
||||
extern "C" {
|
||||
#include <microhttpd.h>
|
||||
#include "microhttpd_wrapper.h"
|
||||
}
|
||||
|
||||
namespace kiwix {
|
||||
|
||||
enum class ResponseMode {
|
||||
ERROR_RESPONSE,
|
||||
RAW_CONTENT,
|
||||
REDIRECTION,
|
||||
ENTRY
|
||||
@@ -42,30 +45,41 @@ class RequestContext;
|
||||
|
||||
class Response {
|
||||
public:
|
||||
Response(const std::string& root, bool verbose, bool withTaskbar, bool withLibraryButton);
|
||||
Response(const std::string& root, bool verbose, bool withTaskbar, bool withLibraryButton, bool blockExternalLinks);
|
||||
~Response() = default;
|
||||
|
||||
int send(const RequestContext& request, MHD_Connection* connection);
|
||||
MHD_Result send(const RequestContext& request, MHD_Connection* connection);
|
||||
|
||||
void set_template(const std::string& template_str, kainjow::mustache::data data);
|
||||
void set_content(const std::string& content);
|
||||
void set_redirection(const std::string& url);
|
||||
void set_entry(const Entry& entry);
|
||||
void set_entry(const Entry& entry, const RequestContext& request);
|
||||
|
||||
|
||||
void set_mimeType(const std::string& mimeType) { m_mimeType = mimeType; }
|
||||
void set_code(int code) { m_returnCode = code; }
|
||||
void set_cache(bool cache) { m_useCache = cache; }
|
||||
void set_cacheable() { m_etag.set_option(ETag::CACHEABLE_ENTITY); }
|
||||
void set_server_id(const std::string& id) { m_etag.set_server_id(id); }
|
||||
void set_etag(const ETag& etag) { m_etag = etag; }
|
||||
void set_compress(bool compress) { m_compress = compress; }
|
||||
void set_taskbar(const std::string& bookName, const std::string& bookTitle);
|
||||
void set_range_first(uint64_t start) { m_startRange = start; }
|
||||
void set_range_len(uint64_t len) { m_lenRange = len; }
|
||||
|
||||
int getReturnCode() { return m_returnCode; }
|
||||
int getReturnCode() const { return m_returnCode; }
|
||||
std::string get_mimeType() const { return m_mimeType; }
|
||||
|
||||
void introduce_taskbar();
|
||||
void inject_externallinks_blocker();
|
||||
|
||||
private:
|
||||
bool can_compress(const RequestContext& request) const;
|
||||
|
||||
private: // functions
|
||||
MHD_Response* create_mhd_response(const RequestContext& request);
|
||||
MHD_Response* create_error_response(const RequestContext& request) const;
|
||||
MHD_Response* create_raw_content_mhd_response(const RequestContext& request);
|
||||
MHD_Response* create_redirection_mhd_response() const;
|
||||
MHD_Response* create_entry_mhd_response() const;
|
||||
|
||||
private: // data
|
||||
bool m_verbose;
|
||||
ResponseMode m_mode;
|
||||
std::string m_root;
|
||||
@@ -75,13 +89,13 @@ class Response {
|
||||
int m_returnCode;
|
||||
bool m_withTaskbar;
|
||||
bool m_withLibraryButton;
|
||||
bool m_useCache;
|
||||
bool m_blockExternalLinks;
|
||||
bool m_compress;
|
||||
bool m_addTaskbar;
|
||||
std::string m_bookName;
|
||||
std::string m_bookTitle;
|
||||
uint64_t m_startRange;
|
||||
uint64_t m_lenRange;
|
||||
ByteRange m_byteRange;
|
||||
ETag m_etag;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <windows.h>
|
||||
#include <winbase.h>
|
||||
#include <shlwapi.h>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
@@ -34,11 +35,11 @@ DWORD WINAPI WinImpl::waitForPID(void* _self)
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::unique_ptr<wchar_t[]> toWideChar(const std::string& value)
|
||||
std::unique_ptr<wchar_t[]> toWideChar(const std::string& value, size_t min_size = 0)
|
||||
{
|
||||
auto size = MultiByteToWideChar(CP_UTF8, 0,
|
||||
size_t size = MultiByteToWideChar(CP_UTF8, 0,
|
||||
value.c_str(), -1, nullptr, 0);
|
||||
auto wdata = std::unique_ptr<wchar_t[]>(new wchar_t[size]);
|
||||
auto wdata = std::unique_ptr<wchar_t[]>(new wchar_t[size>min_size?size:min_size]);
|
||||
auto ret = MultiByteToWideChar(CP_UTF8, 0,
|
||||
value.c_str(), -1, wdata.get(), size);
|
||||
if (0 == ret) {
|
||||
@@ -46,6 +47,9 @@ std::unique_ptr<wchar_t[]> toWideChar(const std::string& value)
|
||||
oss << "Cannot convert to wchar : " << GetLastError();
|
||||
throw std::runtime_error(oss.str());
|
||||
}
|
||||
if (size < min_size) {
|
||||
memset(wdata.get() + size, 0, min_size-size);
|
||||
}
|
||||
return wdata;
|
||||
}
|
||||
|
||||
@@ -55,14 +59,16 @@ void WinImpl::run(commandLine_t& commandLine)
|
||||
STARTUPINFOW startInfo = {0};
|
||||
PROCESS_INFORMATION procInfo;
|
||||
startInfo.cb = sizeof(startInfo);
|
||||
std::ostringstream oss;
|
||||
std::wostringstream oss;
|
||||
for(auto& item: commandLine) {
|
||||
oss << item << " ";
|
||||
auto witem = toWideChar(item, MAX_PATH);
|
||||
PathQuoteSpacesW(witem.get());
|
||||
oss << witem.get() << " ";
|
||||
}
|
||||
auto wCommandLine = toWideChar(oss.str());
|
||||
auto wCommandLine = oss.str();
|
||||
if (CreateProcessW(
|
||||
NULL,
|
||||
wCommandLine.get(),
|
||||
const_cast<wchar_t*>(wCommandLine.c_str()),
|
||||
NULL,
|
||||
NULL,
|
||||
false,
|
||||
@@ -70,7 +76,8 @@ void WinImpl::run(commandLine_t& commandLine)
|
||||
NULL,
|
||||
NULL,
|
||||
&startInfo,
|
||||
&procInfo)) {
|
||||
&procInfo))
|
||||
{
|
||||
m_pid = procInfo.dwProcessId;
|
||||
m_handle = procInfo.hProcess;
|
||||
CloseHandle(procInfo.hThread);
|
||||
|
||||
@@ -25,7 +25,10 @@
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "tools/stringTools.h"
|
||||
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <pugixml.hpp>
|
||||
|
||||
|
||||
@@ -204,3 +207,76 @@ std::string kiwix::nodeToString(const pugi::xml_node& node)
|
||||
std::string kiwix::converta2toa3(const std::string& a2code){
|
||||
return codeisomapping.at(a2code);
|
||||
}
|
||||
|
||||
std::vector<std::string> kiwix::convertTags(const std::string& tags_str)
|
||||
{
|
||||
auto tags = kiwix::split(tags_str, ";");
|
||||
std::vector<std::string> tagsList;
|
||||
bool picSeen(false), vidSeen(false), detSeen(false), indexSeen(false);
|
||||
for (auto tag: tags) {
|
||||
picSeen |= (tag == "nopic" || startsWith(tag, "_pictures:"));
|
||||
vidSeen |= (tag == "novid" || startsWith(tag, "_videos:"));
|
||||
detSeen |= (tag == "nodet" || startsWith(tag, "_details:"));
|
||||
indexSeen |= kiwix::startsWith(tag, "_ftindex");
|
||||
if (tag == "nopic") {
|
||||
tagsList.push_back("_pictures:no");
|
||||
} else if (tag == "novid") {
|
||||
tagsList.push_back("_videos:no");
|
||||
} else if (tag == "nodet") {
|
||||
tagsList.push_back("_details:no");
|
||||
} else if (tag == "_ftindex") {
|
||||
tagsList.push_back("_ftindex:yes");
|
||||
} else {
|
||||
tagsList.push_back(tag);
|
||||
}
|
||||
}
|
||||
if (!indexSeen) {
|
||||
tagsList.push_back("_ftindex:no");
|
||||
}
|
||||
if (!picSeen) {
|
||||
tagsList.push_back("_pictures:yes");
|
||||
}
|
||||
if (!vidSeen) {
|
||||
tagsList.push_back("_videos:yes");
|
||||
}
|
||||
if (!detSeen) {
|
||||
tagsList.push_back("_details:yes");
|
||||
}
|
||||
return tagsList;
|
||||
}
|
||||
|
||||
std::string kiwix::getTagValueFromTagList(
|
||||
const std::vector<std::string>& tagList, const std::string& tagName)
|
||||
{
|
||||
for (auto tag: tagList) {
|
||||
if (tag[0] == '_') {
|
||||
auto delimPos = tag.find(':');
|
||||
if (delimPos == std::string::npos) {
|
||||
// No delimiter... what to do ?
|
||||
continue;
|
||||
}
|
||||
auto cTagName = tag.substr(1, delimPos-1);
|
||||
auto cTagValue = tag.substr(delimPos+1);
|
||||
if (cTagName == tagName) {
|
||||
return cTagValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::stringstream ss;
|
||||
ss << tagName << " cannot be found";
|
||||
throw std::out_of_range(ss.str());
|
||||
}
|
||||
|
||||
bool kiwix::convertStrToBool(const std::string& value)
|
||||
{
|
||||
if (value == "yes") {
|
||||
return true;
|
||||
} else if (value == "no") {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "Tag value '" << value << "' cannot be converted to bool.";
|
||||
throw std::domain_error(ss.str());
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
#include "tools/pathTools.h"
|
||||
#include <stdexcept>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <limits.h>
|
||||
@@ -42,12 +43,16 @@
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define SEPARATOR "\\"
|
||||
# define SEPARATOR "\\"
|
||||
# include <io.h>
|
||||
#else
|
||||
#define SEPARATOR "/"
|
||||
#include <unistd.h>
|
||||
# define SEPARATOR "/"
|
||||
# include <unistd.h>
|
||||
# include <sys/stat.h>
|
||||
#endif
|
||||
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifndef PATH_MAX
|
||||
@@ -76,13 +81,19 @@ std::wstring Utf8ToWide(const std::string& str)
|
||||
bool isRelativePath(const std::string& path)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return path.empty() || path.substr(1, 2) == ":\\" ? false : true;
|
||||
if (path.size() < 3 ) {
|
||||
return true;
|
||||
}
|
||||
if (path.substr(1, 2) == ":\\" || path.substr(0, 2) == "\\\\") {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
return path.empty() || path.substr(0, 1) == "/" ? false : true;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<std::string> normalizeParts(std::vector<std::string> parts, bool absolute)
|
||||
std::vector<std::string> normalizeParts(std::vector<std::string>& parts, bool absolute)
|
||||
{
|
||||
std::vector<std::string> ret;
|
||||
#ifdef _WIN32
|
||||
@@ -90,10 +101,35 @@ std::vector<std::string> normalizeParts(std::vector<std::string> parts, bool abs
|
||||
//Starts from there.
|
||||
auto it = find_if(parts.rbegin(), parts.rend(),
|
||||
[](const std::string& p) ->bool
|
||||
{ return p.length() == 2 && p[1] == ':'; });
|
||||
{ return ((p.length() == 2 && p[1] == ':')
|
||||
|| (p.length() > 2 && p[0] == '\\' && p[1] == '\\')); });
|
||||
if (it != parts.rend()) {
|
||||
parts.erase(parts.begin(), it.base()-1);
|
||||
}
|
||||
// Special case for samba mount point starting with two "\\" ("\\\\samba\\foo")
|
||||
if (parts.size() > 2 && parts[0].empty() && parts[1].empty()) {
|
||||
parts.erase(parts.begin(), parts.begin()+2);
|
||||
parts[0] = "\\\\" + parts[0];
|
||||
}
|
||||
// Special case if we have a samba drive not at first.
|
||||
// Path is "..\\\\sambdadrive\\..\\.." So we will have an empty part.
|
||||
auto previous_empty = false;
|
||||
for (it = parts.rbegin(); it!=parts.rend(); it++) {
|
||||
if(it->empty()) {
|
||||
if (previous_empty) {
|
||||
it++;
|
||||
break;
|
||||
} else {
|
||||
previous_empty = true;
|
||||
}
|
||||
} else {
|
||||
previous_empty = false;
|
||||
}
|
||||
}
|
||||
if (it != parts.rend()) {
|
||||
parts.erase(parts.begin(), it.base()-1);
|
||||
parts[0] = "\\\\" + parts[0];
|
||||
}
|
||||
#endif
|
||||
|
||||
size_t index = 0;
|
||||
@@ -139,8 +175,10 @@ std::vector<std::string> normalizeParts(std::vector<std::string> parts, bool abs
|
||||
|
||||
std::string computeRelativePath(const std::string& path, const std::string& absolutePath)
|
||||
{
|
||||
auto pathParts = normalizeParts(kiwix::split(path, SEPARATOR, false), false);
|
||||
auto absolutePathParts = kiwix::split(absolutePath, SEPARATOR, false);
|
||||
auto parts = kiwix::split(path, SEPARATOR, false);
|
||||
auto pathParts = normalizeParts(parts, false);
|
||||
parts = kiwix::split(absolutePath, SEPARATOR, false);
|
||||
auto absolutePathParts = normalizeParts(parts, true);
|
||||
|
||||
unsigned int commonCount = 0;
|
||||
while (commonCount < pathParts.size()
|
||||
@@ -167,8 +205,10 @@ std::string computeAbsolutePath(const std::string& path, const std::string& rela
|
||||
absolutePath = getCurrentDirectory();
|
||||
}
|
||||
|
||||
auto absoluteParts = normalizeParts(kiwix::split(absolutePath, SEPARATOR, false), true);
|
||||
auto relativeParts = kiwix::split(relativePath, SEPARATOR, false);
|
||||
auto parts = kiwix::split(absolutePath, SEPARATOR, false);
|
||||
auto absoluteParts = normalizeParts(parts, true);
|
||||
parts = kiwix::split(relativePath, SEPARATOR, false);
|
||||
auto relativeParts = normalizeParts(parts, false);
|
||||
|
||||
absoluteParts.insert(absoluteParts.end(), relativeParts.begin(), relativeParts.end());
|
||||
auto ret = kiwix::join(normalizeParts(absoluteParts, true), SEPARATOR);
|
||||
@@ -177,7 +217,8 @@ std::string computeAbsolutePath(const std::string& path, const std::string& rela
|
||||
|
||||
std::string removeLastPathElement(const std::string& path)
|
||||
{
|
||||
auto parts = normalizeParts(kiwix::split(path, SEPARATOR, false), false);
|
||||
auto parts_ = kiwix::split(path, SEPARATOR, false);
|
||||
auto parts = normalizeParts(parts_, false);
|
||||
if (!parts.empty()) {
|
||||
parts.pop_back();
|
||||
}
|
||||
@@ -197,7 +238,8 @@ std::string appendToDirectory(const std::string& directoryPath, const std::strin
|
||||
|
||||
std::string getLastPathElement(const std::string& path)
|
||||
{
|
||||
auto parts = normalizeParts(kiwix::split(path, SEPARATOR), false);
|
||||
auto parts_ = kiwix::split(path, SEPARATOR);
|
||||
auto parts = normalizeParts(parts_, false);
|
||||
if (parts.empty()) {
|
||||
return "";
|
||||
}
|
||||
@@ -227,14 +269,33 @@ std::string getFileSizeAsString(const std::string& path)
|
||||
|
||||
std::string getFileContent(const std::string& path)
|
||||
{
|
||||
std::ifstream f(path, std::ios::in|std::ios::ate);
|
||||
#ifdef _WIN32
|
||||
auto wpath = Utf8ToWide(path);
|
||||
auto fd = _wopen(wpath.c_str(), _O_RDONLY | _O_BINARY);
|
||||
#else
|
||||
auto fd = open(path.c_str(), O_RDONLY);
|
||||
#endif
|
||||
std::string content;
|
||||
if (f.is_open()) {
|
||||
auto size = f.tellg();
|
||||
content.reserve(size);
|
||||
f.seekg(0, std::ios::beg);
|
||||
content.assign((std::istreambuf_iterator<char>(f)),
|
||||
std::istreambuf_iterator<char>());
|
||||
if (fd != -1) {
|
||||
#ifdef _WIN32
|
||||
auto size = _lseeki64(fd, 0, SEEK_END);
|
||||
#else
|
||||
auto size = lseek(fd, 0, SEEK_END);
|
||||
#endif
|
||||
content.resize(size);
|
||||
#ifdef _WIN32
|
||||
_lseeki64(fd, 0, SEEK_SET);
|
||||
#else
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
#endif
|
||||
auto p = (char*)content.data();
|
||||
while (size) {
|
||||
auto readsize = size > 2048 ? 2048 : size;
|
||||
readsize = ::read(fd, p, readsize);
|
||||
p += readsize;
|
||||
size -= readsize;
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
@@ -287,22 +348,22 @@ std::string makeTmpDirectory()
|
||||
/* Try to create a link and if does not work then make a copy */
|
||||
bool copyFile(const std::string& sourcePath, const std::string& destPath)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return CopyFileW(Utf8ToWide(sourcePath).c_str(), Utf8ToWide(destPath).c_str(), 1);
|
||||
#else
|
||||
try {
|
||||
#ifndef _WIN32
|
||||
if (link(sourcePath.c_str(), destPath.c_str()) != 0) {
|
||||
#endif
|
||||
std::ifstream infile(sourcePath.c_str(), std::ios_base::binary);
|
||||
std::ofstream outfile(destPath.c_str(), std::ios_base::binary);
|
||||
outfile << infile.rdbuf();
|
||||
#ifndef _WIN32
|
||||
}
|
||||
#endif
|
||||
} catch (std::exception& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string getExecutablePath(bool realPathOnly)
|
||||
@@ -341,10 +402,21 @@ std::string getExecutablePath(bool realPathOnly)
|
||||
|
||||
bool writeTextFile(const std::string& path, const std::string& content)
|
||||
{
|
||||
std::ofstream file;
|
||||
file.open(path.c_str());
|
||||
file << content;
|
||||
file.close();
|
||||
#ifdef _WIN32
|
||||
auto wpath = Utf8ToWide(path);
|
||||
auto fd = _wopen(wpath.c_str(), _O_WRONLY | _O_CREAT | _O_TRUNC, S_IWRITE);
|
||||
#else
|
||||
auto fd = open(path.c_str(), O_WRONLY | O_CREAT | O_TRUNC,
|
||||
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
||||
#endif
|
||||
if (fd == -1)
|
||||
return false;
|
||||
|
||||
if (write(fd, content.c_str(), content.size()) != (long)content.size()) {
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
close(fd);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -364,30 +436,48 @@ std::string getCurrentDirectory()
|
||||
|
||||
std::string getDataDirectory()
|
||||
{
|
||||
// Try to get the dataDir from the `KIWIX_DATA_DIR` env var
|
||||
#ifdef _WIN32
|
||||
char* cDataDir = ::getenv("APPDATA");
|
||||
wchar_t* cDataDir = ::_wgetenv(L"KIWIX_DATA_DIR");
|
||||
if (cDataDir != nullptr) {
|
||||
return WideToUtf8(cDataDir);
|
||||
}
|
||||
#else
|
||||
char* cDataDir = ::getenv("KIWIX_DATA_DIR");
|
||||
#endif
|
||||
std::string dataDir = cDataDir==nullptr ? "" : cDataDir;
|
||||
if (!dataDir.empty()) {
|
||||
return dataDir;
|
||||
if (cDataDir != nullptr) {
|
||||
return cDataDir;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Compute the dataDir from the user directory.
|
||||
std::string dataDir;
|
||||
#ifdef _WIN32
|
||||
cDataDir = ::getenv("USERPROFILE");
|
||||
dataDir = cDataDir==nullptr ? getCurrentDirectory() : cDataDir;
|
||||
cDataDir = ::_wgetenv(L"APPDATA");
|
||||
if (cDataDir == nullptr)
|
||||
cDataDir = ::_wgetenv(L"USERPROFILE");
|
||||
if (cDataDir != nullptr)
|
||||
dataDir = WideToUtf8(cDataDir);
|
||||
#else
|
||||
cDataDir = ::getenv("XDG_DATA_HOME");
|
||||
dataDir = cDataDir==nullptr ? "" : cDataDir;
|
||||
if (dataDir.empty()) {
|
||||
if (cDataDir != nullptr) {
|
||||
dataDir = cDataDir;
|
||||
} else {
|
||||
cDataDir = ::getenv("HOME");
|
||||
dataDir = cDataDir==nullptr ? getCurrentDirectory() : cDataDir;
|
||||
dataDir = appendToDirectory(dataDir, ".local");
|
||||
dataDir = appendToDirectory(dataDir, "share");
|
||||
if (cDataDir != nullptr) {
|
||||
dataDir = cDataDir;
|
||||
dataDir = appendToDirectory(dataDir, ".local");
|
||||
dataDir = appendToDirectory(dataDir, "share");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
auto ret = appendToDirectory(dataDir, "kiwix");
|
||||
return ret;
|
||||
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 = {
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
#include <pthread.h>
|
||||
|
||||
std::map<std::string, std::shared_ptr<icu::RegexPattern>> regexCache;
|
||||
|
||||
87
src/wrapper/java/book.cpp
Normal file
87
src/wrapper/java/book.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Matthieu Gautier <mgautier@kymeria.fr>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
|
||||
#include <jni.h>
|
||||
#include "org_kiwix_kiwixlib_Book.h"
|
||||
|
||||
#include "utils.h"
|
||||
#include "book.h"
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_kiwix_kiwixlib_Book_allocate(
|
||||
JNIEnv* env, jobject thisObj)
|
||||
{
|
||||
allocate<kiwix::Book>(env, thisObj);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_kiwix_kiwixlib_Book_dispose(JNIEnv* env, jobject thisObj)
|
||||
{
|
||||
dispose<kiwix::Book>(env, thisObj);
|
||||
}
|
||||
|
||||
#define BOOK (getPtr<kiwix::Book>(env, thisObj))
|
||||
|
||||
METHOD(void, Book, update__Lorg_kiwix_kiwixlib_Book_2, jobject otherBook)
|
||||
{
|
||||
BOOK->update(*getPtr<kiwix::Book>(env, otherBook));
|
||||
}
|
||||
|
||||
METHOD(void, Book, update__Lorg_kiwix_kiwixlib_JNIKiwixReader_2, jobject reader)
|
||||
{
|
||||
BOOK->update(**Handle<kiwix::Reader>::getHandle(env, reader));
|
||||
}
|
||||
|
||||
#define GETTER(retType, name) JNIEXPORT retType JNICALL \
|
||||
Java_org_kiwix_kiwixlib_Book_##name (JNIEnv* env, jobject thisObj) \
|
||||
{ \
|
||||
auto cRet = BOOK->name(); \
|
||||
retType ret = c2jni(cRet, env); \
|
||||
return ret; \
|
||||
}
|
||||
|
||||
GETTER(jstring, getId)
|
||||
GETTER(jstring, getPath)
|
||||
GETTER(jboolean, isPathValid)
|
||||
GETTER(jstring, getTitle)
|
||||
GETTER(jstring, getDescription)
|
||||
GETTER(jstring, getLanguage)
|
||||
GETTER(jstring, getCreator)
|
||||
GETTER(jstring, getPublisher)
|
||||
GETTER(jstring, getDate)
|
||||
GETTER(jstring, getUrl)
|
||||
GETTER(jstring, getName)
|
||||
GETTER(jstring, getFlavour)
|
||||
GETTER(jstring, getTags)
|
||||
GETTER(jlong, getArticleCount)
|
||||
GETTER(jlong, getMediaCount)
|
||||
GETTER(jlong, getSize)
|
||||
GETTER(jstring, getFavicon)
|
||||
GETTER(jstring, getFaviconUrl)
|
||||
GETTER(jstring, getFaviconMimeType)
|
||||
|
||||
METHOD(jstring, Book, getTagStr, jstring tagName) try {
|
||||
auto cRet = BOOK->getTagStr(jni2c(tagName, env));
|
||||
return c2jni(cRet, env);
|
||||
} catch(...) {
|
||||
return c2jni<std::string>("", env);
|
||||
}
|
||||
|
||||
#undef GETTER
|
||||
63
src/wrapper/java/filter.cpp
Normal file
63
src/wrapper/java/filter.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2019-2020 Matthieu Gautier <mgautier@kymeria.fr>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
|
||||
#include <jni.h>
|
||||
#include "org_kiwix_kiwixlib_Filter.h"
|
||||
|
||||
#include "library.h"
|
||||
#include "utils.h"
|
||||
|
||||
/* Kiwix Reader JNI functions */
|
||||
METHOD0(void, Filter, allocate) {
|
||||
allocate<kiwix::Filter>(env, thisObj);
|
||||
}
|
||||
|
||||
METHOD0(void, Filter, dispose) {
|
||||
dispose<kiwix::Library>(env, thisObj);
|
||||
}
|
||||
|
||||
#define FILTER (getPtr<kiwix::Filter>(env, thisObj))
|
||||
|
||||
#define FORWARD(name, args_type) \
|
||||
METHOD(jobject, Filter, name, args_type value) { \
|
||||
FILTER->name(jni2c(value, env)); \
|
||||
return thisObj; \
|
||||
}
|
||||
|
||||
#define FORWARDA(name, args_type) \
|
||||
METHOD(jobject, Filter, name, jobjectArray value) { \
|
||||
FILTER->name(jni2c<args_type>(value, env)); \
|
||||
return thisObj; \
|
||||
}
|
||||
|
||||
|
||||
|
||||
FORWARD(local, jboolean)
|
||||
FORWARD(remote, jboolean)
|
||||
FORWARD(valid, jboolean)
|
||||
FORWARDA(acceptTags, jstring)
|
||||
FORWARDA(rejectTags, jstring)
|
||||
FORWARD(lang, jstring)
|
||||
FORWARD(publisher, jstring)
|
||||
FORWARD(creator, jstring)
|
||||
FORWARD(maxSize, jlong)
|
||||
FORWARD(query, jstring)
|
||||
|
||||
|
||||
@@ -28,7 +28,11 @@
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
#if __ANDROID__
|
||||
pthread_mutex_t globalLock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER;
|
||||
#else
|
||||
pthread_mutex_t globalLock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
|
||||
#endif
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_kiwix_kiwixlib_JNIICU_setDataDirectory(
|
||||
JNIEnv* env, jclass kclass, jstring dirStr)
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
#include <jni.h>
|
||||
#include <zim/file.h>
|
||||
#include <android/log.h>
|
||||
#include <exception>
|
||||
#include "org_kiwix_kiwixlib_JNIKiwixReader.h"
|
||||
|
||||
#include "tools/base64.h"
|
||||
@@ -34,14 +34,14 @@ JNIEXPORT jlong JNICALL Java_org_kiwix_kiwixlib_JNIKiwixReader_getNativeReader(
|
||||
{
|
||||
std::string cPath = jni2c(filename, env);
|
||||
|
||||
__android_log_print(ANDROID_LOG_INFO, "kiwix", "Attempting to create reader with: %s", cPath.c_str());
|
||||
LOG("Attempting to create reader with: %s", cPath.c_str());
|
||||
Lock l;
|
||||
try {
|
||||
kiwix::Reader* reader = new kiwix::Reader(cPath);
|
||||
return reinterpret_cast<jlong>(new Handle<kiwix::Reader>(reader));
|
||||
} catch (std::exception& e) {
|
||||
__android_log_print(ANDROID_LOG_WARN, "kiwix", "Error opening ZIM file");
|
||||
__android_log_print(ANDROID_LOG_WARN, "kiwix", e.what());
|
||||
LOG("Error opening ZIM file");
|
||||
LOG(e.what());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -64,8 +64,8 @@ Java_org_kiwix_kiwixlib_JNIKiwixReader_getMainPage(JNIEnv* env, jobject obj)
|
||||
std::string cUrl = READER->getMainPage().getPath();
|
||||
url = c2jni(cUrl, env);
|
||||
} catch (std::exception& e) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", "Unable to get ZIM main page");
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", e.what());
|
||||
LOG("Unable to get ZIM main page");
|
||||
LOG(e.what());
|
||||
url = NULL;
|
||||
}
|
||||
return url;
|
||||
@@ -80,8 +80,8 @@ Java_org_kiwix_kiwixlib_JNIKiwixReader_getId(JNIEnv* env, jobject obj)
|
||||
std::string cId = READER->getId();
|
||||
id = c2jni(cId, env);
|
||||
} catch (std::exception& e) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", "Unable to get ZIM id");
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", e.what());
|
||||
LOG("Unable to get ZIM id");
|
||||
LOG(e.what());
|
||||
id = NULL;
|
||||
}
|
||||
|
||||
@@ -95,10 +95,10 @@ Java_org_kiwix_kiwixlib_JNIKiwixReader_getFileSize(JNIEnv* env, jobject obj)
|
||||
|
||||
try {
|
||||
int cSize = READER->getFileSize();
|
||||
size = c2jni(cSize);
|
||||
size = c2jni(cSize, env);
|
||||
} catch (std::exception& e) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", "Unable to get ZIM file size");
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", e.what());
|
||||
LOG("Unable to get ZIM file size");
|
||||
LOG(e.what());
|
||||
}
|
||||
|
||||
return size;
|
||||
@@ -113,8 +113,8 @@ Java_org_kiwix_kiwixlib_JNIKiwixReader_getCreator(JNIEnv* env, jobject obj)
|
||||
std::string cCreator = READER->getCreator();
|
||||
creator = c2jni(cCreator, env);
|
||||
} catch (std::exception& e) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", "Unable to get ZIM creator");
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", e.what());
|
||||
LOG("Unable to get ZIM creator");
|
||||
LOG(e.what());
|
||||
creator = NULL;
|
||||
}
|
||||
|
||||
@@ -130,8 +130,8 @@ Java_org_kiwix_kiwixlib_JNIKiwixReader_getPublisher(JNIEnv* env, jobject obj)
|
||||
std::string cPublisher = READER->getPublisher();
|
||||
publisher = c2jni(cPublisher, env);
|
||||
} catch (std::exception& e) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", "Unable to get ZIM publish");
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", e.what());
|
||||
LOG("Unable to get ZIM publish");
|
||||
LOG(e.what());
|
||||
publisher = NULL;
|
||||
}
|
||||
return publisher;
|
||||
@@ -146,8 +146,8 @@ Java_org_kiwix_kiwixlib_JNIKiwixReader_getName(JNIEnv* env, jobject obj)
|
||||
std::string cName = READER->getName();
|
||||
name = c2jni(cName, env);
|
||||
} catch (std::exception& e) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", "Unable to get ZIM name");
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", e.what());
|
||||
LOG("Unable to get ZIM name");
|
||||
LOG(e.what());
|
||||
name = NULL;
|
||||
}
|
||||
return name;
|
||||
@@ -166,8 +166,8 @@ Java_org_kiwix_kiwixlib_JNIKiwixReader_getFavicon(JNIEnv* env, jobject obj)
|
||||
base64_encode(cContent),
|
||||
env);
|
||||
} catch (std::exception& e) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", "Unable to get ZIM favicon");
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", e.what());
|
||||
LOG("Unable to get ZIM favicon");
|
||||
LOG(e.what());
|
||||
favicon = NULL;
|
||||
}
|
||||
return favicon;
|
||||
@@ -182,8 +182,8 @@ Java_org_kiwix_kiwixlib_JNIKiwixReader_getDate(JNIEnv* env, jobject obj)
|
||||
std::string cDate = READER->getDate();
|
||||
date = c2jni(cDate, env);
|
||||
} catch (std::exception& e) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", "Unable to get ZIM date");
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", e.what());
|
||||
LOG("Unable to get ZIM date");
|
||||
LOG(e.what());
|
||||
date = NULL;
|
||||
}
|
||||
return date;
|
||||
@@ -198,8 +198,8 @@ Java_org_kiwix_kiwixlib_JNIKiwixReader_getLanguage(JNIEnv* env, jobject obj)
|
||||
std::string cLanguage = READER->getLanguage();
|
||||
language = c2jni(cLanguage, env);
|
||||
} catch (std::exception& e) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", "Unable to get ZIM language");
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", e.what());
|
||||
LOG("Unable to get ZIM language");
|
||||
LOG(e.what());
|
||||
language = NULL;
|
||||
}
|
||||
|
||||
@@ -217,8 +217,8 @@ JNIEXPORT jstring JNICALL Java_org_kiwix_kiwixlib_JNIKiwixReader_getMimeType(
|
||||
auto cMimeType = entry.getMimetype();
|
||||
mimeType = c2jni(cMimeType, env);
|
||||
} catch (std::exception& e) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", "Unable to get mime-type for url: %s", cUrl.c_str());
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", e.what());
|
||||
LOG("Unable to get mime-type for url: %s", cUrl.c_str());
|
||||
LOG(e.what());
|
||||
mimeType = NULL;
|
||||
}
|
||||
return mimeType;
|
||||
@@ -234,7 +234,7 @@ JNIEXPORT jstring JNICALL Java_org_kiwix_kiwixlib_JNIKiwixReader_checkUrl(
|
||||
entry = entry.getFinalEntry();
|
||||
finalUrl = c2jni(entry.getPath(), env);
|
||||
} catch (std::exception& e) {
|
||||
finalUrl = c2jni("", env);
|
||||
finalUrl = c2jni(std::string(), env);
|
||||
}
|
||||
return finalUrl;
|
||||
}
|
||||
@@ -268,8 +268,8 @@ JNIEXPORT jbyteArray JNICALL Java_org_kiwix_kiwixlib_JNIKiwixReader_getContent(
|
||||
data, 0, cSize, reinterpret_cast<const jbyte*>(entry.getBlob().data()));
|
||||
}
|
||||
} catch (std::exception& e) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", "Unable to get content for url: %s", cUrl.c_str());
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", e.what());
|
||||
LOG("Unable to get content for url: %s", cUrl.c_str());
|
||||
LOG(e.what());
|
||||
}
|
||||
|
||||
return data;
|
||||
@@ -284,8 +284,8 @@ JNIEXPORT jbyteArray JNICALL Java_org_kiwix_kiwixlib_JNIKiwixReader_getContentPa
|
||||
/* Default values */
|
||||
/* Retrieve the content */
|
||||
std::string cUrl = jni2c(url, env);
|
||||
unsigned int cOffset = jni2c(offset);
|
||||
unsigned int cLen = jni2c(len);
|
||||
unsigned int cOffset = jni2c(offset, env);
|
||||
unsigned int cLen = jni2c(len, env);
|
||||
try {
|
||||
auto entry = READER->getEntryFromEncodedPath(cUrl);
|
||||
entry = entry.getFinalEntry();
|
||||
@@ -300,12 +300,28 @@ JNIEXPORT jbyteArray JNICALL Java_org_kiwix_kiwixlib_JNIKiwixReader_getContentPa
|
||||
setIntObjValue(cLen, sizeObj, env);
|
||||
}
|
||||
} catch (std::exception& e) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", "Unable to get partial content for url: %s (%u : %u)", cUrl.c_str(), cOffset, cLen);
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", e.what());
|
||||
LOG("Unable to get partial content for url: %s (%u : %u)", cUrl.c_str(), cOffset, cLen);
|
||||
LOG(e.what());
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixReader_getArticleSize(
|
||||
JNIEnv* env, jobject obj, jstring url)
|
||||
{
|
||||
std::string cUrl = jni2c(url, env);
|
||||
try {
|
||||
auto entry = READER->getEntryFromEncodedPath(cUrl);
|
||||
entry = entry.getFinalEntry();
|
||||
return c2jni(entry.getSize(), env);
|
||||
} catch(std::exception& e) {
|
||||
LOG("Unable to get size for url : %s", cUrl.c_str());
|
||||
LOG(e.what());
|
||||
}
|
||||
return c2jni(0, env);
|
||||
}
|
||||
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixReader_getDirectAccessInformation(
|
||||
JNIEnv* env, jobject obj, jstring url)
|
||||
@@ -322,8 +338,8 @@ Java_org_kiwix_kiwixlib_JNIKiwixReader_getDirectAccessInformation(
|
||||
auto part_info = entry.getDirectAccessInfo();
|
||||
setPairObjValue(part_info.first, part_info.second, pair, env);
|
||||
} catch (std::exception& e) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", "Unable to get direct access info for url: %s", cUrl.c_str());
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", e.what());
|
||||
LOG("Unable to get direct access info for url: %s", cUrl.c_str());
|
||||
LOG(e.what());
|
||||
}
|
||||
return pair;
|
||||
}
|
||||
@@ -336,15 +352,18 @@ Java_org_kiwix_kiwixlib_JNIKiwixReader_searchSuggestions(JNIEnv* env,
|
||||
{
|
||||
jboolean retVal = JNI_FALSE;
|
||||
std::string cPrefix = jni2c(prefix, env);
|
||||
unsigned int cCount = jni2c(count);
|
||||
unsigned int cCount = jni2c(count, env);
|
||||
|
||||
try {
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
if (READER->searchSuggestionsSmart(cPrefix, cCount)) {
|
||||
retVal = JNI_TRUE;
|
||||
}
|
||||
#pragma GCC diagnostic pop
|
||||
} catch (std::exception& e) {
|
||||
__android_log_print(ANDROID_LOG_WARN, "kiwix", "Unable to get search results for pattern: %s", cPrefix.c_str());
|
||||
__android_log_print(ANDROID_LOG_WARN, "kiwix", e.what());
|
||||
LOG("Unable to get search results for pattern: %s", cPrefix.c_str());
|
||||
LOG(e.what());
|
||||
}
|
||||
|
||||
return retVal;
|
||||
@@ -361,14 +380,17 @@ Java_org_kiwix_kiwixlib_JNIKiwixReader_getNextSuggestion(JNIEnv* env,
|
||||
std::string cUrl;
|
||||
|
||||
try {
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
if (READER->getNextSuggestion(cTitle, cUrl)) {
|
||||
setStringObjValue(cTitle, titleObj, env);
|
||||
setStringObjValue(cUrl, urlObj, env);
|
||||
retVal = JNI_TRUE;
|
||||
}
|
||||
#pragma GCC diagnostic pop
|
||||
} catch (std::exception& e) {
|
||||
__android_log_print(ANDROID_LOG_WARN, "kiwix", "Unable to get next suggestion");
|
||||
__android_log_print(ANDROID_LOG_WARN, "kiwix", e.what());
|
||||
LOG("Unable to get next suggestion");
|
||||
LOG(e.what());
|
||||
}
|
||||
|
||||
return retVal;
|
||||
@@ -388,8 +410,8 @@ Java_org_kiwix_kiwixlib_JNIKiwixReader_getPageUrlFromTitle(JNIEnv* env,
|
||||
setStringObjValue(entry.getPath(), urlObj, env);
|
||||
return JNI_TRUE;
|
||||
} catch (std::exception& e) {
|
||||
__android_log_print(ANDROID_LOG_WARN, "kiwix", "Unable to get url for title %s: ", cTitle.c_str());
|
||||
__android_log_print(ANDROID_LOG_WARN, "kiwix", e.what());
|
||||
LOG("Unable to get url for title %s: ", cTitle.c_str());
|
||||
LOG(e.what());
|
||||
}
|
||||
|
||||
return JNI_FALSE;
|
||||
@@ -404,8 +426,8 @@ JNIEXPORT jstring JNICALL Java_org_kiwix_kiwixlib_JNIKiwixReader_getTitle(
|
||||
std::string cTitle = READER->getTitle();
|
||||
title = c2jni(cTitle, env);
|
||||
} catch (std::exception& e) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", "Unable to get zim title");
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", e.what());
|
||||
LOG("Unable to get zim title");
|
||||
LOG(e.what());
|
||||
title = NULL;
|
||||
}
|
||||
return title;
|
||||
@@ -420,8 +442,8 @@ Java_org_kiwix_kiwixlib_JNIKiwixReader_getDescription(JNIEnv* env, jobject obj)
|
||||
std::string cDescription = READER->getDescription();
|
||||
description = c2jni(cDescription, env);
|
||||
} catch (std::exception& e) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", "Unable to get zim description");
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", e.what());
|
||||
LOG("Unable to get zim description");
|
||||
LOG(e.what());
|
||||
description = NULL;
|
||||
}
|
||||
return description;
|
||||
@@ -433,10 +455,10 @@ Java_org_kiwix_kiwixlib_JNIKiwixReader_getArticleCount(JNIEnv* env, jobject obj)
|
||||
jint articleCount = 0;
|
||||
try {
|
||||
auto cArticleCount = READER->getArticleCount();
|
||||
articleCount = c2jni(cArticleCount);
|
||||
articleCount = c2jni(cArticleCount, env);
|
||||
} catch (std::exception& e) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", "Unable to get article count.");
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", e.what());
|
||||
LOG("Unable to get article count.");
|
||||
LOG(e.what());
|
||||
}
|
||||
return articleCount;
|
||||
}
|
||||
@@ -447,10 +469,10 @@ Java_org_kiwix_kiwixlib_JNIKiwixReader_getMediaCount(JNIEnv* env, jobject obj)
|
||||
jint mediaCount = 0;
|
||||
try {
|
||||
auto cMediaCount = READER->getMediaCount();
|
||||
mediaCount = c2jni(cMediaCount);
|
||||
mediaCount = c2jni(cMediaCount, env);
|
||||
} catch (std::exception& e) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", "Unable to get media count.");
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", e.what());
|
||||
LOG("Unable to get media count.");
|
||||
LOG(e.what());
|
||||
}
|
||||
return mediaCount;
|
||||
}
|
||||
@@ -467,8 +489,8 @@ JNIEXPORT jboolean JNICALL Java_org_kiwix_kiwixlib_JNIKiwixReader_getRandomPage(
|
||||
setStringObjValue(cUrl, urlObj, env);
|
||||
retVal = JNI_TRUE;
|
||||
} catch (std::exception& e) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", "Unable to get random page");
|
||||
__android_log_print(ANDROID_LOG_ERROR, "kiwix", e.what());
|
||||
LOG("Unable to get random page");
|
||||
LOG(e.what());
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
@@ -59,7 +59,7 @@ JNIEXPORT void JNICALL Java_org_kiwix_kiwixlib_JNIKiwixSearcher_search(
|
||||
JNIEnv* env, jobject obj, jstring query, jint count)
|
||||
{
|
||||
std::string cquery = jni2c(query, env);
|
||||
unsigned int ccount = jni2c(count);
|
||||
unsigned int ccount = jni2c(count, env);
|
||||
|
||||
SEARCHER->search(cquery, 0, ccount);
|
||||
}
|
||||
@@ -21,7 +21,6 @@
|
||||
|
||||
#include <jni.h>
|
||||
#include <zim/file.h>
|
||||
#include <android/log.h>
|
||||
#include "org_kiwix_kiwixlib_JNIKiwixServer.h"
|
||||
|
||||
#include "tools/base64.h"
|
||||
@@ -32,15 +31,15 @@
|
||||
JNIEXPORT jlong JNICALL Java_org_kiwix_kiwixlib_JNIKiwixServer_getNativeServer(
|
||||
JNIEnv* env, jobject obj, jobject jLibrary)
|
||||
{
|
||||
__android_log_print(ANDROID_LOG_INFO, "kiwix", "Attempting to create server");
|
||||
LOG("Attempting to create server");
|
||||
Lock l;
|
||||
try {
|
||||
auto library = Handle<kiwix::Library>::getHandle(env, jLibrary);
|
||||
kiwix::Server* server = new kiwix::Server(*library);
|
||||
auto library = getPtr<kiwix::Library>(env, jLibrary);
|
||||
kiwix::Server* server = new kiwix::Server(library);
|
||||
return reinterpret_cast<jlong>(new Handle<kiwix::Server>(server));
|
||||
} catch (std::exception& e) {
|
||||
__android_log_print(ANDROID_LOG_WARN, "kiwix", "Error creating the server");
|
||||
__android_log_print(ANDROID_LOG_WARN, "kiwix", e.what());
|
||||
LOG("Error creating the server");
|
||||
LOG(e.what());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -86,6 +85,12 @@ Java_org_kiwix_kiwixlib_JNIKiwixServer_setTaskbar(JNIEnv* env, jobject obj, jboo
|
||||
SERVER->setTaskbar(withTaskbar, withLibraryButton);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixServer_setBlockExternalLinks(JNIEnv* env, jobject obj, jboolean blockExternalLinks)
|
||||
{
|
||||
SERVER->setBlockExternalLinks(blockExternalLinks);
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixServer_start(JNIEnv* env, jobject obj)
|
||||
{
|
||||
96
src/wrapper/java/library.cpp
Normal file
96
src/wrapper/java/library.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (C) 2019-2020 Matthieu Gautier <mgautier@kymeria.fr>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
|
||||
#include <jni.h>
|
||||
#include "org_kiwix_kiwixlib_Library.h"
|
||||
|
||||
#include "library.h"
|
||||
#include "reader.h"
|
||||
#include "utils.h"
|
||||
|
||||
/* Kiwix Reader JNI functions */
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_kiwix_kiwixlib_Library_allocate(
|
||||
JNIEnv* env, jobject thisObj)
|
||||
{
|
||||
allocate<kiwix::Library>(env, thisObj);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_kiwix_kiwixlib_Library_dispose(JNIEnv* env, jobject thisObj)
|
||||
{
|
||||
dispose<kiwix::Library>(env, thisObj);
|
||||
}
|
||||
|
||||
#define LIBRARY (getPtr<kiwix::Library>(env, thisObj))
|
||||
|
||||
/* Kiwix library functions */
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_org_kiwix_kiwixlib_Library_addBook(
|
||||
JNIEnv* env, jobject thisObj, jstring path)
|
||||
{
|
||||
auto cPath = jni2c(path, env);
|
||||
|
||||
try {
|
||||
kiwix::Reader reader(cPath);
|
||||
kiwix::Book book;
|
||||
book.update(reader);
|
||||
return LIBRARY->addBook(book);
|
||||
} catch (std::exception& e) {
|
||||
LOG("Unable to add the book");
|
||||
LOG(e.what()); }
|
||||
return false;
|
||||
}
|
||||
|
||||
METHOD(jobject, Library, getBookById, jstring id) {
|
||||
auto cId = jni2c(id, env);
|
||||
auto cBook = new kiwix::Book(LIBRARY->getBookById(cId));
|
||||
jclass cls = env->FindClass("org/kiwix/kiwixlib/Book");
|
||||
jmethodID constructorId = env->GetMethodID(cls, "<init>", "()V");
|
||||
jobject book = env->NewObject(cls, constructorId);
|
||||
setPtr(env, book, cBook);
|
||||
return book;
|
||||
}
|
||||
|
||||
METHOD(jint, Library, getBookCount, jboolean localBooks, jboolean remoteBooks) {
|
||||
return LIBRARY->getBookCount(localBooks, remoteBooks);
|
||||
}
|
||||
|
||||
METHOD0(jobjectArray, Library, getBooksIds) {
|
||||
return c2jni(LIBRARY->getBooksIds(), env);
|
||||
}
|
||||
|
||||
METHOD(jobjectArray, Library, filter, jobject filterObj) {
|
||||
auto filter = getPtr<kiwix::Filter>(env, filterObj);
|
||||
return c2jni(LIBRARY->filter(*filter), env);
|
||||
}
|
||||
|
||||
METHOD0(jobjectArray, Library, getBooksLanguages) {
|
||||
return c2jni(LIBRARY->getBooksLanguages(), env);
|
||||
}
|
||||
|
||||
METHOD0(jobjectArray, Library, getBooksCreators) {
|
||||
return c2jni(LIBRARY->getBooksCreators(), env);
|
||||
}
|
||||
|
||||
METHOD0(jobjectArray, Library, getBooksPublisher) {
|
||||
return c2jni(LIBRARY->getBooksPublishers(), env);
|
||||
}
|
||||
|
||||
132
src/wrapper/java/manager.cpp
Normal file
132
src/wrapper/java/manager.cpp
Normal file
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Matthieu Gautier <mgautier@kymeria.fr>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
|
||||
#include <jni.h>
|
||||
#include <zim/file.h>
|
||||
#include "org_kiwix_kiwixlib_Manager.h"
|
||||
|
||||
#include "manager.h"
|
||||
#include "utils.h"
|
||||
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_kiwix_kiwixlib_Manager_allocate(
|
||||
JNIEnv* env, jobject thisObj, jobject libraryObj)
|
||||
{
|
||||
auto lib = getPtr<kiwix::Library>(env, libraryObj);
|
||||
allocate<kiwix::Manager>(env, thisObj, lib);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_kiwix_kiwixlib_Manager_dispose(JNIEnv* env, jobject thisObj)
|
||||
{
|
||||
dispose<kiwix::Manager>(env, thisObj);
|
||||
}
|
||||
|
||||
#define MANAGER (getPtr<kiwix::Manager>(env, thisObj))
|
||||
|
||||
/* Kiwix manager functions */
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_org_kiwix_kiwixlib_Manager_readFile(
|
||||
JNIEnv* env, jobject thisObj, jstring path)
|
||||
{
|
||||
auto cPath = jni2c(path, env);
|
||||
|
||||
try {
|
||||
return MANAGER->readFile(cPath);
|
||||
} catch (std::exception& e) {
|
||||
LOG("Unable to get readFile");
|
||||
LOG(e.what());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_org_kiwix_kiwixlib_Manager_readXml(
|
||||
JNIEnv* env, jobject thisObj, jstring content, jstring libraryPath)
|
||||
{
|
||||
auto cContent = jni2c(content, env);
|
||||
auto cPath = jni2c(libraryPath, env);
|
||||
|
||||
try {
|
||||
return MANAGER->readXml(cContent, false, cPath);
|
||||
} catch (std::exception& e) {
|
||||
LOG("Unable to get ZIM id");
|
||||
LOG(e.what());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_org_kiwix_kiwixlib_Manager_readOpds(
|
||||
JNIEnv* env, jobject thisObj, jstring content, jstring urlHost)
|
||||
{
|
||||
auto cContent = jni2c(content, env);
|
||||
auto cUrl = jni2c(urlHost, env);
|
||||
|
||||
try {
|
||||
return MANAGER->readOpds(cContent, cUrl);
|
||||
} catch (std::exception& e) {
|
||||
LOG("Unable to get ZIM id");
|
||||
LOG(e.what());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_org_kiwix_kiwixlib_Manager_readBookmarkFile(
|
||||
JNIEnv* env, jobject thisObj, jstring path)
|
||||
{
|
||||
auto cPath = jni2c(path, env);
|
||||
|
||||
try {
|
||||
return MANAGER->readBookmarkFile(cPath);
|
||||
} catch (std::exception& e) {
|
||||
LOG("Unable to get ZIM id");
|
||||
LOG(e.what());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_kiwix_kiwixlib_Manager_addBookFromPath(
|
||||
JNIEnv* env, jobject thisObj,
|
||||
jstring pathToOpen, jstring pathToSave, jstring url, jboolean checkMetaData)
|
||||
{
|
||||
auto cPathToOpen = jni2c(pathToOpen, env);
|
||||
auto cPathToSave = jni2c(pathToSave, env);
|
||||
auto cUrl = jni2c(url, env);
|
||||
jstring id = NULL;
|
||||
|
||||
try {
|
||||
auto cId = MANAGER->addBookFromPathAndGetId(cPathToOpen, cPathToSave, cUrl, checkMetaData);
|
||||
if ( !cId.empty() ) {
|
||||
id = c2jni(cId, env);
|
||||
}
|
||||
} catch (std::exception& e) {
|
||||
LOG("Unable to get ZIM file size");
|
||||
LOG(e.what());
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
55
src/wrapper/java/meson.build
Normal file
55
src/wrapper/java/meson.build
Normal file
@@ -0,0 +1,55 @@
|
||||
|
||||
java_sources = files([
|
||||
'org/kiwix/kiwixlib/JNIICU.java',
|
||||
'org/kiwix/kiwixlib/Book.java',
|
||||
'org/kiwix/kiwixlib/JNIKiwixReader.java',
|
||||
'org/kiwix/kiwixlib/Library.java',
|
||||
'org/kiwix/kiwixlib/Manager.java',
|
||||
'org/kiwix/kiwixlib/Filter.java',
|
||||
'org/kiwix/kiwixlib/JNIKiwixSearcher.java',
|
||||
'org/kiwix/kiwixlib/JNIKiwixServer.java',
|
||||
'org/kiwix/kiwixlib/JNIKiwixInt.java',
|
||||
'org/kiwix/kiwixlib/JNIKiwixString.java',
|
||||
'org/kiwix/kiwixlib/JNIKiwixBool.java',
|
||||
'org/kiwix/kiwixlib/JNIKiwixException.java',
|
||||
'org/kiwix/kiwixlib/Pair.java'
|
||||
])
|
||||
|
||||
kiwix_jni = custom_target('jni',
|
||||
input: java_sources,
|
||||
output: ['org_kiwix_kiwixlib_JNIKiwix.h',
|
||||
'org_kiwix_kiwixlib_Book.h',
|
||||
'org_kiwix_kiwixlib_JNIKiwixReader.h',
|
||||
'org_kiwix_kiwixlib_Library.h',
|
||||
'org_kiwix_kiwixlib_Manager.h',
|
||||
'org_kiwix_kiwixlib_Filter.h',
|
||||
'org_kiwix_kiwixlib_JNIKiwixServer.h',
|
||||
'org_kiwix_kiwixlib_JNIKiwixSearcher.h',
|
||||
'org_kiwix_kiwixlib_JNIKiwixSearcher_Result.h'],
|
||||
command:['javac', '-d', '@OUTDIR@', '-h', '@OUTDIR@', '@INPUT@']
|
||||
)
|
||||
|
||||
jni_sources = files([
|
||||
'kiwixicu.cpp',
|
||||
'book.cpp',
|
||||
'kiwixreader.cpp',
|
||||
'library.cpp',
|
||||
'manager.cpp',
|
||||
'filter.cpp',
|
||||
'kiwixsearcher.cpp',
|
||||
'kiwixserver.cpp',
|
||||
])
|
||||
|
||||
kiwix_sources += jni_sources + [kiwix_jni]
|
||||
|
||||
if 'java' in wrapper
|
||||
kiwix_jar = jar('kiwixlib', java_sources)
|
||||
#junit_jar = files('org/kiwix/testing/junit-4.13.jar')
|
||||
#test_jar = jar('testing', 'org/kiwix/testing/test.java',
|
||||
# link_with: [kiwix_jar, junit_jar])
|
||||
#test('javatest', test_jar)
|
||||
endif
|
||||
|
||||
install_subdir('org', install_dir: 'kiwix-lib/java', exclude_directories: ['kiwix/testing'])
|
||||
install_subdir('res', install_dir: 'kiwix-lib')
|
||||
install_data('AndroidManifest.xml', install_dir: 'kiwix-lib')
|
||||
47
src/wrapper/java/org/kiwix/kiwixlib/Book.java
Normal file
47
src/wrapper/java/org/kiwix/kiwixlib/Book.java
Normal file
@@ -0,0 +1,47 @@
|
||||
|
||||
package org.kiwix.kiwixlib;
|
||||
|
||||
public class Book
|
||||
{
|
||||
public Book() { allocate(); }
|
||||
|
||||
|
||||
public native void update(Book book);
|
||||
public native void update(JNIKiwixReader reader);
|
||||
|
||||
@Override
|
||||
protected void finalize() { dispose(); }
|
||||
|
||||
public native String getId();
|
||||
public native String getPath();
|
||||
public native boolean isPathValid();
|
||||
public native String getTitle();
|
||||
public native String getDescription();
|
||||
public native String getLanguage();
|
||||
public native String getCreator();
|
||||
public native String getPublisher();
|
||||
public native String getDate();
|
||||
public native String getUrl();
|
||||
public native String getName();
|
||||
public native String getFlavour();
|
||||
public native String getTags();
|
||||
/**
|
||||
* Return the value associated to the tag tagName
|
||||
*
|
||||
* @param tagName the tag name to search for.
|
||||
* @return The value of the tag. If the tag is not found, return empty string.
|
||||
*/
|
||||
public native String getTagStr(String tagName);
|
||||
|
||||
public native long getArticleCount();
|
||||
public native long getMediaCount();
|
||||
public native long getSize();
|
||||
|
||||
public native String getFavicon();
|
||||
public native String getFaviconUrl();
|
||||
public native String getFaviconMimeType();
|
||||
|
||||
private native void allocate();
|
||||
private native void dispose();
|
||||
private long nativeHandle;
|
||||
}
|
||||
44
src/wrapper/java/org/kiwix/kiwixlib/Filter.java
Normal file
44
src/wrapper/java/org/kiwix/kiwixlib/Filter.java
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (C) 2019-2020 Matthieu Gautier <mgautier@kymeria.fr>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.kiwix.kiwixlib;
|
||||
|
||||
public class Filter
|
||||
{
|
||||
|
||||
public native Filter local(boolean accept);
|
||||
public native Filter remote(boolean accept);
|
||||
public native Filter valid(boolean accept);
|
||||
public native Filter acceptTags(String[] tags);
|
||||
public native Filter rejectTags(String[] tags);
|
||||
public native Filter lang(String lang);
|
||||
public native Filter publisher(String publisher);
|
||||
public native Filter creator(String creator);
|
||||
public native Filter maxSize(long size);
|
||||
public native Filter query(String query);
|
||||
|
||||
|
||||
public Filter() { allocate(); }
|
||||
|
||||
@Override
|
||||
protected void finalize() { dispose(); }
|
||||
private native void allocate();
|
||||
private native void dispose();
|
||||
private long nativeHandle;
|
||||
}
|
||||
@@ -82,6 +82,16 @@ public class JNIKiwixReader
|
||||
int len,
|
||||
JNIKiwixInt size);
|
||||
|
||||
/**
|
||||
*
|
||||
* Get the size of an article.
|
||||
*
|
||||
* @param url The url of the article.
|
||||
* @return The size of the final (redirections are resolved) article (in byte).
|
||||
* Return 0 if the article is not found.
|
||||
*/
|
||||
public native long getArticleSize(String url);
|
||||
|
||||
/**
|
||||
* getDirectAccessInformation.
|
||||
*
|
||||
@@ -20,7 +20,7 @@
|
||||
package org.kiwix.kiwixlib;
|
||||
|
||||
import org.kiwix.kiwixlib.JNIKiwixException;
|
||||
import org.kiwix.kiwixlib.JNIKiwixLibrary;
|
||||
import org.kiwix.kiwixlib.Library;
|
||||
|
||||
public class JNIKiwixServer
|
||||
{
|
||||
@@ -34,15 +34,17 @@ public class JNIKiwixServer
|
||||
|
||||
public native void setTaskbar(boolean withTaskBar, boolean witLibraryButton);
|
||||
|
||||
public native void setBlockExternalLinks(boolean blockExternalLinks);
|
||||
|
||||
public native boolean start();
|
||||
|
||||
public native void stop();
|
||||
|
||||
public JNIKiwixServer(JNIKiwixLibrary library)
|
||||
public JNIKiwixServer(Library library)
|
||||
{
|
||||
nativeHandle = getNativeServer(library);
|
||||
}
|
||||
|
||||
private native long getNativeServer(JNIKiwixLibrary library);
|
||||
private native long getNativeServer(Library library);
|
||||
private long nativeHandle;
|
||||
}
|
||||
49
src/wrapper/java/org/kiwix/kiwixlib/Library.java
Normal file
49
src/wrapper/java/org/kiwix/kiwixlib/Library.java
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) 2019-2020 Matthieu Gautier <mgautier@kymeria.fr>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.kiwix.kiwixlib;
|
||||
|
||||
import org.kiwix.kiwixlib.Book;
|
||||
import org.kiwix.kiwixlib.JNIKiwixException;
|
||||
|
||||
public class Library
|
||||
{
|
||||
public native boolean addBook(String path) throws JNIKiwixException;
|
||||
|
||||
public native Book getBookById(String id);
|
||||
public native int getBookCount(boolean localBooks, boolean remoteBooks);
|
||||
|
||||
public native String[] getBooksIds();
|
||||
public native String[] filter(Filter filter);
|
||||
|
||||
public native String[] getBooksLanguages();
|
||||
public native String[] getBooksCreators();
|
||||
public native String[] getBooksPublishers();
|
||||
|
||||
public Library()
|
||||
{
|
||||
allocate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() { dispose(); }
|
||||
private native void allocate();
|
||||
private native void dispose();
|
||||
private long nativeHandle;
|
||||
}
|
||||
90
src/wrapper/java/org/kiwix/kiwixlib/Manager.java
Normal file
90
src/wrapper/java/org/kiwix/kiwixlib/Manager.java
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Matthieu Gautier <mgautier@kymeria.fr>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.kiwix.kiwixlib;
|
||||
|
||||
import org.kiwix.kiwixlib.Library;
|
||||
|
||||
public class Manager
|
||||
{
|
||||
/**
|
||||
* Read a `library.xml` file and add books in the library.
|
||||
*
|
||||
* @param path The (utf8) path to the `library.xml` file.
|
||||
* @return True if the file has been properly parsed.
|
||||
*/
|
||||
public native boolean readFile(String path);
|
||||
|
||||
/**
|
||||
* Load a library content stored in a string (at `library.xml` format).
|
||||
*
|
||||
* @param content The content corresponding of the library xml.
|
||||
* @param libraryPath The library path (used to resolve relative paths)
|
||||
* @return True if the content has been properly parsed.
|
||||
*/
|
||||
public native boolean readXml(String content, String libraryPath);
|
||||
|
||||
/**
|
||||
* Load a library content stored in a string (at OPDS stream format)
|
||||
*
|
||||
* @param content the content of the OPDS stream.
|
||||
* @param urlHost the url of the stream (used to resolve relative url)
|
||||
* @return True if the content has been properly parsed.
|
||||
*/
|
||||
public native boolean readOpds(String content, String urlHost);
|
||||
|
||||
/**
|
||||
* Load a bookmark file
|
||||
*
|
||||
* @param path The path of the file to read.
|
||||
* @return True if the content has been properly parsed
|
||||
*/
|
||||
public native boolean readBookmarkFile(String path);
|
||||
|
||||
/**
|
||||
* Add a book to the library.
|
||||
*
|
||||
* @param pathToOpen The path of the zim file to add.
|
||||
* @param pathToSave The path to store in the library in place of
|
||||
* pathToOpen.
|
||||
* @param url The url of the book to store in the library
|
||||
* (useful for kiiwix-serve catalog)
|
||||
* @param checkMetaData Tell if we check metadata before adding a book to the
|
||||
* library.
|
||||
* @return The id of te book if the book has been added to the library.
|
||||
* Empty string if not.
|
||||
*/
|
||||
public native String addBookFromPath(String pathToOpen,
|
||||
String pathToSave,
|
||||
String url,
|
||||
boolean checkMetaData);
|
||||
|
||||
public Manager(Library library) {
|
||||
allocate(library);
|
||||
_library = library;
|
||||
}
|
||||
|
||||
private Library _library;
|
||||
|
||||
@Override
|
||||
protected void finalize() { dispose(); }
|
||||
private native void allocate(Library library);
|
||||
private native void dispose();
|
||||
private long nativeHandle;
|
||||
}
|
||||
@@ -22,5 +22,5 @@ package org.kiwix.kiwixlib;
|
||||
public class Pair
|
||||
{
|
||||
public String filename;
|
||||
public int offset;
|
||||
public long offset;
|
||||
}
|
||||
26
src/wrapper/java/org/kiwix/testing/compile_test.sh
Executable file
26
src/wrapper/java/org/kiwix/testing/compile_test.sh
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
# This script compile the unit test to test the java wrapper.
|
||||
# This is not integrated in meson because ... this is not so easy.
|
||||
|
||||
|
||||
KIWIX_LIB_JAR=$1
|
||||
if [ -z $KIWIX_LIB_JAR ]
|
||||
then
|
||||
echo "You must give the path to the kiwixlib.jar as first argument"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
KIWIX_LIB_DIR=$2
|
||||
if [ -z $KIWIX_LIB_DIR ]
|
||||
then
|
||||
echo "You must give the path to directory containing libkiwix.so as second argument"
|
||||
exit 1
|
||||
fi
|
||||
TEST_SOURCE_DIR=$(dirname $(readlink -f $0))
|
||||
|
||||
|
||||
javac -g -d . -s . -cp $TEST_SOURCE_DIR/junit-4.13.jar:$KIWIX_LIB_JAR $TEST_SOURCE_DIR/test.java
|
||||
|
||||
java -Djava.library.path=$KIWIX_LIB_DIR -cp $TEST_SOURCE_DIR/junit-4.13.jar:$TEST_SOURCE_DIR/hamcrest-core-1.3.jar:$KIWIX_LIB_JAR:. org.junit.runner.JUnitCore test
|
||||
|
||||
BIN
src/wrapper/java/org/kiwix/testing/hamcrest-core-1.3.jar
Normal file
BIN
src/wrapper/java/org/kiwix/testing/hamcrest-core-1.3.jar
Normal file
Binary file not shown.
BIN
src/wrapper/java/org/kiwix/testing/junit-4.13.jar
Normal file
BIN
src/wrapper/java/org/kiwix/testing/junit-4.13.jar
Normal file
Binary file not shown.
53
src/wrapper/java/org/kiwix/testing/test.java
Normal file
53
src/wrapper/java/org/kiwix/testing/test.java
Normal file
@@ -0,0 +1,53 @@
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.*;
|
||||
import org.kiwix.kiwixlib.*;
|
||||
|
||||
public class test {
|
||||
static {
|
||||
System.loadLibrary("kiwix");
|
||||
}
|
||||
|
||||
private static String getCatalogContent()
|
||||
throws IOException
|
||||
{
|
||||
BufferedReader reader = new BufferedReader(new FileReader("catalog.xml"));
|
||||
String line;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
while ((line = reader.readLine()) != null)
|
||||
{
|
||||
sb.append(line + "\n");
|
||||
}
|
||||
reader.close();
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSome()
|
||||
throws IOException
|
||||
{
|
||||
Library lib = new Library();
|
||||
Manager manager = new Manager(lib);
|
||||
String content = getCatalogContent();
|
||||
manager.readOpds(content, "https://library.kiwix.org");
|
||||
assertEquals(lib.getBookCount(true, true), 10);
|
||||
String[] bookIds = lib.getBooksIds();
|
||||
assertEquals(bookIds.length, 10);
|
||||
Book book = lib.getBookById(bookIds[0]);
|
||||
assertEquals(book.getTitle(), "Wikisource");
|
||||
assertEquals(book.getTags(), "wikisource;_category:wikisource;_pictures:no;_videos:no;_details:yes;_ftindex:yes");
|
||||
assertEquals(book.getFaviconUrl(), "https://library.kiwix.org/meta?name=favicon&content=wikisource_fr_all_nopic_2020-01");
|
||||
assertEquals(book.getUrl(), "http://download.kiwix.org/zim/wikisource/wikisource_fr_all_nopic_2020-01.zim.meta4");
|
||||
}
|
||||
|
||||
static
|
||||
public void main(String[] args) {
|
||||
Library lib = new Library();
|
||||
lib.getBookCount(true, true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -26,9 +26,55 @@
|
||||
|
||||
#include <pthread.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
#if __ANDROID__
|
||||
#include <android/log.h>
|
||||
#define LOG(...) __android_log_print(ANDROID_LOG_ERROR, "kiwix", __VA_ARGS__)
|
||||
#else
|
||||
#define LOG(...)
|
||||
#endif
|
||||
|
||||
extern pthread_mutex_t globalLock;
|
||||
|
||||
template<typename T>
|
||||
void setPtr(JNIEnv* env, jobject thisObj, T* ptr)
|
||||
{
|
||||
jclass thisClass = env->GetObjectClass(thisObj);
|
||||
jfieldID fieldId = env->GetFieldID(thisClass, "nativeHandle", "J");
|
||||
env->SetLongField(thisObj, fieldId, reinterpret_cast<jlong>(ptr));
|
||||
}
|
||||
|
||||
template<typename T, typename ...Args>
|
||||
void allocate(JNIEnv* env, jobject thisObj, Args && ...args)
|
||||
{
|
||||
T* ptr = new T(std::forward<Args>(args)...);
|
||||
setPtr(env, thisObj, ptr);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T* getPtr(JNIEnv* env, jobject thisObj)
|
||||
{
|
||||
jclass thisClass = env->GetObjectClass(thisObj);
|
||||
jfieldID fidNumber = env->GetFieldID(thisClass, "nativeHandle", "J");
|
||||
return reinterpret_cast<T*>(env->GetLongField(thisObj, fidNumber));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void dispose(JNIEnv* env, jobject thisObj)
|
||||
{
|
||||
delete getPtr<T>(env, thisObj);
|
||||
}
|
||||
|
||||
#define METHOD0(retType, class, name) \
|
||||
JNIEXPORT retType JNICALL Java_org_kiwix_kiwixlib_##class##_##name( \
|
||||
JNIEnv* env, jobject thisObj)
|
||||
|
||||
#define METHOD(retType, class, name, ...) \
|
||||
JNIEXPORT retType JNICALL Java_org_kiwix_kiwixlib_##class##_##name( \
|
||||
JNIEnv* env, jobject thisObj, __VA_ARGS__)
|
||||
|
||||
inline jfieldID getHandleField(JNIEnv* env, jobject obj)
|
||||
{
|
||||
jclass c = env->GetObjectClass(obj);
|
||||
@@ -36,6 +82,12 @@ inline jfieldID getHandleField(JNIEnv* env, jobject obj)
|
||||
return env->GetFieldID(c, "nativeHandle", "J");
|
||||
}
|
||||
|
||||
inline jobjectArray createArray(JNIEnv* env, size_t length, const std::string& type_sig)
|
||||
{
|
||||
jclass c = env->FindClass(type_sig.c_str());
|
||||
return env->NewObjectArray(length, c, NULL);
|
||||
}
|
||||
|
||||
class Lock
|
||||
{
|
||||
protected:
|
||||
@@ -91,19 +143,67 @@ struct LockedHandle : public Lock {
|
||||
T* operator->() { return h->h; }
|
||||
T* operator*() { return h->h; }
|
||||
operator bool() const { return (h->h != nullptr); }
|
||||
operator T*() const { return h->h; }
|
||||
};
|
||||
|
||||
/* c2jni type conversion functions */
|
||||
inline jboolean c2jni(const bool& val) { return val ? JNI_TRUE : JNI_FALSE; }
|
||||
template<typename T>
|
||||
struct JType { };
|
||||
|
||||
template<> struct JType<bool>{ typedef jboolean type_t; };
|
||||
template<> struct JType<int>{ typedef jint type_t; };
|
||||
template<> struct JType<long>{ typedef jlong type_t; };
|
||||
template<> struct JType<uint64_t> { typedef jlong type_t; };
|
||||
template<> struct JType<uint32_t> { typedef jlong type_t; };
|
||||
template<> struct JType<std::string>{ typedef jstring type_t; };
|
||||
template<> struct JType<std::vector<std::string>>{ typedef jobjectArray type_t; };
|
||||
|
||||
template<typename T>
|
||||
inline typename JType<T>::type_t c2jni(const T& val, JNIEnv* env) {
|
||||
return static_cast<typename JType<T>::type_t>(val);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline jboolean c2jni(const bool& val, JNIEnv* env) { return val ? JNI_TRUE : JNI_FALSE; }
|
||||
|
||||
template<>
|
||||
inline jstring c2jni(const std::string& val, JNIEnv* env)
|
||||
{
|
||||
return env->NewStringUTF(val.c_str());
|
||||
}
|
||||
|
||||
inline jint c2jni(const int val) { return (jint)val; }
|
||||
inline jint c2jni(const unsigned val) { return (unsigned)val; }
|
||||
template<>
|
||||
inline jobjectArray c2jni(const std::vector<std::string>& val, JNIEnv* env)
|
||||
{
|
||||
auto array = createArray(env, val.size(), "java/lang/String");
|
||||
size_t index = 0;
|
||||
for (auto& elem: val) {
|
||||
auto jElem = c2jni(elem, env);
|
||||
env->SetObjectArrayElement(array, index++, jElem);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
template<typename T, typename U=T>
|
||||
struct CType { };
|
||||
|
||||
template<> struct CType<jboolean>{ typedef bool type_t; };
|
||||
template<> struct CType<jint>{ typedef int type_t; };
|
||||
template<> struct CType<jlong>{ typedef long type_t; };
|
||||
template<> struct CType<jstring>{ typedef std::string type_t; };
|
||||
|
||||
template<typename U>
|
||||
struct CType<jobjectArray, U>{ typedef std::vector<typename CType<U>::type_t> type_t; };
|
||||
|
||||
/* jni2c type conversion functions */
|
||||
inline bool jni2c(const jboolean& val) { return val == JNI_TRUE; }
|
||||
template<typename T, typename U=T>
|
||||
inline typename CType<T>::type_t jni2c(const T& val, JNIEnv* env) {
|
||||
return static_cast<typename CType<T>::type_t>(val);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline bool jni2c(const jboolean& val, JNIEnv* env) { return val == JNI_TRUE; }
|
||||
|
||||
template<>
|
||||
inline std::string jni2c(const jstring& val, JNIEnv* env)
|
||||
{
|
||||
const char* chars = env->GetStringUTFChars(val, 0);
|
||||
@@ -112,7 +212,21 @@ inline std::string jni2c(const jstring& val, JNIEnv* env)
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline int jni2c(const jint val) { return (int)val; }
|
||||
template<typename U>
|
||||
inline typename CType<jobjectArray, U>::type_t jni2c(const jobjectArray& val, JNIEnv* env)
|
||||
{
|
||||
jsize length = env->GetArrayLength(val);
|
||||
typename CType<jobjectArray, U>::type_t v(length);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < length; i++) {
|
||||
U obj = (U) env->GetObjectArrayElement(val, i);
|
||||
auto cobj = jni2c<U>(obj, env);
|
||||
v.push_back(cobj);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
/* Method to deal with variable passed by reference */
|
||||
inline std::string getStringObjValue(const jobject obj, JNIEnv* env)
|
||||
{
|
||||
@@ -141,17 +255,17 @@ inline void setBoolObjValue(const bool value, const jobject obj, JNIEnv* env)
|
||||
{
|
||||
jclass objClass = env->GetObjectClass(obj);
|
||||
jfieldID objFid = env->GetFieldID(objClass, "value", "Z");
|
||||
env->SetIntField(obj, objFid, c2jni(value));
|
||||
env->SetIntField(obj, objFid, c2jni(value, env));
|
||||
}
|
||||
|
||||
inline void setPairObjValue(const std::string& filename, const int offset,
|
||||
inline void setPairObjValue(const std::string& filename, const long offset,
|
||||
const jobject obj, JNIEnv* env)
|
||||
{
|
||||
jclass objClass = env->GetObjectClass(obj);
|
||||
jfieldID filenameFid = env->GetFieldID(objClass, "filename", "Ljava/lang/String;");
|
||||
env->SetObjectField(obj, filenameFid, c2jni(filename, env));
|
||||
jfieldID offsetFid = env->GetFieldID(objClass, "offset", "I");
|
||||
env->SetIntField(obj, offsetFid, offset);
|
||||
jfieldID offsetFid = env->GetFieldID(objClass, "offset", "J");
|
||||
env->SetLongField(obj, offsetFid, offset);
|
||||
}
|
||||
|
||||
#endif // _ANDROID_JNI_UTILS_H
|
||||
@@ -1,3 +1,8 @@
|
||||
resource_files = run_command(find_program('python3'),
|
||||
'-c',
|
||||
'import sys; f=open(sys.argv[1]); print(f.read())',
|
||||
files('resources_list.txt')
|
||||
).stdout().strip().split('\n')
|
||||
|
||||
lib_resources = custom_target('resources',
|
||||
input: 'resources_list.txt',
|
||||
@@ -7,5 +12,5 @@ lib_resources = custom_target('resources',
|
||||
'--hfile', '@OUTPUT1@',
|
||||
'--source_dir', '@OUTDIR@',
|
||||
'@INPUT@'],
|
||||
build_always_stale: true
|
||||
depend_files: resource_files
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<Description>Search zim files in the catalog.</Description>
|
||||
<Url type="application/atom+xml;profile=opds-catalog"
|
||||
xmlns:atom="http://www.w3.org/2005/Atom"
|
||||
xmlns:k="http://kiwix.org/opensearchextension/1.0"
|
||||
indexOffset="0"
|
||||
template="/{{root}}/catalog/search?q={searchTerms}&lang={language}&count={count}&start={startIndex}"/>
|
||||
template="/{{root}}/catalog/search?q={searchTerms?}&lang={language?}&name={k:name?}&tag={k:tag?}¬ag={k:notag?}&maxsize={k:maxsize?}&count={count?}&start={startIndex?}"/>
|
||||
</OpenSearchDescription>
|
||||
|
||||
@@ -20,6 +20,7 @@ skin/jquery-ui/jquery-ui.min.css
|
||||
skin/caret.png
|
||||
skin/taskbar.js
|
||||
skin/taskbar.css
|
||||
skin/block_external.js
|
||||
templates/search_result.html
|
||||
templates/no_search_result.html
|
||||
templates/404.html
|
||||
@@ -28,4 +29,6 @@ templates/index.html
|
||||
templates/suggestion.json
|
||||
templates/head_part.html
|
||||
templates/taskbar_part.html
|
||||
templates/external_blocker_part.html
|
||||
templates/captured_external.html
|
||||
opensearchdescription.xml
|
||||
|
||||
72
static/skin/block_external.js
Normal file
72
static/skin/block_external.js
Normal file
@@ -0,0 +1,72 @@
|
||||
var block_path = "/catch/external";
|
||||
// called only on external links
|
||||
function capture_event(e, target) { target.setAttribute("href", encodeURI(block_path + "?source=" + target.href)); }
|
||||
|
||||
// called on all link clicks. filters external and call capture_event
|
||||
function on_click_event(e) {
|
||||
var target = findParent("a", e.target);
|
||||
if (target !== null && "href" in target) {
|
||||
var href = target.href;
|
||||
if (window.location.pathname.indexOf(block_path) == 0) // already in catch page
|
||||
return;
|
||||
if (href.indexOf(window.location.origin) == 0)
|
||||
return;
|
||||
if (href.substr(0, 2) == "//")
|
||||
return capture_event(e, target);
|
||||
if (href.substr(0, 5) == "http:")
|
||||
return capture_event(e, target);
|
||||
if (href.substr(0, 6) == "https:")
|
||||
return capture_event(e, target);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// script entrypoint (called on document ready)
|
||||
function run() { live('a', 'click', on_click_event); }
|
||||
|
||||
// find first parent with tagname
|
||||
function findParent(tagname, el) {
|
||||
while (el) {
|
||||
if ((el.nodeName || el.tagName).toLowerCase() === tagname.toLowerCase()) {
|
||||
return el;
|
||||
}
|
||||
el = el.parentNode;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// matches polyfill
|
||||
this.Element && function(ElementPrototype) {
|
||||
ElementPrototype.matches = ElementPrototype.matches ||
|
||||
ElementPrototype.matchesSelector ||
|
||||
ElementPrototype.webkitMatchesSelector ||
|
||||
ElementPrototype.msMatchesSelector ||
|
||||
function(selector) {
|
||||
var node = this, nodes = (node.parentNode || node.document).querySelectorAll(selector), i = -1;
|
||||
while (nodes[++i] && nodes[i] != node);
|
||||
return !!nodes[i];
|
||||
}
|
||||
}(Element.prototype);
|
||||
|
||||
// helper for enabling IE 8 event bindings
|
||||
function addEvent(el, type, handler) {
|
||||
if (el.attachEvent) el.attachEvent('on'+type, handler); else el.addEventListener(type, handler);
|
||||
}
|
||||
|
||||
// live binding helper using matchesSelector
|
||||
function live(selector, event, callback, context) {
|
||||
addEvent(context || document, event, function(e) {
|
||||
var found, el = e.target || e.srcElement;
|
||||
while (el && el.matches && el !== context && !(found = el.matches(selector))) el = el.parentElement;
|
||||
if (found) callback.call(el, e);
|
||||
});
|
||||
}
|
||||
|
||||
// in case the document is already rendered
|
||||
if (document.readyState!='loading') run();
|
||||
// modern browsers
|
||||
else if (document.addEventListener) document.addEventListener('DOMContentLoaded', run);
|
||||
// IE <= 8
|
||||
else document.attachEvent('onreadystatechange', function(){
|
||||
if (document.readyState=='complete') run();
|
||||
});
|
||||
16150
static/skin/jquery-ui/jquery-ui.js
vendored
Normal file
16150
static/skin/jquery-ui/jquery-ui.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
14
static/templates/captured_external.html
Normal file
14
static/templates/captured_external.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>External link blocked</title>
|
||||
</head>
|
||||
<body class="kiwix">
|
||||
<h1>External link blocked</h1>
|
||||
<p>This instance of Kiwix protects you from accidentaly going to external (out-of ZIM) links.</p>
|
||||
<p>If you intend to go to such locations, please click the link below.</p>
|
||||
<p><a href="{{ source }}">Go to {{ source }}</a></p>
|
||||
<div id="kiwixfooter">Powered by <a href="https://kiwix.org">Kiwix</a></div>
|
||||
</body>
|
||||
</html>
|
||||
1
static/templates/external_blocker_part.html
Normal file
1
static/templates/external_blocker_part.html
Normal file
@@ -0,0 +1 @@
|
||||
<script type="text/javascript" src="{{root}}/skin/block_external.js"></script>
|
||||
33
test/book.cpp
Normal file
33
test/book.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#include "gtest/gtest.h"
|
||||
#include "../include/book.h"
|
||||
|
||||
TEST(BookTest, updateTest)
|
||||
{
|
||||
kiwix::Book book;
|
||||
|
||||
book.setReadOnly(false);
|
||||
book.setPath("/home/user/Downloads/skin-of-color-society_en_all_2019-11.zim");
|
||||
book.setPathValid(true);
|
||||
book.setUrl("book-url");
|
||||
book.setTags("youtube;_videos:yes;_ftindex:yes;_ftindex:yes;_pictures:yes;_details:yes");
|
||||
book.setName("skin-of-color-society_en_all");
|
||||
book.setFavicon("book-favicon");
|
||||
book.setFaviconMimeType("book-favicon-mimetype");
|
||||
|
||||
kiwix::Book newBook;
|
||||
|
||||
newBook.setReadOnly(true);
|
||||
EXPECT_FALSE(newBook.update(book));
|
||||
|
||||
newBook.setReadOnly(false);
|
||||
EXPECT_TRUE(newBook.update(book));
|
||||
|
||||
EXPECT_EQ(newBook.readOnly(), book.readOnly());
|
||||
EXPECT_EQ(newBook.getPath(), book.getPath());
|
||||
EXPECT_EQ(newBook.isPathValid(), book.isPathValid());
|
||||
EXPECT_EQ(newBook.getUrl(), book.getUrl());
|
||||
EXPECT_EQ(newBook.getTags(), book.getTags());
|
||||
EXPECT_EQ(newBook.getName(), book.getName());
|
||||
EXPECT_EQ(newBook.getFavicon(), book.getFavicon());
|
||||
EXPECT_EQ(newBook.getFaviconMimeType(), book.getFaviconMimeType());
|
||||
}
|
||||
BIN
test/data/corner_cases.zim
Normal file
BIN
test/data/corner_cases.zim
Normal file
Binary file not shown.
0
test/data/corner_cases/empty.css
Normal file
0
test/data/corner_cases/empty.css
Normal file
0
test/data/corner_cases/empty.html
Normal file
0
test/data/corner_cases/empty.html
Normal file
0
test/data/corner_cases/empty.js
Normal file
0
test/data/corner_cases/empty.js
Normal file
0
test/data/corner_cases/empty.png
Normal file
0
test/data/corner_cases/empty.png
Normal file
15
test/data/create_corner_cases_zim_file
Executable file
15
test/data/create_corner_cases_zim_file
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
rm -f corner_cases.zim
|
||||
zimwriterfs -w empty.html \
|
||||
-f empty.png \
|
||||
-l=en \
|
||||
-t="ZIM corner cases" \
|
||||
-d="" \
|
||||
-c="" \
|
||||
-p="" \
|
||||
corner_cases \
|
||||
corner_cases.zim \
|
||||
&& echo 'corner_cases.zim was successfully created' \
|
||||
|| echo '!!! Failed to create corner_cases.zim !!!' >&2
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user