mirror of
https://github.com/kiwix/libkiwix.git
synced 2025-12-24 15:07:59 -05:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00c8f55cc4 |
12
.codecov.yml
12
.codecov.yml
@@ -1,12 +0,0 @@
|
||||
codecov:
|
||||
notify:
|
||||
require_ci_to_pass: yes
|
||||
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
threshold: 1%
|
||||
|
||||
ignore:
|
||||
- "test"
|
||||
12
.github/FUNDING.yml
vendored
12
.github/FUNDING.yml
vendored
@@ -1,12 +0,0 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
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
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
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/
|
||||
15
.github/stale.yml
vendored
15
.github/stale.yml
vendored
@@ -1,15 +0,0 @@
|
||||
daysUntilClose: false
|
||||
staleLabel: stale
|
||||
|
||||
issues:
|
||||
daysUntilStale: 60
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be now be reviewed manually. Thank you
|
||||
for your contributions.
|
||||
pulls:
|
||||
daysUntilStale: 7
|
||||
markComment: >
|
||||
This pull request has been automatically marked as stale because it has not had
|
||||
recent activity. It will be now be reviewed manually. Thank you
|
||||
for your contributions.
|
||||
200
.github/workflows/ci.yml
vendored
200
.github/workflows/ci.yml
vendored
@@ -1,200 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
macOS:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
- macos-aarch64-dyn
|
||||
- macos-x86_64-dyn
|
||||
- ios-arm64-dyn
|
||||
- ios-x86_64-dyn
|
||||
include:
|
||||
- target: macos-aarch64-dyn
|
||||
arch_name: arm64-apple-macos
|
||||
run_test: true
|
||||
- target: macos-x86_64-dyn
|
||||
arch_name: x86_64-apple-darwin
|
||||
run_test: true
|
||||
- target: ios-arm64-dyn
|
||||
arch_name: aarch64-apple-ios
|
||||
run_test: false
|
||||
- target: ios-x86_64-dyn
|
||||
arch_name: x86-apple-ios-simulator
|
||||
run_test: false
|
||||
runs-on: macos-15
|
||||
|
||||
env:
|
||||
HOME: /Users/runner
|
||||
steps:
|
||||
- name: Retrieve source code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
brew update
|
||||
brew install ninja meson
|
||||
|
||||
- name: Install dependencies
|
||||
uses: kiwix/kiwix-build/actions/dl_deps_archive@main
|
||||
with:
|
||||
target_platform: ${{ matrix.target }}
|
||||
|
||||
- name: Compile
|
||||
env:
|
||||
PKG_CONFIG_PATH: ${{env.HOME}}/BUILD_${{matrix.arch_name}}/INSTALL/lib/pkgconfig
|
||||
CPPFLAGS: -I${{env.HOME}}/BUILD_${{matrix.arch_name}}/INSTALL/include
|
||||
MESON_OPTION: --default-library=shared -Db_coverage=true
|
||||
MESON_CROSSFILE: ${{env.HOME}}/BUILD_${{matrix.arch_name}}/meson_cross_file.txt
|
||||
shell: bash
|
||||
run: |
|
||||
if [ -e $MESON_CROSSFILE ]; then
|
||||
MESON_OPTION="$MESON_OPTION --cross-file $MESON_CROSSFILE -Dstatic-linkage=true"
|
||||
fi
|
||||
meson . build ${MESON_OPTION}
|
||||
ninja -C build
|
||||
|
||||
- name: Test libkiwix
|
||||
if: matrix.run_test
|
||||
env:
|
||||
SKIP_BIG_MEMORY_TEST: 1
|
||||
LD_LIBRARY_PATH: ${{env.HOME}}/BUILD_${{matrix.arch_name}}/INSTALL/lib:${{env.HOME}}/BUILD_${{matrix.arch_name}}/INSTALL/lib64
|
||||
run: meson test -C build --verbose
|
||||
|
||||
Windows:
|
||||
runs-on: windows-2022
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup python 3.10
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Install packages
|
||||
run:
|
||||
choco install pkgconfiglite ninja
|
||||
|
||||
- name: Install python modules
|
||||
run: pip3 install meson
|
||||
|
||||
- name: Setup MSVC compiler
|
||||
uses: bus1/cabuild/action/msdevshell@v1
|
||||
with:
|
||||
architecture: x64
|
||||
|
||||
- name: Install dependencies
|
||||
uses: kiwix/kiwix-build/actions/dl_deps_archive@main
|
||||
with:
|
||||
target_platform: win-x86_64-static
|
||||
|
||||
- name: Compile
|
||||
shell: cmd
|
||||
run: |
|
||||
set PKG_CONFIG_PATH=%cd%\BUILD_win-amd64\INSTALL\lib\pkgconfig
|
||||
set CPPFLAGS=-I%cd%\BUILD_win-amd64\INSTALL\include
|
||||
meson.exe setup . build -Dwerror=false --default-library=static --buildtype=release
|
||||
cd build
|
||||
ninja.exe
|
||||
|
||||
- name: Test
|
||||
shell: cmd
|
||||
run: |
|
||||
cd build
|
||||
meson.exe test --verbose
|
||||
env:
|
||||
WAIT_TIME_FACTOR_TEST: 10
|
||||
|
||||
Linux:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
- linux-x86_64-static
|
||||
- linux-x86_64-dyn
|
||||
- android-arm
|
||||
- android-arm64
|
||||
image_variant: ['jammy']
|
||||
include:
|
||||
- target: linux-x86_64-static
|
||||
lib_postfix: '/x86_64-linux-gnu'
|
||||
arch_name: linux-x86_64
|
||||
run_test: true
|
||||
coverage: true
|
||||
- target: linux-x86_64-dyn
|
||||
lib_postfix: '/x86_64-linux-gnu'
|
||||
arch_name: linux-x86_64
|
||||
run_test: true
|
||||
coverage: true
|
||||
- target: android-arm
|
||||
lib_postfix: '/arm-linux-androideabi'
|
||||
arch_name: arm-linux-androideabi
|
||||
run_test: false
|
||||
coverage: false
|
||||
- target: android-arm64
|
||||
lib_postfix: '/aarch64-linux-android'
|
||||
arch_name: aarch64-linux-android
|
||||
run_test: false
|
||||
coverage: false
|
||||
env:
|
||||
HOME: /home/runner
|
||||
runs-on: ubuntu-22.04
|
||||
container:
|
||||
image: "ghcr.io/kiwix/kiwix-build_ci_${{matrix.image_variant}}:2025-06-07"
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- name: Install dependencies
|
||||
uses: kiwix/kiwix-build/actions/dl_deps_archive@main
|
||||
with:
|
||||
target_platform: ${{ matrix.target }}
|
||||
- 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 [ -e "${{env.HOME}}/BUILD_${{matrix.arch_name}}/meson_cross_file.txt" ]; then
|
||||
MESON_OPTION="$MESON_OPTION --cross-file ${{env.HOME}}/BUILD_${{matrix.arch_name}}/meson_cross_file.txt"
|
||||
else
|
||||
MESON_OPTION="$MESON_OPTION -Db_coverage=true"
|
||||
fi
|
||||
if [[ "${{matrix.target}}" =~ android-.* ]]; then
|
||||
MESON_OPTION="$MESON_OPTION -Dstatic-linkage=true"
|
||||
fi
|
||||
meson . build ${MESON_OPTION}
|
||||
cd build
|
||||
ninja
|
||||
env:
|
||||
PKG_CONFIG_PATH: "/home/runner/BUILD_${{matrix.arch_name}}/INSTALL/lib/pkgconfig:/home/runner/BUILD_${{matrix.arch_name}}/INSTALL/lib${{matrix.lib_postfix}}/pkgconfig"
|
||||
CPPFLAGS: "-I/home/runner/BUILD_${{matrix.arch_name}}/INSTALL/include"
|
||||
- name: Test
|
||||
if: matrix.run_test
|
||||
shell: bash
|
||||
run: |
|
||||
cd build
|
||||
meson test --verbose
|
||||
if [[ "${{matrix.coverage}}" = "true" ]]; then
|
||||
ninja coverage
|
||||
fi
|
||||
env:
|
||||
LD_LIBRARY_PATH: "/home/runner/BUILD_${{matrix.arch_name}}/INSTALL/lib:/home/runner/BUILD_${{matrix.arch_name}}/INSTALL/lib${{matrix.lib_postfix}}"
|
||||
SKIP_BIG_MEMORY_TEST: 1
|
||||
|
||||
- name: Publish coverage
|
||||
if: matrix.coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
111
.github/workflows/package.yml
vendored
111
.github/workflows/package.yml
vendored
@@ -1,111 +0,0 @@
|
||||
name: Packages
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
build-deb:
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
distro:
|
||||
# - debian-unstable
|
||||
# - debian-trixie
|
||||
# - debian-bookworm
|
||||
# - debian-bullseye
|
||||
- ubuntu-noble
|
||||
- ubuntu-jammy
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# Determine which PPA we should upload to
|
||||
- name: PPA
|
||||
id: ppa
|
||||
run: |
|
||||
if [[ $REF == refs/tags* ]]
|
||||
then
|
||||
echo "ppa=kiwixteam/release" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "ppa=kiwixteam/dev" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
env:
|
||||
REF: ${{ github.ref }}
|
||||
|
||||
- uses: legoktm/gh-action-auto-dch@main
|
||||
with:
|
||||
fullname: Kiwix builder
|
||||
email: release+launchpad@kiwix.org
|
||||
distro: ${{ matrix.distro }}
|
||||
|
||||
# - uses: legoktm/gh-action-build-deb@debian-unstable
|
||||
# if: matrix.distro == 'debian-unstable'
|
||||
# name: Build package for debian-unstable
|
||||
# id: build-debian-unstable
|
||||
# with:
|
||||
# args: --no-sign
|
||||
#
|
||||
# - uses: legoktm/gh-action-build-deb@b47978ba8498dc8b8153cc3b5f99a5fc1afa5de1 # pin@debian-trixie
|
||||
# if: matrix.distro == 'debian-trixie'
|
||||
# name: Build package for debian-trixie
|
||||
# id: build-debian-trixie
|
||||
# with:
|
||||
# args: --no-sign
|
||||
#
|
||||
# - uses: legoktm/gh-action-build-deb@1f4e86a6bb34aaad388167eaf5eb85d553935336 # pin@debian-bookworm
|
||||
# if: matrix.distro == 'debian-bookworm'
|
||||
# name: Build package for debian-bookworm
|
||||
# id: build-debian-bookworm
|
||||
# with:
|
||||
# args: --no-sign
|
||||
#
|
||||
# - uses: legoktm/gh-action-build-deb@084b4263209252ec80a75d2c78a586192c17f18d # pin@debian-bullseye
|
||||
# if: matrix.distro == 'debian-bullseye'
|
||||
# name: Build package for debian-bullseye
|
||||
# id: build-debian-bullseye
|
||||
# with:
|
||||
# args: --no-sign
|
||||
|
||||
- uses: legoktm/gh-action-build-deb@9114a536498b65c40b932209b9833aa942bf108d # pin@ubuntu-noble
|
||||
if: matrix.distro == 'ubuntu-noble'
|
||||
name: Build package for ubuntu-noble
|
||||
id: build-ubuntu-noble
|
||||
with:
|
||||
args: --no-sign
|
||||
ppa: ${{ steps.ppa.outputs.ppa }}
|
||||
|
||||
- uses: legoktm/gh-action-build-deb@ubuntu-jammy
|
||||
if: matrix.distro == 'ubuntu-jammy'
|
||||
name: Build package for ubuntu-jammy
|
||||
id: build-ubuntu-jammy
|
||||
with:
|
||||
args: --no-sign
|
||||
ppa: ${{ steps.ppa.outputs.ppa }}
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Packages for ${{ matrix.distro }}
|
||||
path: output
|
||||
|
||||
- uses: legoktm/gh-action-dput@main
|
||||
name: Upload dev package
|
||||
# Only upload on pushes to main
|
||||
if: github.event_name == 'push' && github.event.ref == 'refs/heads/main' && startswith(matrix.distro, 'ubuntu-')
|
||||
with:
|
||||
gpg_key: ${{ secrets.LAUNCHPAD_GPG }}
|
||||
repository: ppa:kiwixteam/dev
|
||||
packages: output/*_source.changes
|
||||
|
||||
- uses: legoktm/gh-action-dput@main
|
||||
name: Upload release package
|
||||
if: github.event_name == 'release' && startswith(matrix.distro, 'ubuntu-')
|
||||
with:
|
||||
gpg_key: ${{ secrets.LAUNCHPAD_GPG }}
|
||||
repository: ppa:kiwixteam/release
|
||||
packages: output/*_source.changes
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -1,9 +0,0 @@
|
||||
.idea/
|
||||
*.swp
|
||||
subprojects/googletest-release*
|
||||
*.class
|
||||
build/
|
||||
.vscode/
|
||||
builddir/
|
||||
.cache/
|
||||
.clangd/
|
||||
@@ -1,21 +0,0 @@
|
||||
# Read the Docs configuration file
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
# Required
|
||||
version: 2
|
||||
|
||||
# Set the version of Python and other tools you might need
|
||||
build:
|
||||
os: ubuntu-22.04
|
||||
tools:
|
||||
python: "3.11"
|
||||
|
||||
# Build documentation in the docs/ directory with Sphinx
|
||||
sphinx:
|
||||
configuration: docs/conf.py
|
||||
|
||||
# We recommend specifying your dependencies to enable reproducible builds:
|
||||
# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
|
||||
python:
|
||||
install:
|
||||
- requirements: docs/requirements.txt
|
||||
13
.travis.yml
Normal file
13
.travis.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
language: cpp
|
||||
dist: trusty
|
||||
sudo: required
|
||||
cache: ccache
|
||||
install: travis/install_deps.sh
|
||||
script: travis/compile.sh
|
||||
env:
|
||||
- PLATFORM="native_static"
|
||||
- PLATFORM="native_dyn"
|
||||
- PLATFORM="win32_static"
|
||||
- PLATFORM="win32_dyn"
|
||||
- PLATFORM="android_arm"
|
||||
- PLATFORM="android_arm64"
|
||||
4
COPYING
4
COPYING
@@ -77,7 +77,7 @@ modification follow.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
@@ -510,7 +510,7 @@ actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
|
||||
679
ChangeLog
679
ChangeLog
@@ -1,682 +1,3 @@
|
||||
libkiwix 14.1.1
|
||||
===============
|
||||
|
||||
* Server:
|
||||
- Fix regression for kiwix-serve --nosearchbar (@veloman-yunkan #1250)
|
||||
- Avoid results content interpretation... crash in fulltext search (@vighnesh-sawant #1241)
|
||||
- Fix for intermittent /content/blank.html errors (@veloman-yunkan #1249)
|
||||
|
||||
libkiwix 14.1.0
|
||||
===============
|
||||
|
||||
* Server:
|
||||
- Viewer detects & tracks intrapage navigation anchors too (@veloman-yunkan #1213)
|
||||
- Add support for catalog only mode (@veloman-yunkan #1219)
|
||||
- Add API which returns server access url (@vighnesh-sawant #1234)
|
||||
- Fix chrome searchbar placeholder text overflow (@aditii2712 #1185)
|
||||
- Fix magnet link queryStyring (@rgaudin #1160)
|
||||
- Improve chrome printing stylesheet (@kelson42 #1202)
|
||||
- Default white background (@kelson42 #1205)
|
||||
|
||||
* Other:
|
||||
- Switched to the new libzim illustrations API (@veloman-yunkan #1226)
|
||||
- Stop building Windows with DEBUG symbols in CI (@kelson42 #1165)
|
||||
- Update many things in the CI/CD (@kelson42 #1203 #1194 #1209 #1207 #1235)
|
||||
- Requires now libzim 9.4.0 (@kelson42 #1231)
|
||||
- Fix compilation for FreeBSD (@OICe2 #1173 #1174)
|
||||
- Wait up to 1s to let aria2c to start before complaining (@kelson42 #1169)
|
||||
|
||||
libkiwix 14.0.0
|
||||
===============
|
||||
|
||||
* Server:
|
||||
- Support of IPv6 (@veloman-yunkan @aryanA101a #1074 #1093)
|
||||
- Better public IP configuration/detection (@sgourdas #1132)
|
||||
- Fix API errors in catalog searches if Xapian keyword in used (@veloman-yunkan #1137)
|
||||
- Clearly define which Web browsers are supported (@kelson42 @rgaudin @jaifroid @benoit74 #1132)
|
||||
- Improve welcome page download buttons (@veloman-yunkan #1094)
|
||||
- Better handling of external (non-HTTP) links (@veloman-yunkan #1123)
|
||||
- Fix book illustration size on welcome page to 48x48 pixels (@veloman-yunkan #1127)
|
||||
- Remove "Multiple Languages" in language filter (@veloman-yunkan #1098)
|
||||
- Stop transforming tags casing (@kelson42 @veloman-yunkan #1079 #1121)
|
||||
- ZIM file size consistently advertised in MiB (@harsha-mangena #1132)
|
||||
- Few new supported languages in the filter (@kelson42 #1080)
|
||||
- Improve accesskeys (@kelson42 #1075)
|
||||
- Add OpenSearch <link> to head of pages (@kelson42 #1070)
|
||||
* Compilation/Packaging:
|
||||
- Multiple fixes around deb packaging (@kelson42 #1108 #1114 #1135)
|
||||
- Generating of libkiwix.pc via Meson (@veloman-yunkan #1133)
|
||||
- Native Windows CI/CD (@mgautierfr @kelson42 #1113 #1125)
|
||||
- Better check (maximum) libzim version (@kelson42 #1124)
|
||||
- Multiple automated tests improvements (@veloman-yunkan #1068 #1067)
|
||||
* Other:
|
||||
- Deleted supported env. variable `$KIWIX_DATA_DIR` and `kiwix::getDataDirectory()` (@sgourdas #1107)
|
||||
- New string slugification for filenames (@shaopenglin #1105)
|
||||
- Multiple improvements around aria2c download mgmt. (@veloman-yunkan #1097)
|
||||
|
||||
libkiwix 13.1.0
|
||||
===============
|
||||
|
||||
* Server:
|
||||
- Properly translated error pages (@veloman-yunkan #1032)
|
||||
- Properly translated search result page (@veloman-yunkan #1046)
|
||||
- Default UI language is resolved in frontend (@veloman-yunkan #1044)
|
||||
- Better support of older Web browsers by polyfilling replaceAll() (@veloman-yunkan #1054)
|
||||
* New API to migrate bookmarks between books (@mgautierfr #1043)
|
||||
* Fixed compilation on Haiku OS (@Begasus #1048)
|
||||
|
||||
libkiwix 13.0.0
|
||||
===============
|
||||
|
||||
* Server:
|
||||
- Improved look & feel of kiwix-serve UI (@veloman-yunkan #917 #1021)
|
||||
- Increase tolerance to malformed (control characters) ZIM entry titles (@veloman-yunkan #1023)
|
||||
- API allowing to filter many categories at once (@juuz0 #974)
|
||||
- Cookie-less user language control (@veloman-yumkan #997)
|
||||
- Hack to fix Mirrorbrain based broken magnet URLs (@rgaudin #1001)
|
||||
* Fix handling of books with 'Name' metadata with dots (@mgautier #1016)
|
||||
* New method beautifyFileSize() to provide nice-looking book sizes (@vuuz0 #971)
|
||||
* Fix a few missing includes (@mgautierfr #978)
|
||||
* New functions to read - kiwix-serve - languages and categories streams (@juuz0 #967)
|
||||
* Add support of Fon language (@kelson42 #1013)
|
||||
* C++17 code base compliancy (@mgautierfr #996)
|
||||
* Use everywhere std::shared_ptr in place of raw pointer (@mgautierfr #991)
|
||||
* Do not use [[nodiscard]] attribute on compiler not supporting it (@mgautierfr #1003)
|
||||
* Add a non minified version of autoComplete.js (@mgautierfr #1008)
|
||||
* Multiple CI/CD improvements (@kelson42 #982)
|
||||
|
||||
libkiwix 12.1.0
|
||||
===============
|
||||
|
||||
* Server:
|
||||
- Introduce a `/nojs` endpoint to browse catalog and zim files with a browser without js (@juuz0 #897)
|
||||
- Translate the viewer (@veloman-yunkan #871 #846)
|
||||
- Display `mul` on tile when zim is multi-languages (@juuz0 #934)
|
||||
- Suggestion links point to the `/content` endpoint (@veloman-yunkan #862)
|
||||
- Correctly compress web fonts in http answers (@kelson42 #856)
|
||||
- Correctly encode link in suggestions (@veloman-yunkan #859 #860 #963)
|
||||
- Correctly encode url redirection (@veloman-yunkan #866 #890)
|
||||
- Properly handle user language, through cookies and http headers (@veloman-yunkan #849 #869)
|
||||
- Fix url encoding (@veloman-yunkan #870)
|
||||
- Fix viewer for viewer for SeaMonkey (@veloman-yunkan #887)
|
||||
- Make the downloader threadsafe (@mgautierfr #886)
|
||||
- Add RSS feed in the main page (pointing to the catalog) (@juuz0 #882 #920)
|
||||
- Correctly set the mimetype for json and ico (@veloman-yunkan #892)
|
||||
- `count=-1` correspond to unlimited count (instead of 0) (@veloman-yunkan #894)
|
||||
- Keep the navigation bar on top (@juuz0 #896)
|
||||
- Make the viewer's iframe "safe" (@veloman-yunkan #906 #930)
|
||||
- Correctly escape search link in XML Opds output (@veloman-yunkan #936)
|
||||
- Store values needed for the viewer js in the url fragment instead of the query string (@juuz0 #907)
|
||||
- Get rid of legacy OPDS API usage in the viewer (@veloman-yunkan #939)
|
||||
- Fix charset encoding declaration in OPDS response MIME types (@veloman-yunkan #942)
|
||||
- Fix PDF in the viewer (@veloman-yunkan #940)
|
||||
- Fix external links handling in the viewer (@veloman-yunkan #959)
|
||||
- Add tests of searching with accents (@mgautierfs #954)
|
||||
* Fix handling of missing illustration in the book (@veloman-yunkan #961)
|
||||
* Add support for multi languages zim files (@veloman-yunkan #904)
|
||||
* Fix includes for openbsd (@bentley #949)
|
||||
* Fix pathes in git to allow git clone on Windows (@adamlamar #868)
|
||||
* Switch to `main` as principal branch (instead of `master`) (@kelson42)
|
||||
* Remove libkiwix android publisher from the repository (@kelson42 #884)
|
||||
* Various fixes of meson and CI. (@mgautierfr @kelson42)
|
||||
|
||||
libkiwix 12.0.0
|
||||
===============
|
||||
|
||||
* [API Break] Remove wrapper around libzim (@mgautierfr #789)
|
||||
* Allow kiwix-serve to use custom resource files (@veloman-yunkan #779)
|
||||
* Properly handle searchProtocolPrefix when rendering search result (@veloman-yunkan #823)
|
||||
* Prevent search on multi language content (@veloman-yunkan #838)
|
||||
* Use new `zim::Archive::getMediaCount` from libzim (@mgautierfr #836)
|
||||
* Catalog:
|
||||
- Include tags in free text catalog search (@veloman-yunkan #802)
|
||||
- Illustration's url is based on book's uuid (@veloman-yunkan #804)
|
||||
- Cleanup of the opds-dumper (@veloman-yunkan #829)
|
||||
- Allow filtering of catalog content using multiple languages (@veloman-yunkan #841)
|
||||
- Make opds-dumper respect the namemapper (@mgautierfr #837)
|
||||
* Server:
|
||||
- Correctly handle `\` in suggestion json generation (@veloman-yunkan #843)
|
||||
- Better http caching (@veloman-yunkan #833)
|
||||
- Make `/suggest` endpoint thread-safe (@veloman-yunkan #834)
|
||||
- Better redirection of main page (@veloman-yunkan #827)
|
||||
- Remove jquery (@mgautierfr @juuz0 #796)
|
||||
- Better Viewer of zim content :
|
||||
. Introduce `/content` endpoints (@veloman-yunkan #806)
|
||||
. Switch to iframe based content viewer (@veloman-yunkan #716)
|
||||
- Optimised design of the welcome page:
|
||||
. Alignement (@juuz0 @kelson42 #786)
|
||||
. Exit download modal on pressing escape key (@juzz0 #800)
|
||||
. Add favicon for different devices (@juzz0 #805)
|
||||
. Fix auto hidding of the toolbar (@veloman-yunkan #821)
|
||||
. Allow user to filter books by tags in the front page (@juuz0 #711)
|
||||
* CI :
|
||||
- Trigger CI on pull_request (@kelson42 #791)
|
||||
- Drop Ubuntu Impish packaging (@legoktm #825)
|
||||
- Add Ubuntu Kinetic packaging (@legoktm #801)
|
||||
* Testing:
|
||||
- Test ICULanguageInfo (@veloman-yunkan #795)
|
||||
- Introduce fake `test` language to test i18n (@veloman-yunkan #848)
|
||||
* Fix documentation (@kelson42 #816)
|
||||
* Udpate translation (#787 #839 #847)
|
||||
|
||||
libkiwix 11.0.0
|
||||
===============
|
||||
|
||||
* [server] Add support for internationalization (@veloman-yunkan #679)
|
||||
* [server] Use gzip compression instead of deflat (mgautierfr #757)
|
||||
* [server] Version the static resources. This allow better invalidating
|
||||
browser cache when resources are changed (@veloman-yunkan #712)
|
||||
* [server|front] Use integer to query the host for page length (@juuz0 #772)
|
||||
* [server] Improve multizim search API:
|
||||
- Improvement of the cache system
|
||||
- Better API to select on which books to search in.
|
||||
- SysAdmin is now able to limit the number of book we search in for a multizim search
|
||||
* [server] Introduce a opensearch API for multizim fulltext search
|
||||
* [wrapper] Remove java wrapper
|
||||
* Testing:
|
||||
- Testing of search result pages content (@veloman-yunkan #765)
|
||||
- Better testing structure of xml search result (@veloman-yunkan #780)
|
||||
|
||||
libkiwix 10.1.1
|
||||
===============
|
||||
|
||||
* Correctly detect the number of article for older zims (<=6) (@mgautier #743)
|
||||
* [server] Fix fulltext search (@mgautierfr #724)
|
||||
* [server][internal] New way to build Error message (@veloman-yunkan #732 #738 #744)
|
||||
* Fix CI (@mgautierfr #736)
|
||||
|
||||
libkiwix 10.1.0
|
||||
===============
|
||||
|
||||
This release is an important one as it fixes a Xss vulnerability introduced
|
||||
in libkiwix 10.0.0
|
||||
|
||||
* [SECURITY] Fix a Xss attack vulnerability (introduced in 10.0.0) (@juuz0 #721)
|
||||
* [server] Add a option to set a limit on the number of connexion per IP (@kelson42 #700)
|
||||
* [server] Do not display a lang tag in the UI if the book has no language (@juuz0 #706)
|
||||
* [server] Add the book title associated to a search results (@thavelick #705, @mgautierfr #718)
|
||||
* Add `dc:issued` to opds output stream (@veloman-yunkan #715)
|
||||
* Add handling of several languages not provided by ICU (@juuz0 #701)
|
||||
* [server] Add a caching system for search and suggestion (@maneeshpm #620)
|
||||
* Fix cross-compilation (@kelson42 #703)
|
||||
* Add unit-testing of suggestions and error pages (@veloman-yunkan #709 #710 #727)
|
||||
* Better testing system of html response (@veloman-yunkan #725)
|
||||
|
||||
libkiwix 10.0.1
|
||||
===============
|
||||
|
||||
* [server] The catalog search interpret `count=0` as no limit.
|
||||
This was the case for a long time. This was changed unintentionally
|
||||
(@veloman-yunkan #686)
|
||||
* [server] Correctly generere a human friendly title in the server frontend.
|
||||
(@juuz0 #687, @kelson42 #689)
|
||||
* [server] Fix download button if there is no url do download from.
|
||||
(@juuz0 #691)
|
||||
* Add non-minified isotope.pkdg.js
|
||||
Needed for debian packaging as we need the source and minified version is
|
||||
not the source (@legoktm #693)
|
||||
* [server] Add a tooltip with the full language for the lang tag.
|
||||
* CI fixes (@kelson42 @legoktm)
|
||||
|
||||
libkiwix 10.0.0
|
||||
===============
|
||||
|
||||
This release is huge release.
|
||||
The project has been renamed to libkiwix, it is more coherent with the library name.
|
||||
|
||||
* Server front page :
|
||||
- Use js in the front page to display the available book,
|
||||
using the OPDS stream as source. The front page is now populated only with
|
||||
the visible books and user can search for books. (@MananJethwany #530, #541, #534)
|
||||
(@kelson42 #628)
|
||||
- Revamp css (@MananJethwany #559)
|
||||
- Correctly Convert 3iso language code to 2iso (@juuz0 #672)
|
||||
|
||||
* Server suggestions search :
|
||||
- Add pagination for suggestion search (@maneeshpm #591)
|
||||
- Fix suggestion system (@MananJethwany #498)
|
||||
- Provide the kind and path (when adapted) to the suggestion answer (@MananJethwany #464)
|
||||
- The displayed suggestion have now highligth on the searched terms (@maneeshpm #505)
|
||||
- Properly handle html encoding of suggestions (@veloman-yunkan #458)
|
||||
|
||||
* Server improvements :
|
||||
- Remove meta endpoints (@mgautier #669)
|
||||
- Add raw endpoints to get the raw content of a zim (@mgautierfr #646)
|
||||
- Add details on 404 error pages (@soumyankar #490)
|
||||
- Fix headbar insertion when `<head>` tag has attributes (@kelson42 #440)
|
||||
- Better headbar insertion (after charset definition) (@kelson42 #442)
|
||||
|
||||
* New OPDS Stream v2 :
|
||||
- Add a list of categories (@veloman-yunkan)
|
||||
- Support for partial entries (@veloman-yunkan #602)
|
||||
- Support multiple icons size in the OPDS stream (@veloman-yunkan #577 #630)
|
||||
- Add language endpoint to catalog (@veloman-yunkan #553)
|
||||
- Add illustration API to get the illustration of a book (@mgautierfr #645)
|
||||
- OPDS search can now filter books by category (@veloman-yunkan #459)
|
||||
|
||||
* Library improvements :
|
||||
- Allow the libray to be live reloaded when the library.xml changes (@veloman-yunkan #636)
|
||||
- Properly handle removing of book from the library (@veloman-yunkan #485)
|
||||
- Use xapian to search for books in the library (@veloman-yunkan #460, #488)
|
||||
|
||||
* Added methods/functions :
|
||||
- Fix `fileExist` and introduce `fileReadable` (@juuz0 #668)
|
||||
- Add `getVersions` and `printVersions` functions (@kelson42 #665)
|
||||
- Add `getNetworkInterfaces()` and `getBestPublicIP()` functions (@juuz0 #622)
|
||||
- Add `get_zimid()` method to the search result (@maneeshpm #510)
|
||||
|
||||
* Various improvements :
|
||||
- Better secret value for aria2c rpc (@juuz0 #666)
|
||||
- Avoid duplicated Archive/Reader in the Searcher (@veloman-yunkan #648)
|
||||
- Add basic documentation (@mgautierfr #640)
|
||||
- Do not use Reader internally (@maneeshpm #536 #576)
|
||||
- Remove dependency headers from our public headers (@mgautierfr #574)
|
||||
- Downloader now don't write metalink on the filesystem (@kelson42 #502)
|
||||
- Support opening a zim file using a fd (@veloman-yukan #429)
|
||||
- Use C++11 std::thread instead of pthread (@mgautierfr #445)
|
||||
- [READER] Do not crash if zim file has no `Counter` metadata (@mgautierfr #449)
|
||||
- Ensure libzim dependency is compiled with xapian (@mgautierfr #434)
|
||||
- Support video and audio mimetype in `getMediaCount` (@kelson42 #439)
|
||||
- Better parsing of the counterMap (@kelson42 #437)
|
||||
- Adapt libkiwix to libzim 7.0.0 (@mgautierfr #428)
|
||||
- Remove deprecated methods (@mgautierfr)
|
||||
- CI: Build package for Ubuntu Hirsute, Impish and Jammy (@legoktm #431 #568) and remove Groovy
|
||||
- Fix compilation for FreeBSD (@swills g#432)
|
||||
- Many fixes and improvement (@MananJethwany, @maneeshpm, @veloman-yunkan, @mgautierfr)
|
||||
|
||||
kiwix-lib 9.4.1
|
||||
===============
|
||||
|
||||
* Fix `M/Counter` parsing.
|
||||
* [SERVER] Adjust body padding-top for taskbar
|
||||
* Fix potential crash when stoping a server not started.
|
||||
* Various fix in build system and the CI.
|
||||
|
||||
kiwix-lib 9.4.0
|
||||
===============
|
||||
|
||||
* [SERVER] Make the headers handling case insensitive.
|
||||
* [SERVER] Make server answer 204 http status code for empty search
|
||||
* [PACKAGING] Made CI build deb packages.
|
||||
* [SERVER] Add a way to prevent taskbar and external link bloquer at article
|
||||
level.
|
||||
* Fix meson file to be compatible with meson 0.45
|
||||
* [SERVER] Update search requests to use pageStart/pageLength instead of
|
||||
pageStart/pageEnd arguments.
|
||||
* [SERVER] Set a fixed favicon size in the main page.
|
||||
* [SERVER] Refactor the response system code to better handling future new
|
||||
libzim api.
|
||||
* Fix segmentation fault around exchange with aria2 process making
|
||||
kiwix-desktop crash at exit.
|
||||
|
||||
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
|
||||
===============
|
||||
|
||||
* Improve a few compilation scripts
|
||||
|
||||
kiwix-lib 8.2.1
|
||||
===============
|
||||
|
||||
* Reintroduce kiwix-serve taskbar
|
||||
|
||||
kiwix-lib 8.2.0
|
||||
===============
|
||||
|
||||
* More debug information if aria2c command fails
|
||||
* Allow to set kiwix-serve port
|
||||
* Better (dead) bookmarks mgmt
|
||||
|
||||
kiwix-lib 8.1.0
|
||||
===============
|
||||
|
||||
* Fix pathTools manipulation.
|
||||
* Add missing implementation of getArticleCount and getMediaCount on android.
|
||||
* Correctly convert windows path to utf8.
|
||||
* Add code coverage in the CI
|
||||
|
||||
kiwix-lib 8.0.1
|
||||
===============
|
||||
|
||||
* Fix join function
|
||||
|
||||
kiwix-lib 8.0.0
|
||||
===============
|
||||
|
||||
* Add new methods to get all (and new) metadata from the zim file.
|
||||
* Add methods to get the value of a specific tag.
|
||||
* [API Change] Convert tags value to the new convention.
|
||||
* [API Change] Rename `getMatatag` method to `getMetadata`
|
||||
* [ABI Change] Correctly detect executable path in appimage.
|
||||
|
||||
kiwix-lib 7.0.0
|
||||
===============
|
||||
|
||||
* [API break] Add a argument to kiwix-serve to specify the library to use.
|
||||
|
||||
kiwix-lib 6.0.4
|
||||
===============
|
||||
|
||||
* Fix HTML rendering of the search result if there is no result.
|
||||
* Do not crash at html rendering if request ask for 0 results (start == end)
|
||||
* Correctly find the executable path if we are using AppImage
|
||||
|
||||
kiwix-lib 6.0.3
|
||||
===============
|
||||
|
||||
* force one column suggestion in kiwix-serve suggestions
|
||||
* fix fulltext search link in suggestions
|
||||
* UI fixes in kiwix-serve rendering
|
||||
|
||||
kiwix-lib 6.0.2
|
||||
===============
|
||||
|
||||
* Correctly set the groupId in the pom file.
|
||||
|
||||
kiwix-lib 6.0.1
|
||||
===============
|
||||
|
||||
* Generate the pom file for android/maven
|
||||
|
||||
kiwix-lib 6.0.0
|
||||
===============
|
||||
|
||||
* Move the server code in kiwix-lib (from kiwix-serve).
|
||||
* Add unit test on regex functions.
|
||||
* Fix computerAbsolutePath (thread safe, memory leak).
|
||||
* Correctly set the book's path as valid if we construct the book from a
|
||||
reader.
|
||||
* [JNI] Add a method to know if a article is a redirection.
|
||||
* Do not embed the gtest dependency.
|
||||
* [JNI] Add a constructor to JNIKiwixString.
|
||||
* Change order of search of the favicon urls.
|
||||
* Clean a lot of unecessary includes in headers. (potential "API break")
|
||||
|
||||
kiwix-lib 5.2.0
|
||||
===============
|
||||
|
||||
* kiwix-serve integration (as a seperated process).
|
||||
* Fix crash in the suggestion search.
|
||||
* Better API to filter the library books.
|
||||
* New kiwix-lib application for android. (.aar)
|
||||
* Use ReLinker to link with libkiwix.so in android.
|
||||
* Correctly set the verbosity of zim search.
|
||||
|
||||
kiwix-lib 5.1.0
|
||||
===============
|
||||
|
||||
* Add function to pause, resume and stop downloads.
|
||||
* Add zim's tags in the opds stream.
|
||||
* Addapt to new libzim 5.0.0 API.
|
||||
|
||||
kiwix-lib 5.0.0
|
||||
===============
|
||||
|
||||
* Remove error message when trying to open an wrong zim file.
|
||||
* Rewrite `makeTmpDirectory` to not use uuid functions on windows.
|
||||
* [API break] Remove `getNetworkInterfaces` and `getBestPublicIp`.
|
||||
* Remove rpath
|
||||
* Detect infinite (and too long) redirect loops.
|
||||
|
||||
kiwix-lib 4.1.0
|
||||
===============
|
||||
|
||||
* Allow the library to be filtered by tags.
|
||||
* Fix language mapping.
|
||||
* Update README about mustache dependency.
|
||||
|
||||
kiwix-lib 4.0.1
|
||||
===============
|
||||
|
||||
* Fix "maybe uninitialize variable" issue.
|
||||
* Ensure path are stored correctly (absolute path) in the library.
|
||||
* [CI] Use the new deps archive xz
|
||||
|
||||
kiwix-lib 4.0.0
|
||||
===============
|
||||
|
||||
* [API break] Remove support for external index.
|
||||
* Move to the mustache templating system instead of ctpp2.
|
||||
* Make meson.build works for meson>=0.43.0
|
||||
* [API break] Move the basic tools from the `common` directory to `tools`.
|
||||
|
||||
kiwix-lib 3.1.1
|
||||
===============
|
||||
|
||||
* The OPDS feed book's date must be the date of the book, not the date of the
|
||||
feed generation.
|
||||
* Convert the standard opds date to our format (YYYY-MM-DD)
|
||||
* Remove duplicate language attribute in the libxml dumper.
|
||||
* Create the datadirectory to not fail to write a file in a non-existent
|
||||
directory
|
||||
|
||||
kiwix-lib 3.1.0
|
||||
===============
|
||||
|
||||
* Add a method to get the favicon url of book (if available).
|
||||
* Move dump code of library.xml in a specific class.
|
||||
* Add a first support to bookmarks
|
||||
|
||||
kiwix-lib 3.0.3
|
||||
===============
|
||||
|
||||
* Add the 'en' language to the mapping alpha2-code ('en') to alpha3-code
|
||||
('eng').
|
||||
* Correctly write the 'ArticleCount' and 'MediaCount' in the library.xml.
|
||||
* Correctly fill the book size for the zim file size.
|
||||
* Fix launch of aria2c.
|
||||
|
||||
kiwix-lib 3.0.2
|
||||
===============
|
||||
|
||||
* Use the correct path separator when computing relativePath on Windows.
|
||||
|
||||
kiwix-lib 3.0.1
|
||||
===============
|
||||
|
||||
* Small fix about parsing the opdsStream.
|
||||
|
||||
kiwix-lib 3.0.0
|
||||
===============
|
||||
|
||||
* Change the downloader to use aria2 using a separated process (with rpc)
|
||||
instead of using the libaria2. This simplify a lot the link process to
|
||||
libaria2 on Windows.
|
||||
- kiwix-lib doesn't depend on libaria2 anymore.
|
||||
- kiwix-lib now depends on libcurl.
|
||||
* [API break] Library class API has been updated :
|
||||
- Books are referenced by id, not index. A lot of methods have been
|
||||
updated this way.
|
||||
- Books "list" is now private.
|
||||
- There is no more "current" book.
|
||||
- listBooksIds's filters have been updated.
|
||||
* [API break] Book class API has been updated :
|
||||
- Move the definition of Book in `book.h`.
|
||||
- Use getter/setter methods instead public members.
|
||||
- Size (getSize/setSize) is now returned in bytes, not kB.
|
||||
- Dependending of how the book has been initialized (opdsfeed), the
|
||||
faviconUrl may be stored in the book, the favicon being downloaded when
|
||||
using `getFavicon`.
|
||||
- The path (and indexPath) are always absolute path.
|
||||
- Book has now a downloadId, corresponding to the aria2 download id (if
|
||||
exists)
|
||||
* [API break] Manager class API has been updated :
|
||||
- The manager is mainly use to fill a Libray from a "library.xml" file or
|
||||
opds feed. Other operations (has removeBookById, setBookPath, filter, ...)
|
||||
have been removed.
|
||||
- The manager use a intermediate class (LibraryManipulator) to add book to
|
||||
the library. This dependency injection allow caller code to hook the add
|
||||
of a book to the library.
|
||||
- The manager work on a existing Library. It doesn't how a internal
|
||||
Library.
|
||||
* [API break] OpdsDumper class API has been updated :
|
||||
- dumpOPDSFeed method now take the list of bookIds to dump instead of
|
||||
dumping all books in the library.
|
||||
- OpdsDumper can now dump openSearch result information (total result
|
||||
count, start index, ...).
|
||||
* [API break] Common tools API has been updated :
|
||||
- `base64_encode` and `base64_decode` take std::string as arguments.
|
||||
- New `download` function in networkTools.h using libcurl.
|
||||
- New `getDataDirectory` function in pathTools.
|
||||
- Better `beautifyInteger` and `beautifyFileSize` functions.
|
||||
- New `nodeToString` function serializing a pugi::xml_node to a string.
|
||||
- New `converta2toa3` function to convert alpha2 language code to aplha3
|
||||
language code.
|
||||
|
||||
|
||||
kiwix-lib 2.0.2
|
||||
===============
|
||||
|
||||
* [Android] Forward c++ errors message de Java world.
|
||||
* Follow redirection of favicon.
|
||||
* Make aria2 dependency optional.
|
||||
* Inculde unistd.h only on unix platform.
|
||||
|
||||
kiwix-lib 2.0.1
|
||||
===============
|
||||
|
||||
* Fix parsing of url.
|
||||
* Remove unused static resources.
|
||||
* Correctly decode reserved characters in URLs.
|
||||
* Explicitly use icu namespace to allow use of packaged icu lib.
|
||||
|
||||
kiwix-lib 2.0.0
|
||||
===============
|
||||
|
||||
* Introduce a new API to retrive content from a reader.
|
||||
* Introduce the `Entry` class.
|
||||
* Reader's methods return an `Entry`.
|
||||
* Content and other information can be retrieved from the `Entry`.
|
||||
* Older Reader's methods are depreciated.
|
||||
* Add an `OPDSDumper` class to dump a whole `Library` as an OPDS feed.
|
||||
* Add a tool function to get the content of a file.
|
||||
* Add a tool function to create a tempory directory.
|
||||
* Add a `Downloader` class to download a file.
|
||||
* Allow the manager to populate a `Library` from an OPDS feed.
|
||||
* Try to locate libctpp2 in default system libdir and then fallback in 'lib'
|
||||
directory.
|
||||
* Build kiwix-lib setting RPATH.
|
||||
* Build kiwix-lib without warning (werror=true)
|
||||
* Build kiwix-lib on macos.
|
||||
|
||||
kiwix-lib 1.1.1
|
||||
===============
|
||||
|
||||
* Correct the name of kiwix-lib (from `kiwixlib`) in meson.build to generate
|
||||
dist archive with the correct name.
|
||||
* Libzim version need to be at least 3.2.0
|
||||
|
||||
kiwix-lib 1.1.0
|
||||
===============
|
||||
|
||||
* Allow for more than 70 search result per page in html results rendering
|
||||
(kiwix/kiwix-tools#92)
|
||||
* Add a small api to do geo queries.
|
||||
* Add multi-search support in the JNI (#67)
|
||||
* Add an API to get only one part of an article.
|
||||
* Add an API to get direct location of an article content in the zim file.
|
||||
* Improve urlencoding
|
||||
* Fix pagination in html results rendering.
|
||||
* Compile using gcc-5 on Travis.
|
||||
* Allow JNI to access search snippets.
|
||||
* JNI throw an exception instead of returning an invalid object if something
|
||||
goes wrong.
|
||||
* Add doctext documentation. (#116)
|
||||
* Various bug fixes.
|
||||
|
||||
kiwix-lib 1.0.0
|
||||
===============
|
||||
|
||||
|
||||
265
README.md
265
README.md
@@ -1,239 +1,120 @@
|
||||
Libkiwix
|
||||
========
|
||||
Kiwix library
|
||||
=============
|
||||
|
||||
The Libkiwix provides the [Kiwix](https://kiwix.org) software suite
|
||||
core. It contains the code shared by all Kiwix ports (Windows,
|
||||
GNU/Linux, macOS, Android, iOS, ...).
|
||||
|
||||
[](https://download.kiwix.org/release/libkiwix/)
|
||||
[](https://github.com/kiwix/libkiwix/wiki/Repology)
|
||||
[](https://github.com/kiwix/libkiwix/actions?query=branch%3Amain)
|
||||
[](https://libkiwix.readthedocs.org/en/latest/?badge=latest)
|
||||
[](https://www.codefactor.io/repository/github/kiwix/libkiwix)
|
||||
[](https://codecov.io/gh/kiwix/libkiwix)
|
||||
[](https://www.gnu.org/licenses/gpl-3.0)
|
||||
The Kiwix library provides the Kiwix software core. It contains the
|
||||
code shared by all Kiwix ports (Windows, Linux, OSX, Android, ...).
|
||||
|
||||
Disclaimer
|
||||
----------
|
||||
|
||||
This document assumes you have a little knowledge about software
|
||||
compilation. If you experience difficulties with the dependencies or
|
||||
with the Libkiwix compilation itself, we recommend to have a look to
|
||||
[kiwix-build](https://github.com/kiwix/kiwix-build).
|
||||
with the Kiwix libary compilation itself, we recommend to have a look
|
||||
to [kiwix-build](https://github.com/kiwix/kiwix-build).
|
||||
|
||||
Preamble
|
||||
--------
|
||||
|
||||
Although the Libkiwix can be (cross-)compiled on/for many systems, the
|
||||
following documentation explains how to do it on POSIX ones. It is
|
||||
primarily thought for GNU/Linux systems and has been tested on recent
|
||||
releases of Ubuntu and Fedora.
|
||||
Although the Kiwix library can be compiled/cross-compiled on/for many
|
||||
sytems, the following documentation explains how to do it on POSIX
|
||||
ones. It is primarly though for GNU/Linux systems and has been tested
|
||||
on recent releases of Ubuntu and Fedora.
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
The Libkiwix relies on many third party software libraries. They are
|
||||
prerequisites to the Libkiwix compilation. Following libraries need to
|
||||
be available:
|
||||
* [ICU](https://site.icu-project.org/) (package `libicu-dev` on Ubuntu)
|
||||
* [ZIM](https://openzim.org/) (package `libzim-dev` on Ubuntu)
|
||||
* [Pugixml](https://pugixml.org/) (package `libpugixml-dev` 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). Use Mustache version 4.1 or above.
|
||||
* [Libcurl](https://curl.se/libcurl) (`libcurl4-gnutls-dev`, `libcurl4-nss-dev` or `libcurl4-openssl-dev` on Ubuntu)
|
||||
* [Microhttpd](https://www.gnu.org/software/libmicrohttpd) (package `libmicrohttpd-dev` on Ubuntu)
|
||||
* [Zlib](https://zlib.net/) (package `zlib1g-dev` on Ubuntu)
|
||||
The Kiwix library relies on many third parts software libraries. They
|
||||
are prerequisites to the Kiwix library compilation. Following
|
||||
libraries need to be available:
|
||||
|
||||
To test the code:
|
||||
* [Google Test](https://github.com/google/googletest) (package `googletest` on Ubuntu)
|
||||
|
||||
The following dependency needs to be available at runtime:
|
||||
* [Aria2](https://aria2.github.io/) (package `aria2` on Ubuntu)
|
||||
* ICU ................................... http://site.icu-project.org/
|
||||
(package libicu-dev on Ubuntu)
|
||||
* ZIM ........................................ http://www.openzim.org/
|
||||
(package libzim-dev on Ubuntu)
|
||||
* Pugixml ........................................ http://pugixml.org/
|
||||
(package libpugixml-dev on Ubuntu)
|
||||
* ctpp2 ........................................ http://ctpp.havoc.ru/
|
||||
(package libctpp2-dev on Ubuntu)
|
||||
* Xapian ......................................... https://xapian.org/
|
||||
(package libxapian-dev on Ubuntu)
|
||||
|
||||
These dependencies may or may not be packaged by your operating
|
||||
system. They may also be packaged but only in an older version. The
|
||||
compilation script will tell you if one of them is missing or too old.
|
||||
In the worst case, you will have to download and compile bleeding edge
|
||||
In the worse case, you will have to download and compile bleeding edge
|
||||
version by hand.
|
||||
|
||||
If you want to install these dependencies locally, then use the
|
||||
`libkiwix` directory as install prefix.
|
||||
kiwix-lib directory as install prefix.
|
||||
|
||||
Environment
|
||||
If you compile ctpp2 from source and want to compile the Kiwix library
|
||||
statically then you will probably need to rename ctpp2 static library
|
||||
from ctpp2-st.a to ctpp2.a.
|
||||
|
||||
Environnement
|
||||
-------------
|
||||
|
||||
The Libkiwix builds using [Meson](https://mesonbuild.com/) version
|
||||
0.45 or higher. Meson relies itself on Ninja, pkg-config and few other
|
||||
The Kiwix library builds using [Meson](http://mesonbuild.com/) version
|
||||
0.34 or higher. Meson relies itself on Ninja, pkg-config and few other
|
||||
compilation tools.
|
||||
|
||||
Install first the few common compilation tools:
|
||||
* [Meson](https://mesonbuild.com/)
|
||||
* [Ninja](https://ninja-build.org/)
|
||||
* [pkg-config](https://www.freedesktop.org/wiki/Software/pkg-config/)
|
||||
* Automake
|
||||
* Libtool
|
||||
* Virtualenv
|
||||
* Pkg-config
|
||||
|
||||
These tools should be packaged if you use a cutting edge operating
|
||||
system. If not, have a look to the [Troubleshooting](#Troubleshooting)
|
||||
section.
|
||||
|
||||
Compilation
|
||||
-----------
|
||||
|
||||
Once all dependencies are installed, you can compile the Libkiwix
|
||||
with:
|
||||
```bash
|
||||
meson . build
|
||||
ninja -C build
|
||||
Then install Meson itself:
|
||||
```
|
||||
|
||||
By default, it will compile dynamic linked libraries. All binary files
|
||||
will be created in the `build` directory created automatically by
|
||||
Meson. If you want statically linked libraries, you can add
|
||||
`--default-library=static` option to the Meson command.
|
||||
|
||||
Depending of you system, `ninja` may be called `ninja-build`.
|
||||
|
||||
The android wrapper uses deprecated methods of libkiwix so it cannot be compiled
|
||||
with `werror=true` (the default). So you must pass `-Dwerror=false` to meson:
|
||||
|
||||
```bash
|
||||
meson . build -Dwrapper=android -Dwerror=false
|
||||
ninja -C build
|
||||
```
|
||||
|
||||
Static files compilation
|
||||
------------------------
|
||||
|
||||
Libkiwix has a few static files 'compiled' within the binary
|
||||
code. This is mostly Javascript/HTML/pictures necessary for the HTTP
|
||||
daemon.
|
||||
|
||||
These static files are available in the `static` directory and are
|
||||
compiled by custom Python code available in this repository `scripts`
|
||||
directory. This happens automatically at compilation time without any
|
||||
additional command to run.
|
||||
|
||||
To avoid HTTP caching issues, the URLs (to the static content) are
|
||||
appended with a `cacheid` parameter (this is called "cache
|
||||
busting"). This `cacheid` value derived from the
|
||||
[sha1sum](https://en.wikipedia.org/wiki/Sha1sum) of each targeted
|
||||
static file. As a consequence, each time you change a static file, the
|
||||
corresponding `cacheid` value will change.
|
||||
|
||||
To properly test this feature, this `cacheid` needs to be added
|
||||
manually to the automated tests and has to be commited. After
|
||||
modifying the needed static file, [run the automated
|
||||
tests](#Testing). They will fail, but the inspection of the testing
|
||||
log will give you the new `cacheid` value(s). Finally update
|
||||
`test/server.cpp` with the appropriate `cacheid` value(s) which have
|
||||
changed.
|
||||
|
||||
Testing
|
||||
-------
|
||||
|
||||
To run the automated tests:
|
||||
```bash
|
||||
cd build
|
||||
meson test
|
||||
```
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
If you want to install the Libkiwix and the headers you just have
|
||||
compiled on your system, here we go:
|
||||
```bash
|
||||
ninja -C build install
|
||||
```
|
||||
|
||||
You might need to run the command as root (or using `sudo`), depending
|
||||
where you want to install the libraries. After the installation
|
||||
succeeded, you may need to run `ldconfig` (as `root`).
|
||||
|
||||
Uninstallation
|
||||
--------------
|
||||
|
||||
If you want to uninstall the Kiwix library:
|
||||
```bash
|
||||
ninja -C build uninstall
|
||||
```
|
||||
|
||||
Like for the installation, you might need to run the command as `root`
|
||||
(or using `sudo`).
|
||||
|
||||
Custom Index Page
|
||||
-----------------
|
||||
|
||||
to use custom welcome page mention `customIndexPage` argument in `kiwix::internalServer()` or use `kiwix::server->setCustomIndexTemplate()`.
|
||||
(note - while using custom html file please mention all external links as absolute path.)
|
||||
|
||||
to create a HTML template with custom JS you need to have a look at various OPDS based endpoints as mentioned [here](https://wiki.kiwix.org/wiki/OPDS) to load books.
|
||||
|
||||
To use JS provided by kiwix-serve you can use the following template to start with ->
|
||||
|
||||
```
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title><-- Custom Tittle --></title>
|
||||
<script src="{{root}}/skin/isotope.pkgd.min.js" defer></script>
|
||||
<script src="{{root}}/skin/iso6391To3.js"></script>
|
||||
<script type="text/javascript" src="{{root}}/skin/index.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
- To get books listed using `index.js` add - `<div class="book__list"></div>` under body tag.
|
||||
- To get number of books listed add - `<h3 class="kiwixHomeBody__results"></h3>` under body tag.
|
||||
- To add language select box add - `<select id="languageFilter"></select>` under body tag.
|
||||
- To add category select box add - `<select id="categoryFilter"></select>` under body tag.
|
||||
- To add search box for books use following form -
|
||||
```
|
||||
<form id='kiwixSearchForm'>
|
||||
<input type="text" name="q" placeholder="Search" id="searchFilter" class='kiwixSearch filter'>
|
||||
<input type="submit" class="kiwixButton" value="Search"/>
|
||||
</form>
|
||||
```
|
||||
|
||||
|
||||
If you compile manually Libmicrohttpd, you might need to compile it
|
||||
without GNU TLS, a bug here will impeach further compilation
|
||||
otherwise.
|
||||
|
||||
If the compilation still fails, you might need to get a more recent
|
||||
version of a dependency than the one packaged by your Linux
|
||||
distribution. Try then with a source tarball distributed by the
|
||||
problematic upstream project or even directly from the source code
|
||||
repository.
|
||||
|
||||
Troubleshooting
|
||||
---------------
|
||||
|
||||
If you need to install Meson "manually":
|
||||
```bash
|
||||
virtualenv -p python3 ./ # Create virtualenv
|
||||
source bin/activate # Activate the virtualenv
|
||||
pip3 install meson # Install Meson
|
||||
pip install meson # Install Meson
|
||||
hash -r # Refresh bash paths
|
||||
```
|
||||
|
||||
If you need to install Ninja "manually":
|
||||
```bash
|
||||
Finally download and build Ninja locally:
|
||||
```
|
||||
git clone git://github.com/ninja-build/ninja.git
|
||||
cd ninja
|
||||
git checkout release
|
||||
./configure.py --bootstrap
|
||||
mkdir ../bin
|
||||
mkdir ../bin
|
||||
cp ninja ../bin
|
||||
cd ..
|
||||
```
|
||||
|
||||
Compilation
|
||||
-----------
|
||||
|
||||
Once all dependencies are installed, you can compile kiwix-lib with:
|
||||
```
|
||||
mkdir build
|
||||
meson . build
|
||||
cd build
|
||||
ninja
|
||||
```
|
||||
|
||||
By default, it will compile dynamic linked libraries. If you want
|
||||
statically linked libraries, you can add `--default-library=static`
|
||||
option to the Meson command.
|
||||
|
||||
Depending of you system, `ninja` may be called `ninja-build`.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
If you want to install the libraries you just have compiled on your
|
||||
system, here we go:
|
||||
|
||||
```
|
||||
ninja install
|
||||
cd ..
|
||||
```
|
||||
|
||||
You might need to run the command as root, depending where you want to
|
||||
install the libraries.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
[GPLv3](https://www.gnu.org/licenses/gpl-3.0) or later, see
|
||||
[COPYING](COPYING) for more details.
|
||||
GPLv3 or later, see COPYING for more details.
|
||||
|
||||
5
debian/changelog
vendored
5
debian/changelog
vendored
@@ -1,5 +0,0 @@
|
||||
libkiwix (0.0.0) unstable; urgency=medium
|
||||
|
||||
* Initial release
|
||||
|
||||
-- Kunal Mehta <legoktm@debian.org> Wed, 08 Jul 2020 18:12:32 -0700
|
||||
47
debian/control
vendored
47
debian/control
vendored
@@ -1,47 +0,0 @@
|
||||
Source: libkiwix
|
||||
Priority: optional
|
||||
Maintainer: Kiwix team <kiwix@kiwix.org>
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
meson,
|
||||
pkgconf,
|
||||
libzim-dev (>= 9.0), libzim-dev (<< 10.0),
|
||||
libcurl4-gnutls-dev,
|
||||
libicu-dev,
|
||||
libgtest-dev,
|
||||
libkainjow-mustache-dev,
|
||||
libmicrohttpd-dev,
|
||||
libpugixml-dev,
|
||||
zlib1g-dev
|
||||
Standards-Version: 4.5.0
|
||||
Section: libs
|
||||
Homepage: https://github.com/kiwix/libkiwix
|
||||
Rules-Requires-Root: no
|
||||
|
||||
Package: libkiwix-dev
|
||||
Section: libdevel
|
||||
Architecture: any
|
||||
Multi-Arch: same
|
||||
Depends: libkiwix14 (= ${binary:Version}), ${misc:Depends}, python3,
|
||||
libzim-dev (>= 9.0), libzim-dev (<< 10.0),
|
||||
libicu-dev,
|
||||
libpugixml-dev,
|
||||
libcurl4-gnutls-dev,
|
||||
libmicrohttpd-dev,
|
||||
zlib1g-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: libkiwix14
|
||||
Architecture: any
|
||||
Multi-Arch: same
|
||||
Depends: ${shlibs:Depends}, ${misc:Depends}, aria2
|
||||
Conflicts: libkiwix0, libkiwix3, libkiwix9, libkiwix10, libkiwix11, libkiwix12, libkiwix13
|
||||
Replaces: libkiwix0, libkiwix3, libkiwix9, libkiwix10, libkiwix11, libkiwix12, libkiwix13
|
||||
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
1
debian/copyright
vendored
@@ -1 +0,0 @@
|
||||
See COPYING in the repository root.
|
||||
4
debian/libkiwix-dev.install
vendored
4
debian/libkiwix-dev.install
vendored
@@ -1,4 +0,0 @@
|
||||
usr/include
|
||||
usr/lib/*/libkiwix.so
|
||||
usr/lib/*/pkgconfig
|
||||
usr/bin
|
||||
2
debian/libkiwix-dev.manpages
vendored
2
debian/libkiwix-dev.manpages
vendored
@@ -1,2 +0,0 @@
|
||||
usr/share/man/man1/kiwix-compile-resources.1*
|
||||
usr/share/man/man1/kiwix-compile-i18n.1*
|
||||
1
debian/libkiwix14.install
vendored
1
debian/libkiwix14.install
vendored
@@ -1 +0,0 @@
|
||||
usr/lib/*/libkiwix.so.*
|
||||
8
debian/rules
vendored
8
debian/rules
vendored
@@ -1,8 +0,0 @@
|
||||
#!/usr/bin/make -f
|
||||
export DEB_BUILD_MAINT_OPTIONS = hardening=+all
|
||||
|
||||
%:
|
||||
dh $@ --buildsystem=meson
|
||||
|
||||
override_dh_auto_test:
|
||||
dh_auto_test -- -t 3
|
||||
1
debian/source/format
vendored
1
debian/source/format
vendored
@@ -1 +0,0 @@
|
||||
3.0 (native)
|
||||
2
docs/.gitignore
vendored
2
docs/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
api
|
||||
xml
|
||||
68
docs/conf.py
68
docs/conf.py
@@ -1,68 +0,0 @@
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# This file only contains a selection of the most common options. For a full
|
||||
# list see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
# -- Path setup --------------------------------------------------------------
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#
|
||||
import os
|
||||
# import sys
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = 'libkiwix'
|
||||
copyright = '2022, libkiwix-team'
|
||||
author = 'libkiwix-team'
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'breathe',
|
||||
'exhale'
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This pattern also affects html_static_path and html_extra_path.
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
breathe_projects = {
|
||||
"libkiwix": "./xml"
|
||||
}
|
||||
breathe_default_project = 'libkiwix'
|
||||
|
||||
exhale_args = {
|
||||
"containmentFolder": "./api",
|
||||
"rootFileName": "ref_api.rst",
|
||||
"rootFileTitle": "Reference API",
|
||||
"doxygenStripFromPath":"..",
|
||||
"treeViewIsBootstrap": True,
|
||||
"createTreeView" : True,
|
||||
"exhaleExecutesDoxygen": True,
|
||||
"exhaleDoxygenStdin": "INPUT = ../include"
|
||||
}
|
||||
|
||||
primary_domain = 'cpp'
|
||||
|
||||
highlight_language = 'cpp'
|
||||
@@ -1,14 +0,0 @@
|
||||
.. libkiwix documentation master file, created by
|
||||
sphinx-quickstart on Fri Jul 24 15:40:50 2020.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Welcome to libkiwix's documentation!
|
||||
==================================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Contents:
|
||||
|
||||
usage
|
||||
api/ref_api
|
||||
@@ -1,7 +0,0 @@
|
||||
|
||||
sphinx = find_program('sphinx-build', native:true)
|
||||
|
||||
sphinx_target = run_target('doc',
|
||||
command: [sphinx, '-bhtml',
|
||||
meson.current_source_dir(),
|
||||
meson.current_build_dir()])
|
||||
@@ -1,3 +0,0 @@
|
||||
breathe
|
||||
exhale
|
||||
sphinx_rtd_theme
|
||||
@@ -1,15 +0,0 @@
|
||||
Libkiwix programming
|
||||
====================
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
libkiwix is written in C++. To use the library, you need the include files of libkiwix have
|
||||
to link against libzim.
|
||||
|
||||
Errors are handled with exceptions. When something goes wrong, libkiwix throws an error,
|
||||
which is always derived from std::exception.
|
||||
|
||||
All classes are defined in the namespace kiwix.
|
||||
|
||||
libkiwix is a set of tools to manage zim files and provide some common functionnality.
|
||||
36
format_code.sh
Executable file
36
format_code.sh
Executable file
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
files=(
|
||||
"include/library.h"
|
||||
"include/common/stringTools.h"
|
||||
"include/common/pathTools.h"
|
||||
"include/common/otherTools.h"
|
||||
"include/common/regexTools.h"
|
||||
"include/common/networkTools.h"
|
||||
"include/manager.h"
|
||||
"include/reader.h"
|
||||
"include/kiwix.h"
|
||||
"include/xapianSearcher.h"
|
||||
"include/searcher.h"
|
||||
"src/library.cpp"
|
||||
"src/android/kiwix.cpp"
|
||||
"src/android/org/kiwix/kiwixlib/JNIKiwixBool.java"
|
||||
"src/android/org/kiwix/kiwixlib/JNIKiwix.java"
|
||||
"src/android/org/kiwix/kiwixlib/JNIKiwixString.java"
|
||||
"src/android/org/kiwix/kiwixlib/JNIKiwixInt.java"
|
||||
"src/searcher.cpp"
|
||||
"src/common/pathTools.cpp"
|
||||
"src/common/regexTools.cpp"
|
||||
"src/common/otherTools.cpp"
|
||||
"src/common/networkTools.cpp"
|
||||
"src/common/stringTools.cpp"
|
||||
"src/xapianSearcher.cpp"
|
||||
"src/manager.cpp"
|
||||
"src/reader.cpp"
|
||||
)
|
||||
|
||||
for i in "${files[@]}"
|
||||
do
|
||||
echo $i
|
||||
clang-format -i -style=file $i
|
||||
done
|
||||
162
include/book.h
162
include/book.h
@@ -1,162 +0,0 @@
|
||||
/*
|
||||
* Copyright 2011 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef KIWIX_BOOK_H
|
||||
#define KIWIX_BOOK_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include "common.h"
|
||||
|
||||
namespace pugi {
|
||||
class xml_node;
|
||||
}
|
||||
|
||||
namespace zim {
|
||||
class Archive;
|
||||
}
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
class OPDSDumper;
|
||||
|
||||
/**
|
||||
* A class to store information about a book (a zim file)
|
||||
*/
|
||||
class Book
|
||||
{
|
||||
public: // types
|
||||
class Illustration
|
||||
{
|
||||
friend class Book;
|
||||
public:
|
||||
uint16_t width = 48;
|
||||
uint16_t height = 48;
|
||||
std::string mimeType;
|
||||
std::string url;
|
||||
|
||||
const std::string& getData() const;
|
||||
|
||||
private:
|
||||
mutable std::string data;
|
||||
mutable std::mutex mutex;
|
||||
};
|
||||
|
||||
typedef std::vector<std::shared_ptr<const Illustration>> Illustrations;
|
||||
|
||||
public: // functions
|
||||
Book();
|
||||
~Book();
|
||||
|
||||
bool update(const Book& other);
|
||||
void update(const zim::Archive& archive);
|
||||
void updateFromXml(const pugi::xml_node& node, const std::string& baseDir);
|
||||
void updateFromOpds(const pugi::xml_node& node, const std::string& urlHost);
|
||||
std::string getHumanReadableIdFromPath() const;
|
||||
|
||||
bool readOnly() const { return m_readOnly; }
|
||||
const std::string& getId() const { return m_id; }
|
||||
const std::string& getPath() const { return m_path; }
|
||||
bool isPathValid() const { return m_pathValid; }
|
||||
const std::string& getTitle() const { return m_title; }
|
||||
const std::string& getDescription() const { return m_description; }
|
||||
DEPRECATED const std::string& getLanguage() const { return m_language; }
|
||||
const std::string& getCommaSeparatedLanguages() const { return m_language; }
|
||||
const std::vector<std::string> getLanguages() const;
|
||||
const std::string& getCreator() const { return m_creator; }
|
||||
const std::string& getPublisher() const { return m_publisher; }
|
||||
const std::string& getDate() const { return m_date; }
|
||||
const std::string& getUrl() const { return m_url; }
|
||||
const std::string& getName() const { return m_name; }
|
||||
std::string getCategory() const;
|
||||
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; }
|
||||
const uint64_t& getSize() const { return m_size; }
|
||||
DEPRECATED const std::string& getFavicon() const;
|
||||
DEPRECATED const std::string& getFaviconUrl() const;
|
||||
DEPRECATED const std::string& getFaviconMimeType() const;
|
||||
|
||||
Illustrations getIllustrations() const;
|
||||
std::shared_ptr<const Illustration> getIllustration(unsigned int size) const;
|
||||
|
||||
const std::string& getDownloadId() const { return m_downloadId; }
|
||||
|
||||
void setReadOnly(bool readOnly) { m_readOnly = readOnly; }
|
||||
void setId(const std::string& id) { m_id = id; }
|
||||
void setPath(const std::string& path);
|
||||
void setPathValid(bool valid) { m_pathValid = valid; }
|
||||
void setTitle(const std::string& title) { m_title = title; }
|
||||
void setDescription(const std::string& description) { m_description = description; }
|
||||
void setLanguage(const std::string& language) { m_language = language; }
|
||||
void setCreator(const std::string& creator) { m_creator = creator; }
|
||||
void setPublisher(const std::string& publisher) { m_publisher = publisher; }
|
||||
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; }
|
||||
void setMediaCount(uint64_t mediaCount) { m_mediaCount = mediaCount; }
|
||||
void setSize(uint64_t size) { m_size = size; }
|
||||
void setDownloadId(const std::string& downloadId) { m_downloadId = downloadId; }
|
||||
|
||||
private: // functions
|
||||
std::string getCategoryFromTags() const;
|
||||
const Illustration& getDefaultIllustration() const;
|
||||
|
||||
protected: // data
|
||||
std::string m_id;
|
||||
std::string m_downloadId;
|
||||
std::string m_path;
|
||||
bool m_pathValid = false;
|
||||
std::string m_title;
|
||||
std::string m_description;
|
||||
std::string m_category;
|
||||
std::string m_language;
|
||||
std::string m_creator;
|
||||
std::string m_publisher;
|
||||
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 = 0;
|
||||
uint64_t m_mediaCount = 0;
|
||||
bool m_readOnly = false;
|
||||
uint64_t m_size = 0;
|
||||
Illustrations m_illustrations;
|
||||
|
||||
// Used as the return value of getDefaultIllustration() when no default
|
||||
// illustration is found in the book
|
||||
static const Illustration missingDefaultIllustration;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 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.
|
||||
*/
|
||||
|
||||
#ifndef KIWIX_BOOKMARK_H
|
||||
#define KIWIX_BOOKMARK_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace pugi {
|
||||
class xml_node;
|
||||
}
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
class Book;
|
||||
/**
|
||||
* A class to store information about a bookmark (an article in a book)
|
||||
*/
|
||||
class Bookmark
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Create an empty bookmark.
|
||||
*
|
||||
* Bookmark must be populated with `set*` methods
|
||||
*/
|
||||
Bookmark();
|
||||
|
||||
/**
|
||||
* Create a bookmark given a Book, a path and a title.
|
||||
*/
|
||||
Bookmark(const Book& book, const std::string& path, const std::string& title);
|
||||
|
||||
~Bookmark();
|
||||
|
||||
void updateFromXml(const pugi::xml_node& node);
|
||||
|
||||
const std::string& getBookId() const { return m_bookId; }
|
||||
const std::string& getBookTitle() const { return m_bookTitle; }
|
||||
const std::string& getBookName() const { return m_bookName; }
|
||||
const std::string& getBookFlavour() const { return m_bookFlavour; }
|
||||
const std::string& getUrl() const { return m_url; }
|
||||
const std::string& getTitle() const { return m_title; }
|
||||
const std::string& getLanguage() const { return m_language; }
|
||||
const std::string& getDate() const { return m_date; }
|
||||
|
||||
void setBookId(const std::string& bookId) { m_bookId = bookId; }
|
||||
void setBookTitle(const std::string& bookTitle) { m_bookTitle = bookTitle; }
|
||||
void setBookName(const std::string& bookName) { m_bookName = bookName; }
|
||||
void setBookFlavour(const std::string& bookFlavour) { m_bookFlavour = bookFlavour; }
|
||||
void setUrl(const std::string& url) { m_url = url; }
|
||||
void setTitle(const std::string& title) { m_title = title; }
|
||||
void setLanguage(const std::string& language) { m_language = language; }
|
||||
void setDate(const std::string& date) { m_date = date; }
|
||||
|
||||
protected:
|
||||
std::string m_bookId;
|
||||
std::string m_bookTitle;
|
||||
std::string m_bookName;
|
||||
std::string m_bookFlavour;
|
||||
std::string m_url;
|
||||
std::string m_title;
|
||||
std::string m_language;
|
||||
std::string m_date;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,25 +0,0 @@
|
||||
|
||||
#ifndef _KIWIX_COMMON_H_
|
||||
#define _KIWIX_COMMON_H_
|
||||
|
||||
#include <zim/zim.h>
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define DEPRECATED __attribute__((deprecated))
|
||||
#elif defined(_MSC_VER)
|
||||
#define DEPRECATED __declspec(deprecated)
|
||||
#else
|
||||
#praga message("WARNING: You need to implement DEPRECATED for this compiler")
|
||||
#define DEPRECATED
|
||||
#endif
|
||||
|
||||
|
||||
namespace kiwix {
|
||||
|
||||
enum class IpMode { IPV4, IPV6, ALL, AUTO }; // AUTO: Server decides the protocol
|
||||
typedef zim::size_type size_type;
|
||||
typedef zim::offset_type offset_type;
|
||||
|
||||
}
|
||||
|
||||
#endif //_KIWIX_COMMON_H_
|
||||
4
include/common/base64.h
Normal file
4
include/common/base64.h
Normal file
@@ -0,0 +1,4 @@
|
||||
#include <string>
|
||||
|
||||
std::string base64_encode(unsigned char const* , unsigned int len);
|
||||
std::string base64_decode(std::string const& s);
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2011 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
* Copyright 2012 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
|
||||
@@ -17,21 +17,33 @@
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef KIWIX_PATHTOOLS_H
|
||||
#define KIWIX_PATHTOOLS_H
|
||||
|
||||
#include <string>
|
||||
#ifndef KIWIX_NETWORKTOOLS_H
|
||||
#define KIWIX_NETWORKTOOLS_H
|
||||
|
||||
#ifdef _WIN32
|
||||
std::string WideToUtf8(const std::wstring& wstr);
|
||||
std::wstring Utf8ToWide(const std::string& str);
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <net/if.h>
|
||||
#include <netdb.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
unsigned int getFileSize(const std::string& path);
|
||||
std::string getFileSizeAsString(const std::string& path);
|
||||
std::string makeTmpDirectory();
|
||||
bool copyFile(const std::string& sourcePath, const std::string& destPath);
|
||||
bool writeTextFile(const std::string& path, const std::string& content);
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
std::map<std::string, std::string> getNetworkInterfaces();
|
||||
std::string getBestPublicIp();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2021 Veloman Yunkan <veloman.yunkan@gmail.com>
|
||||
* Copyright 2014 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
|
||||
@@ -17,17 +17,18 @@
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef KIWIX_OPDS_CATALOG_H
|
||||
#define KIWIX_OPDS_CATALOG_H
|
||||
#ifndef KIWIX_OTHERTOOLS_H
|
||||
#define KIWIX_OTHERTOOLS_H
|
||||
|
||||
|
||||
#include "library.h"
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
void sleep(unsigned int milliseconds);
|
||||
}
|
||||
|
||||
std::string getSearchUrl(const Filter& f);
|
||||
|
||||
} // namespace kiwix
|
||||
|
||||
#endif // KIWIX_OPDS_CATALOG_H
|
||||
#endif
|
||||
61
include/common/pathTools.h
Normal file
61
include/common/pathTools.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2011 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef KIWIX_PATHTOOLS_H
|
||||
#define KIWIX_PATHTOOLS_H
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <fstream>
|
||||
#include <ios>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <direct.h>
|
||||
#endif
|
||||
|
||||
#include "stringTools.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
bool isRelativePath(const string& path);
|
||||
string computeAbsolutePath(const string path, const string relativePath);
|
||||
string computeRelativePath(const string path, const string absolutePath);
|
||||
string removeLastPathElement(const string path,
|
||||
const bool removePreSeparator = false,
|
||||
const bool removePostSeparator = false);
|
||||
string appendToDirectory(const string& directoryPath, const string& filename);
|
||||
|
||||
unsigned int getFileSize(const string& path);
|
||||
string getFileSizeAsString(const string& path);
|
||||
bool fileExists(const string& path);
|
||||
bool makeDirectory(const string& path);
|
||||
bool copyFile(const string& sourcePath, const string& destPath);
|
||||
string getLastPathElement(const string& path);
|
||||
string getExecutablePath();
|
||||
string getCurrentDirectory();
|
||||
bool writeTextFile(const string& path, const string& content);
|
||||
#endif
|
||||
@@ -20,11 +20,17 @@
|
||||
#ifndef KIWIX_REGEXTOOLS_H
|
||||
#define KIWIX_REGEXTOOLS_H
|
||||
|
||||
#include <unicode/regex.h>
|
||||
#include <unicode/ucnv.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
bool matchRegex(const std::string& content, const std::string& regex);
|
||||
std::string replaceRegex(const std::string& content,
|
||||
const std::string& replacement,
|
||||
const std::string& regex);
|
||||
std::string appendToFirstOccurence(const std::string& content,
|
||||
const std::string regex,
|
||||
const std::string& replacement);
|
||||
|
||||
#endif
|
||||
@@ -21,83 +21,47 @@
|
||||
#define KIWIX_STRINGTOOLS_H
|
||||
|
||||
#include <unicode/unistr.h>
|
||||
#include <unicode/locid.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "pathTools.h"
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
std::string beautifyInteger(uint64_t number);
|
||||
#ifndef __ANDROID__
|
||||
|
||||
std::string beautifyInteger(const unsigned int number);
|
||||
std::string beautifyFileSize(const unsigned int number);
|
||||
std::string urlEncode(const std::string& c);
|
||||
void printStringInHexadecimal(const char* s);
|
||||
void printStringInHexadecimal(icu::UnicodeString s);
|
||||
void printStringInHexadecimal(UnicodeString s);
|
||||
void stringReplacement(std::string& str,
|
||||
const std::string& oldStr,
|
||||
const std::string& newStr);
|
||||
std::string encodeDiples(const std::string& str);
|
||||
|
||||
#endif
|
||||
|
||||
std::string removeAccents(const std::string& text);
|
||||
void loadICUExternalTables();
|
||||
std::string urlDecode(const std::string& c);
|
||||
|
||||
class ICULanguageInfo
|
||||
{
|
||||
public:
|
||||
explicit ICULanguageInfo(const std::string& langCode);
|
||||
|
||||
std::string iso3Code() const;
|
||||
std::string selfName() const;
|
||||
|
||||
private:
|
||||
const icu::Locale locale;
|
||||
};
|
||||
|
||||
std::string escapeForJSON(const std::string& s, bool escapeQuote = true);
|
||||
|
||||
/* urlEncode() is the equivalent of JS encodeURIComponent(), with the only
|
||||
* difference that the slash (/) symbol is NOT encoded. */
|
||||
std::string urlEncode(const std::string& value);
|
||||
std::string urlDecode(const std::string& value, bool component = false);
|
||||
|
||||
std::string join(const std::vector<std::string>& list, const std::string& sep);
|
||||
std::vector<std::string> split(const std::string&, const std::string&);
|
||||
std::vector<std::string> split(const char*, const char*);
|
||||
std::vector<std::string> split(const std::string&, const char*);
|
||||
std::vector<std::string> split(const char*, const std::string&);
|
||||
|
||||
std::string ucAll(const std::string& word);
|
||||
std::string lcAll(const std::string& word);
|
||||
std::string ucFirst(const std::string& word);
|
||||
std::string lcFirst(const std::string& word);
|
||||
|
||||
/* This function is broken, related Github issue
|
||||
* https://github.com/kiwix/libkiwix/issues/1188 */
|
||||
std::string toTitle(const std::string& word);
|
||||
|
||||
std::string normalize(const std::string& word);
|
||||
template<typename T>
|
||||
std::string to_string(T value)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << value;
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T extractFromString(const std::string& str) {
|
||||
std::istringstream iss(str);
|
||||
T ret;
|
||||
iss >> ret;
|
||||
if(iss.fail() || !iss.eof()) {
|
||||
throw std::invalid_argument("no conversion");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<>
|
||||
std::string extractFromString(const std::string& str);
|
||||
|
||||
bool startsWith(const std::string& base, const std::string& start);
|
||||
|
||||
std::string stripSuffix(const std::string& str, const std::string& suffix);
|
||||
|
||||
std::vector<std::string> getTitleVariants(const std::string& title);
|
||||
} //namespace kiwix
|
||||
#endif
|
||||
79
include/ctpp2/CTPP2VMStringLoader.hpp
Normal file
79
include/ctpp2/CTPP2VMStringLoader.hpp
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2013 Renaud Gaudin <reg@kiwix.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef _CTPP2_VM_STRING_LOADER_HPP__
|
||||
#define _CTPP2_VM_STRING_LOADER_HPP__ 1
|
||||
|
||||
#include <ctpp2/CTPP2VMLoader.hpp>
|
||||
#include <ctpp2/CTPP2Util.hpp>
|
||||
#include <ctpp2/CTPP2Exception.hpp>
|
||||
#include <ctpp2/CTPP2VMExecutable.hpp>
|
||||
#include <ctpp2/CTPP2VMInstruction.hpp>
|
||||
#include <ctpp2/CTPP2VMMemoryCore.hpp>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
@file VMStringLoader.hpp
|
||||
@brief Load program core from file
|
||||
*/
|
||||
|
||||
namespace CTPP // C++ Template Engine
|
||||
{
|
||||
// FWD
|
||||
struct VMExecutable;
|
||||
|
||||
/**
|
||||
@class VMStringLoader CTPP2VMStringLoader.hpp <CTPP2VMStringLoader.hpp>
|
||||
@brief Load program core from file
|
||||
*/
|
||||
class CTPP2DECL VMStringLoader:
|
||||
public VMLoader
|
||||
{
|
||||
public:
|
||||
/**
|
||||
*/
|
||||
VMStringLoader(CCHAR_P rawContent, size_t rawContentSize);
|
||||
/**
|
||||
@brief Get ready-to-run program
|
||||
*/
|
||||
const VMMemoryCore * GetCore() const;
|
||||
|
||||
/**
|
||||
@brief A destructor
|
||||
*/
|
||||
~VMStringLoader() throw();
|
||||
private:
|
||||
/** Program core */
|
||||
VMExecutable * oCore;
|
||||
/** Ready-to-run program */
|
||||
VMMemoryCore * pVMMemoryCore;
|
||||
};
|
||||
|
||||
} // namespace CTPP
|
||||
#endif // _CTPP2_VM_STRING_LOADER_HPP__
|
||||
// End.
|
||||
@@ -1,232 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 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.
|
||||
*/
|
||||
|
||||
#ifndef KIWIX_DOWNLOADER_H
|
||||
#define KIWIX_DOWNLOADER_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <mutex>
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
class Aria2;
|
||||
struct DownloadedFile {
|
||||
DownloadedFile()
|
||||
: success(false) {}
|
||||
bool success;
|
||||
std::string path;
|
||||
};
|
||||
|
||||
class AriaError : public std::runtime_error {
|
||||
public:
|
||||
AriaError(const std::string& message) : std::runtime_error(message) {}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A representation of a current download.
|
||||
*
|
||||
* `Download` is not thread safe. User must care to not call method on a
|
||||
* same download from different threads.
|
||||
* However, it is safe to use different `Download`s from different threads.
|
||||
*/
|
||||
|
||||
class Download {
|
||||
public:
|
||||
typedef enum { K_ACTIVE, K_WAITING, K_PAUSED, K_ERROR, K_COMPLETE, K_REMOVED, K_UNKNOWN } StatusResult;
|
||||
|
||||
Download() :
|
||||
m_status(K_UNKNOWN) {}
|
||||
Download(std::shared_ptr<Aria2> p_aria, std::string did)
|
||||
: mp_aria(p_aria),
|
||||
m_status(K_UNKNOWN),
|
||||
m_did(did) {};
|
||||
|
||||
/**
|
||||
* Update the status of the download.
|
||||
*
|
||||
* This call make an aria rpc call and is blocking.
|
||||
* Some download (started with a metalink) are in fact several downloads.
|
||||
* - A first one to download the metadlink.
|
||||
* - A second one to download the real file.
|
||||
*
|
||||
* If `follow` is true, updateStatus tries to detect that and tracks
|
||||
* the second download when the first one is finished.
|
||||
* By passing false to `follow`, `Download` will only track the first download.
|
||||
*
|
||||
* `getFoo` methods are based on the last statusUpdate.
|
||||
*
|
||||
* @param follow: Do we have to follow following downloads.
|
||||
*/
|
||||
void updateStatus(bool follow);
|
||||
|
||||
/**
|
||||
* Pause the download (and call updateStatus)
|
||||
*/
|
||||
void pauseDownload();
|
||||
|
||||
/**
|
||||
* Resume the download (and call updateStatus)
|
||||
*/
|
||||
void resumeDownload();
|
||||
|
||||
/**
|
||||
* Cancel the download.
|
||||
*
|
||||
* A canceled downlod cannot be resume and updateStatus does nothing.
|
||||
* However, you can still get information based on the last known information.
|
||||
*/
|
||||
void cancelDownload();
|
||||
|
||||
/*
|
||||
* Get the status of the download.
|
||||
*/
|
||||
StatusResult getStatus() const { return m_status; }
|
||||
|
||||
/*
|
||||
* Get the id of the download.
|
||||
*/
|
||||
const std::string& getDid() const { return m_did; }
|
||||
|
||||
/*
|
||||
* Get the id of the "second" download.
|
||||
*
|
||||
* Set only if the "first" download is a metalink and is complete.
|
||||
*/
|
||||
const std::string& getFollowedBy() const { return m_followedBy; }
|
||||
|
||||
/*
|
||||
* Get the total length of the download.
|
||||
*/
|
||||
uint64_t getTotalLength() const { return m_totalLength; }
|
||||
|
||||
/*
|
||||
* Get the completed length of the download.
|
||||
*/
|
||||
uint64_t getCompletedLength() const { return m_completedLength; }
|
||||
|
||||
/*
|
||||
* Get the download speed of the download.
|
||||
*/
|
||||
uint64_t getDownloadSpeed() const { return m_downloadSpeed; }
|
||||
|
||||
/*
|
||||
* Get the verified length of the download.
|
||||
*/
|
||||
uint64_t getVerifiedLength() const { return m_verifiedLength; }
|
||||
|
||||
/*
|
||||
* Get the path (local file) of the download.
|
||||
*/
|
||||
const std::string& getPath() const { return m_path; }
|
||||
|
||||
/*
|
||||
* Get the download uris of the download.
|
||||
*/
|
||||
const std::vector<std::string>& getUris() const { return m_uris; }
|
||||
|
||||
protected:
|
||||
std::shared_ptr<Aria2> mp_aria;
|
||||
StatusResult m_status;
|
||||
std::string m_did = "";
|
||||
std::string m_followedBy = "";
|
||||
uint64_t m_totalLength;
|
||||
uint64_t m_completedLength;
|
||||
uint64_t m_downloadSpeed;
|
||||
uint64_t m_verifiedLength;
|
||||
std::vector<std::string> m_uris;
|
||||
std::string m_path;
|
||||
};
|
||||
|
||||
/**
|
||||
* A tool to download things.
|
||||
*
|
||||
* A Downloader manages `Download` using aria2 in the background.
|
||||
* `Downloader` is threadsafe.
|
||||
* However, the returned `Download`s are NOT threadsafe.
|
||||
*/
|
||||
class Downloader
|
||||
{
|
||||
public: // types
|
||||
typedef std::vector<std::pair<std::string, std::string>> Options;
|
||||
|
||||
public: // functions
|
||||
/*
|
||||
* Create a new Downloader object.
|
||||
*
|
||||
* @param sessionFileDir: The directory where aria2 will store its session file.
|
||||
*/
|
||||
explicit Downloader(std::string sessionFileDir);
|
||||
virtual ~Downloader();
|
||||
|
||||
void close();
|
||||
|
||||
/**
|
||||
* Start a new download.
|
||||
*
|
||||
* This method is thread safe and returns a pointer to a newly created
|
||||
* `Download` or an existing one with a matching URI. In the latter case
|
||||
* the options parameter is ignored, which can lead to surprising results.
|
||||
* For example, if the old and new download requests (sharing the same URI)
|
||||
* have different values for the download directory or output file name
|
||||
* options, after the download is reported to be complete the downloaded file
|
||||
* will be present only at the location specified for the first request.
|
||||
*
|
||||
* User should call `update` on the returned `Download` to have an accurate status.
|
||||
*
|
||||
* @param uri: The uri of the thing to download.
|
||||
* @param downloadDir: The download directory where the thing should be stored (takes precedence over any "dir" in `options`).
|
||||
* @param options: A series of pair <option_name, option_value> to pass to aria.
|
||||
* @return: The newly created Download.
|
||||
*/
|
||||
std::shared_ptr<Download> startDownload(const std::string& uri, const std::string& downloadDir, Options options = {});
|
||||
|
||||
/**
|
||||
* Get a download corrsponding to a download id (did)
|
||||
* User should call `update` on the returned `Download` to have an accurate status.
|
||||
*
|
||||
* @param did: The download id to search for.
|
||||
* @return: The Download corresponding to did.
|
||||
* @throw: Throw std::out_of_range if did is not found.
|
||||
*/
|
||||
std::shared_ptr<Download> getDownload(const std::string& did);
|
||||
|
||||
/**
|
||||
* Get the number of downloads currently managed.
|
||||
*/
|
||||
size_t getNbDownload() const;
|
||||
|
||||
/**
|
||||
* Get the ids of the managed downloads.
|
||||
*/
|
||||
std::vector<std::string> getDownloadIds() const;
|
||||
|
||||
private: // data
|
||||
mutable std::mutex m_lock;
|
||||
std::map<std::string, std::shared_ptr<Download>> m_knownDownloads;
|
||||
std::shared_ptr<Aria2> mp_aria;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 Veloman Yunkan <veloman.yunkan@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef KIWIX_I18N
|
||||
#define KIWIX_I18N
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
std::string getTranslatedString(const std::string& lang, const std::string& key);
|
||||
|
||||
namespace i18n
|
||||
{
|
||||
|
||||
typedef std::map<std::string, std::string> Parameters;
|
||||
|
||||
std::string expandParameterizedString(const std::string& lang,
|
||||
const std::string& key,
|
||||
const Parameters& params);
|
||||
|
||||
class GetTranslatedString
|
||||
{
|
||||
public:
|
||||
explicit GetTranslatedString(const std::string& lang) : m_lang(lang) {}
|
||||
|
||||
std::string operator()(const std::string& key) const
|
||||
{
|
||||
return getTranslatedString(m_lang, key);
|
||||
}
|
||||
|
||||
std::string operator()(const std::string& key, const Parameters& params) const
|
||||
{
|
||||
return expandParameterizedString(m_lang, key, params);
|
||||
}
|
||||
|
||||
private:
|
||||
const std::string m_lang;
|
||||
};
|
||||
|
||||
} // namespace i18n
|
||||
|
||||
class ParameterizedMessage
|
||||
{
|
||||
public: // types
|
||||
typedef i18n::Parameters Parameters;
|
||||
|
||||
public: // functions
|
||||
ParameterizedMessage(const std::string& msgId, const Parameters& params)
|
||||
: msgId(msgId)
|
||||
, params(params)
|
||||
{}
|
||||
|
||||
std::string getText(const std::string& lang) const;
|
||||
|
||||
const std::string& getMsgId() const { return msgId; }
|
||||
const Parameters& getParams() const { return params; }
|
||||
|
||||
private: // data
|
||||
const std::string msgId;
|
||||
const Parameters params;
|
||||
};
|
||||
|
||||
inline ParameterizedMessage nonParameterizedMessage(const std::string& msgId)
|
||||
{
|
||||
const ParameterizedMessage::Parameters noParams;
|
||||
return ParameterizedMessage(msgId, noParams);
|
||||
}
|
||||
|
||||
std::string translateBookCategory(const std::string& lang, const std::string& category);
|
||||
|
||||
} // namespace kiwix
|
||||
|
||||
#endif // KIWIX_I18N
|
||||
@@ -22,4 +22,4 @@
|
||||
|
||||
#include "library.h"
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -1,30 +0,0 @@
|
||||
#ifndef KIWIXLIB_KIWIX_SERVE_H_
|
||||
#define KIWIXLIB_KIWIX_SERVE_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
class Subprocess;
|
||||
namespace kiwix {
|
||||
|
||||
class KiwixServe
|
||||
{
|
||||
public:
|
||||
KiwixServe(const std::string& libraryPath, int port = 8181);
|
||||
~KiwixServe();
|
||||
|
||||
void run();
|
||||
void shutDown();
|
||||
bool isRunning();
|
||||
int getPort() { return m_port; }
|
||||
int setPort(int port);
|
||||
|
||||
private:
|
||||
std::unique_ptr<Subprocess> mp_kiwixServe;
|
||||
int m_port;
|
||||
std::string m_libraryPath;
|
||||
};
|
||||
|
||||
}; //end namespace kiwix
|
||||
|
||||
#endif // KIWIXLIB_KIWIX_SERVE_H_
|
||||
@@ -20,492 +20,87 @@
|
||||
#ifndef KIWIX_LIBRARY_H
|
||||
#define KIWIX_LIBRARY_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stack>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <zim/archive.h>
|
||||
#include <zim/search.h>
|
||||
|
||||
#include "book.h"
|
||||
#include "bookmark.h"
|
||||
#include "common.h"
|
||||
#include "common/regexTools.h"
|
||||
#include "common/stringTools.h"
|
||||
|
||||
#define KIWIX_LIBRARY_VERSION "20110515"
|
||||
|
||||
namespace Xapian {
|
||||
class WritableDatabase;
|
||||
};
|
||||
using namespace std;
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
enum supportedIndexType { UNKNOWN, XAPIAN };
|
||||
|
||||
class OPDSDumper;
|
||||
class Library;
|
||||
|
||||
enum supportedListSortBy { UNSORTED, TITLE, SIZE, DATE, CREATOR, PUBLISHER };
|
||||
enum supportedListMode {
|
||||
ALL = 0,
|
||||
LOCAL = 1,
|
||||
REMOTE = 1 << 1,
|
||||
NOLOCAL = 1 << 2,
|
||||
NOREMOTE = 1 << 3,
|
||||
VALID = 1 << 4,
|
||||
NOVALID = 1 << 5
|
||||
};
|
||||
|
||||
enum MigrationMode {
|
||||
/** When migrating bookmarks, do not allow to migrate to an older book than the currently pointed one
|
||||
* (or date stored in the bookmark if book is invalid)
|
||||
*
|
||||
* If no newer books are found, no upgrade is made.
|
||||
*/
|
||||
UPGRADE_ONLY = 0,
|
||||
|
||||
/** Try hard to do a migration. This mostly does:
|
||||
* - Try to find a newer book.
|
||||
* - If book is invalid: find a best book, potentially older.
|
||||
* Older book will never be returned if current book is a valid one.
|
||||
*/
|
||||
ALLOW_DOWNGRADE = 1,
|
||||
};
|
||||
|
||||
class Filter {
|
||||
public: // types
|
||||
using Tags = std::vector<std::string>;
|
||||
|
||||
private: // data
|
||||
uint64_t activeFilters;
|
||||
Tags _acceptTags;
|
||||
Tags _rejectTags;
|
||||
std::string _category;
|
||||
std::string _lang;
|
||||
std::string _publisher;
|
||||
std::string _creator;
|
||||
size_t _maxSize;
|
||||
std::string _query;
|
||||
bool _queryIsPartial;
|
||||
std::string _name;
|
||||
std::string _flavour;
|
||||
|
||||
public: // functions
|
||||
Filter();
|
||||
~Filter() = default;
|
||||
|
||||
/**
|
||||
* Set the filter to check local.
|
||||
*
|
||||
* A local book is a book with a path.
|
||||
* If accept is true, only local book are accepted.
|
||||
* If accept is false, only non local book are accepted.
|
||||
*/
|
||||
Filter& local(bool accept);
|
||||
|
||||
/**
|
||||
* Set the filter to check remote.
|
||||
*
|
||||
* A remote book is a book with a url.
|
||||
* If accept is true, only remote book are accepted.
|
||||
* If accept is false, only non remote book are accepted.
|
||||
*/
|
||||
Filter& remote(bool accept);
|
||||
|
||||
/**
|
||||
* Set the filter to check validity.
|
||||
*
|
||||
* A valid book is a book with a path pointing to a existing zim file.
|
||||
* If accept is true, only valid book are accepted.
|
||||
* If accept is false, only non valid book are accepted.
|
||||
*/
|
||||
Filter& valid(bool accept);
|
||||
|
||||
/**
|
||||
* Set the filter to only accept book with corresponding tag.
|
||||
*/
|
||||
Filter& acceptTags(const Tags& tags);
|
||||
Filter& rejectTags(const Tags& tags);
|
||||
|
||||
/**
|
||||
* Set the filter to only accept books in the specified category.
|
||||
*
|
||||
* Multiple categories can be specified as a comma-separated list (in
|
||||
* which case a book in any of those categories will match).
|
||||
*/
|
||||
Filter& category(std::string category);
|
||||
|
||||
/**
|
||||
* Set the filter to only accept books in the specified language.
|
||||
*
|
||||
* Multiple languages can be specified as a comma-separated list (in
|
||||
* which case a book in any of those languages will match).
|
||||
*/
|
||||
Filter& lang(std::string lang);
|
||||
|
||||
Filter& publisher(std::string publisher);
|
||||
Filter& creator(std::string creator);
|
||||
Filter& maxSize(size_t size);
|
||||
Filter& query(std::string query, bool partial=true);
|
||||
Filter& name(std::string name);
|
||||
Filter& flavour(std::string flavour);
|
||||
Filter& clearLang();
|
||||
Filter& clearCategory();
|
||||
|
||||
bool hasQuery() const;
|
||||
const std::string& getQuery() const { return _query; }
|
||||
bool queryIsPartial() const { return _queryIsPartial; }
|
||||
|
||||
bool hasName() const;
|
||||
const std::string& getName() const { return _name; }
|
||||
|
||||
bool hasCategory() const;
|
||||
const std::string& getCategory() const { return _category; }
|
||||
|
||||
bool hasLang() const;
|
||||
const std::string& getLang() const { return _lang; }
|
||||
|
||||
bool hasPublisher() const;
|
||||
const std::string& getPublisher() const { return _publisher; }
|
||||
|
||||
bool hasCreator() const;
|
||||
const std::string& getCreator() const { return _creator; }
|
||||
|
||||
bool hasFlavour() const;
|
||||
const std::string& getFlavour() const { return _flavour; }
|
||||
|
||||
const Tags& getAcceptTags() const { return _acceptTags; }
|
||||
const Tags& getRejectTags() const { return _rejectTags; }
|
||||
|
||||
private: // functions
|
||||
friend class Library;
|
||||
|
||||
bool accept(const Book& book) const;
|
||||
};
|
||||
|
||||
|
||||
class ZimSearcher : public zim::Searcher
|
||||
{
|
||||
public:
|
||||
explicit ZimSearcher(zim::Searcher&& searcher)
|
||||
: zim::Searcher(searcher)
|
||||
{}
|
||||
|
||||
std::unique_lock<std::mutex> getLock() {
|
||||
return std::unique_lock<std::mutex>(m_mutex);
|
||||
}
|
||||
virtual ~ZimSearcher() = default;
|
||||
private:
|
||||
std::mutex m_mutex;
|
||||
};
|
||||
|
||||
template<typename, typename>
|
||||
class ConcurrentCache;
|
||||
|
||||
template<typename, typename>
|
||||
class MultiKeyCache;
|
||||
|
||||
using LibraryPtr = std::shared_ptr<Library>;
|
||||
using ConstLibraryPtr = std::shared_ptr<const Library>;
|
||||
|
||||
|
||||
// Some compiler we use don't have [[nodiscard]] attribute.
|
||||
// We don't want to declare `create` with it in this case.
|
||||
#define LIBKIWIX_NODISCARD
|
||||
#if defined __has_cpp_attribute
|
||||
#if __has_cpp_attribute (nodiscard)
|
||||
#undef LIBKIWIX_NODISCARD
|
||||
#define LIBKIWIX_NODISCARD [[nodiscard]]
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/**
|
||||
* A Library store several books.
|
||||
*/
|
||||
class Library: public std::enable_shared_from_this<Library>
|
||||
class Book
|
||||
{
|
||||
public:
|
||||
typedef uint64_t Revision;
|
||||
typedef std::vector<std::string> BookIdCollection;
|
||||
typedef std::map<std::string, int> AttributeCounts;
|
||||
typedef std::set<std::string> BookIdSet;
|
||||
Book();
|
||||
~Book();
|
||||
|
||||
private:
|
||||
static bool sortByLastOpen(const Book& a, const Book& b);
|
||||
static bool sortByTitle(const Book& a, const Book& b);
|
||||
static bool sortBySize(const Book& a, const Book& b);
|
||||
static bool sortByDate(const Book& a, const Book& b);
|
||||
static bool sortByCreator(const Book& a, const Book& b);
|
||||
static bool sortByPublisher(const Book& a, const Book& b);
|
||||
static bool sortByLanguage(const Book& a, const Book& b);
|
||||
string getHumanReadableIdFromPath();
|
||||
|
||||
string id;
|
||||
string path;
|
||||
string pathAbsolute;
|
||||
string last;
|
||||
string indexPath;
|
||||
string indexPathAbsolute;
|
||||
supportedIndexType indexType;
|
||||
string title;
|
||||
string description;
|
||||
string language;
|
||||
string creator;
|
||||
string publisher;
|
||||
string date;
|
||||
string url;
|
||||
string name;
|
||||
string tags;
|
||||
string origId;
|
||||
string articleCount;
|
||||
string mediaCount;
|
||||
bool readOnly;
|
||||
string size;
|
||||
string favicon;
|
||||
string faviconMimeType;
|
||||
};
|
||||
|
||||
class Library
|
||||
{
|
||||
public:
|
||||
Library();
|
||||
|
||||
public:
|
||||
LIBKIWIX_NODISCARD static LibraryPtr create() {
|
||||
return LibraryPtr(new Library());
|
||||
}
|
||||
~Library();
|
||||
|
||||
/**
|
||||
* Library is not a copiable object. However it can be moved.
|
||||
*/
|
||||
Library(const Library& ) = delete;
|
||||
Library(Library&& ) = delete;
|
||||
void operator=(const Library& ) = delete;
|
||||
Library& operator=(Library&& ) = delete;
|
||||
|
||||
/**
|
||||
* Add a book to the library.
|
||||
*
|
||||
* If a book already exist in the library with the same id, update
|
||||
* the existing book instead of adding a new one.
|
||||
*
|
||||
* @param book The book to add.
|
||||
* @return True if the book has been added.
|
||||
* False if a book has been updated.
|
||||
*/
|
||||
string version;
|
||||
bool addBook(const Book& book);
|
||||
bool removeBookByIndex(const unsigned int bookIndex);
|
||||
vector<kiwix::Book> books;
|
||||
|
||||
/**
|
||||
* A self-explanatory alias for addBook()
|
||||
/*
|
||||
* 'current' is the variable storing the current content/book id
|
||||
* in the library. This is used to be able to load per default a
|
||||
* content. As Kiwix may work with many library XML files, you may
|
||||
* have "current" defined many time with different values. The
|
||||
* last XML file read has the priority, Although we do not have an
|
||||
* library object for each file, we want to be able to fallback to
|
||||
* an 'old' current book if the one which should be load
|
||||
* failed. That is the reason why we need a stack here
|
||||
*/
|
||||
bool addOrUpdateBook(const Book& book) { return addBook(book); }
|
||||
|
||||
/**
|
||||
* Add a bookmark to the library.
|
||||
*
|
||||
* @param bookmark the book to add.
|
||||
*/
|
||||
void addBookmark(const Bookmark& bookmark);
|
||||
|
||||
/**
|
||||
* Remove a bookmark
|
||||
*
|
||||
* @param zimId The zimId of the bookmark.
|
||||
* @param url The url of the bookmark.
|
||||
* @return True if the bookmark has been removed.
|
||||
*/
|
||||
bool removeBookmark(const std::string& zimId, const std::string& url);
|
||||
|
||||
/**
|
||||
* Migrate all invalid bookmarks.
|
||||
*
|
||||
* All invalid bookmarks (ie pointing to unknown books, no check is made on bookmark pointing to
|
||||
* invalid articles of valid book) will be migrated (if possible) to a better book.
|
||||
* "Better book", will be determined using method `getBestTargetBookId`.
|
||||
*
|
||||
* @return A tuple<int, int>: <The number of bookmarks updated>, <Number of invalid bookmarks before migration was performed>.
|
||||
*/
|
||||
std::tuple<int, int> migrateBookmarks(MigrationMode migrationMode = ALLOW_DOWNGRADE);
|
||||
|
||||
/**
|
||||
* Migrate all bookmarks associated to a specific book.
|
||||
*
|
||||
* All bookmarks associated to `sourceBookId` book will be migrated to a better book.
|
||||
* "Better book", will be determined using method `getBestTargetBookId`.
|
||||
*
|
||||
* @param sourceBookId the source bookId of the bookmarks to migrate.
|
||||
* @param migrationMode how we will find the best book.
|
||||
* @return The number of bookmarks updated.
|
||||
*/
|
||||
int migrateBookmarks(const std::string& sourceBookId, MigrationMode migrationMode = UPGRADE_ONLY);
|
||||
|
||||
/**
|
||||
* Migrate bookmarks
|
||||
*
|
||||
* Migrate all bookmarks pointing to `source` to `destination`.
|
||||
*
|
||||
* @param sourceBookId the source bookId of the bookmarks to migrate.
|
||||
* @param targetBookId the destination bookId to migrate the bookmarks to.
|
||||
* @return The number of bookmarks updated.
|
||||
*/
|
||||
int migrateBookmarks(const std::string& sourceBookId, const std::string& targetBookId);
|
||||
|
||||
/**
|
||||
* Get the best available bookId for a bookmark.
|
||||
*
|
||||
* Given a bookmark, return the best available bookId.
|
||||
* "best available bookId" is determined using heuristitcs based on book name, flavour and date.
|
||||
*
|
||||
* @param bookmark The bookmark to search the bookId for.
|
||||
* @param migrationMode The migration mode to use.
|
||||
* @return A bookId. Potentially empty string if no suitable book found.
|
||||
*/
|
||||
std::string getBestTargetBookId(const Bookmark& bookmark, MigrationMode migrationMode) const;
|
||||
|
||||
/**
|
||||
* Get the best bookId for a combination of book's name, flavour and date.
|
||||
*
|
||||
* Given a bookName (mandatory), try to find the best book.
|
||||
* If preferedFlavour is given, will try to find a book with the same flavour. If not found, return a book with a different flavour.
|
||||
* If minDate is given, return a book newer than minDate. If not found, return a empty bookId.
|
||||
*
|
||||
* @param bookName The name of the book
|
||||
* @param preferedFlavour The prefered flavour.
|
||||
* @param minDate the minimal book date acceptable. Must be a string in the format "YYYY-MM-DD".
|
||||
* @return A bookId corresponding to the query, or empty string if not found.
|
||||
*/
|
||||
std::string getBestTargetBookId(const std::string& bookName, const std::string& preferedFlavour="", const std::string& minDate="") const;
|
||||
|
||||
// XXX: This is a non-thread-safe operation
|
||||
const Book& getBookById(const std::string& id) const;
|
||||
// XXX: This is a non-thread-safe operation
|
||||
const Book& getBookByPath(const std::string& path) const;
|
||||
|
||||
Book getBookByIdThreadSafe(const std::string& id) const;
|
||||
|
||||
std::shared_ptr<zim::Archive> getArchiveById(const std::string& id);
|
||||
std::shared_ptr<ZimSearcher> getSearcherById(const std::string& id) {
|
||||
return getSearcherByIds(BookIdSet{id});
|
||||
}
|
||||
std::shared_ptr<ZimSearcher> getSearcherByIds(const BookIdSet& ids);
|
||||
|
||||
/**
|
||||
* Remove a book from the library.
|
||||
*
|
||||
* @param id the id of the book to remove.
|
||||
* @return True if the book were in the lirbrary and has been removed.
|
||||
*/
|
||||
bool removeBookById(const std::string& id);
|
||||
|
||||
/**
|
||||
* Write the library to a file.
|
||||
*
|
||||
* @param path the path of the file to write to.
|
||||
* @return True if the library has been correctly saved.
|
||||
*/
|
||||
bool writeToFile(const std::string& path) const;
|
||||
|
||||
/**
|
||||
* Write the library bookmarks to a file.
|
||||
*
|
||||
* @param path the path of the file to write to.
|
||||
* @return True if the library has been correctly saved.
|
||||
*/
|
||||
bool writeBookmarksToFile(const std::string& path) const;
|
||||
|
||||
/**
|
||||
* Get the number of book in the library.
|
||||
*
|
||||
* @param localBooks If we must count local books (books with a path).
|
||||
* @param remoteBooks If we must count remote books (books with an url)
|
||||
* @return The number of books.
|
||||
*/
|
||||
unsigned int getBookCount(const bool localBooks, const bool remoteBooks) const;
|
||||
|
||||
/**
|
||||
* Get all languagues of the books in the library.
|
||||
*
|
||||
* @return A list of languages.
|
||||
*/
|
||||
std::vector<std::string> getBooksLanguages() const;
|
||||
|
||||
/**
|
||||
* Get all languagues of the books in the library with counts.
|
||||
*
|
||||
* @return A list of languages with the count of books in each language.
|
||||
*/
|
||||
AttributeCounts getBooksLanguagesWithCounts() const;
|
||||
|
||||
/**
|
||||
* Get all categories of the books in the library.
|
||||
*
|
||||
* @return A list of categories.
|
||||
*/
|
||||
std::vector<std::string> getBooksCategories() const;
|
||||
|
||||
/**
|
||||
* Get all book creators of the books in the library.
|
||||
*
|
||||
* @return A list of book creators.
|
||||
*/
|
||||
std::vector<std::string> getBooksCreators() const;
|
||||
|
||||
/**
|
||||
* Get all book publishers of the books in the library.
|
||||
*
|
||||
* @return A list of book publishers.
|
||||
*/
|
||||
std::vector<std::string> getBooksPublishers() const;
|
||||
|
||||
/**
|
||||
* Get all bookmarks.
|
||||
*
|
||||
* @return A list of bookmarks
|
||||
*/
|
||||
const std::vector<kiwix::Bookmark> getBookmarks(bool onlyValidBookmarks = true) const;
|
||||
|
||||
/**
|
||||
* Get all book ids of the books in the library.
|
||||
*
|
||||
* @return A list of book ids.
|
||||
*/
|
||||
BookIdCollection getBooksIds() const;
|
||||
|
||||
/**
|
||||
* Filter the library and return the id of the keep elements.
|
||||
*
|
||||
* @param filter The filter to use.
|
||||
* @return The list of bookIds corresponding to the filter.
|
||||
*/
|
||||
BookIdCollection filter(const Filter& filter) const;
|
||||
|
||||
|
||||
/**
|
||||
* Sort (in place) bookIds using the given comparator.
|
||||
*
|
||||
* @param bookIds the list of book Ids to sort
|
||||
* @param comparator how to sort the books
|
||||
* @return The sorted list of books
|
||||
*/
|
||||
void sort(BookIdCollection& bookIds, supportedListSortBy sortBy, bool ascending) const;
|
||||
|
||||
/**
|
||||
* Return the current revision of the library.
|
||||
*
|
||||
* The revision of the library is updated (incremented by one) by
|
||||
* the addBook() and removeBookById() operations.
|
||||
*
|
||||
* @return Current revision of the library.
|
||||
*/
|
||||
Revision getRevision() const;
|
||||
|
||||
/**
|
||||
* Remove books that have not been updated since the specified revision.
|
||||
*
|
||||
* @param rev the library revision to use
|
||||
* @return Count of books that were removed by this operation.
|
||||
*/
|
||||
uint32_t removeBooksNotUpdatedSince(Revision rev);
|
||||
|
||||
friend class OPDSDumper;
|
||||
friend class libXMLDumper;
|
||||
|
||||
private: // types
|
||||
typedef const std::string& (Book::*BookStrPropMemFn)() const;
|
||||
struct Entry : Book
|
||||
{
|
||||
Library::Revision lastUpdatedRevision = 0;
|
||||
};
|
||||
|
||||
private: // functions
|
||||
AttributeCounts getBookAttributeCounts(BookStrPropMemFn p) const;
|
||||
std::vector<std::string> getBookPropValueSet(BookStrPropMemFn p) const;
|
||||
BookIdCollection filterViaBookDB(const Filter& filter) const;
|
||||
std::string getBestFromBookCollection(BookIdCollection books, const Bookmark& bookmark, MigrationMode migrationMode) const;
|
||||
unsigned int getBookCount_not_protected(const bool localBooks, const bool remoteBooks) const;
|
||||
void updateBookDB(const Book& book);
|
||||
void dropCache(const std::string& bookId);
|
||||
|
||||
private: //data
|
||||
mutable std::recursive_mutex m_mutex;
|
||||
Library::Revision m_revision;
|
||||
std::map<std::string, Entry> m_books;
|
||||
using ArchiveCache = ConcurrentCache<std::string, std::shared_ptr<zim::Archive>>;
|
||||
std::unique_ptr<ArchiveCache> mp_archiveCache;
|
||||
using SearcherCache = MultiKeyCache<std::string, std::shared_ptr<ZimSearcher>>;
|
||||
std::unique_ptr<SearcherCache> mp_searcherCache;
|
||||
std::vector<kiwix::Bookmark> m_bookmarks;
|
||||
std::unique_ptr<Xapian::WritableDatabase> m_bookDB;
|
||||
stack<string> current;
|
||||
};
|
||||
|
||||
// We don't need it anymore and we don't want to polute any other potential usage
|
||||
// of `LIBKIWIX_NODISCARD` token.
|
||||
#undef LIBKIWIX_NODISCARD
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -20,168 +20,88 @@
|
||||
#ifndef KIWIX_MANAGER_H
|
||||
#define KIWIX_MANAGER_H
|
||||
|
||||
#include "book.h"
|
||||
#include "library.h"
|
||||
|
||||
#include <time.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace pugi {
|
||||
class xml_document;
|
||||
}
|
||||
#include <pugixml.hpp>
|
||||
|
||||
#include "common/base64.h"
|
||||
#include "common/pathTools.h"
|
||||
#include "common/regexTools.h"
|
||||
#include "library.h"
|
||||
#include "reader.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
enum supportedListMode { LASTOPEN, REMOTE, LOCAL };
|
||||
enum supportedListSortBy { TITLE, SIZE, DATE, CREATOR, PUBLISHER };
|
||||
|
||||
class LibraryManipulator
|
||||
{
|
||||
public: // functions
|
||||
explicit LibraryManipulator(LibraryPtr library);
|
||||
virtual ~LibraryManipulator();
|
||||
|
||||
LibraryPtr getLibrary() const { return library; }
|
||||
|
||||
bool addBookToLibrary(const Book& book);
|
||||
void addBookmarkToLibrary(const Bookmark& bookmark);
|
||||
uint32_t removeBooksNotUpdatedSince(Library::Revision rev);
|
||||
|
||||
protected: // overrides
|
||||
virtual void bookWasAddedToLibrary(const Book& book);
|
||||
virtual void bookmarkWasAddedToLibrary(const Bookmark& bookmark);
|
||||
virtual void booksWereRemovedFromLibrary();
|
||||
|
||||
private: // data
|
||||
LibraryPtr library;
|
||||
};
|
||||
|
||||
/**
|
||||
* A tool to manage a `Library`.
|
||||
*/
|
||||
class Manager
|
||||
{
|
||||
public: // types
|
||||
typedef std::vector<std::string> Paths;
|
||||
public:
|
||||
Manager();
|
||||
~Manager();
|
||||
|
||||
public: // functions
|
||||
explicit Manager(LibraryManipulator manipulator);
|
||||
explicit Manager(LibraryPtr library);
|
||||
|
||||
/**
|
||||
* Read a `library.xml` and add book in the file to the library.
|
||||
*
|
||||
* @param path The (utf8) path to the `library.xml`.
|
||||
* @param readOnly Set if the libray path could be overwritten latter with
|
||||
* updated content.
|
||||
* @param trustLibrary use book metadata coming from XML.
|
||||
* @return True if file has been properly parsed.
|
||||
*/
|
||||
bool readFile(const std::string& path, bool readOnly = true, bool trustLibrary = true);
|
||||
|
||||
/**
|
||||
* Sync the contents of the library with one or more `library.xml` files.
|
||||
*
|
||||
* The metadata of the library files is trusted unconditionally.
|
||||
* Any books not present in the input library.xml files are removed
|
||||
* from the library.
|
||||
*
|
||||
* @param paths The (utf8) paths to the `library.xml` files.
|
||||
*/
|
||||
void reload(const Paths& paths);
|
||||
|
||||
/**
|
||||
* Load a library content store in the string.
|
||||
*
|
||||
* @param xml The content corresponding of the library xml
|
||||
* @param readOnly Set if the libray path could be overwritten latter with
|
||||
* updated content.
|
||||
* @param libraryPath The library path (used to resolve relative path)
|
||||
* @return True if the content has been properly parsed.
|
||||
*/
|
||||
bool readXml(const std::string& xml,
|
||||
bool readFile(const string path, const bool readOnly = true);
|
||||
bool readFile(const string nativePath,
|
||||
const string UTF8Path,
|
||||
const bool readOnly = true);
|
||||
bool readXml(const string xml,
|
||||
const bool readOnly = true,
|
||||
const std::string& libraryPath = "",
|
||||
bool trustLibrary = true);
|
||||
|
||||
/**
|
||||
* Load a library content stored in a OPDS stream.
|
||||
*
|
||||
* @param content The content of the OPDS stream.
|
||||
* @param readOnly Set if the library path could be overwritten later with
|
||||
* updated content.
|
||||
* @param libraryPath The library path (used to resolve relative path)
|
||||
* @return True if the content has been properly parsed.
|
||||
*/
|
||||
bool readOpds(const std::string& content, const std::string& urlHost);
|
||||
|
||||
|
||||
/**
|
||||
* Load a bookmark file.
|
||||
*
|
||||
* @param path The path of the file to read.
|
||||
* @return True if the content has been properly parsed.
|
||||
*/
|
||||
bool readBookmarkFile(const std::string& path);
|
||||
|
||||
/**
|
||||
* Add a book to the library.
|
||||
*
|
||||
* @param pathToOpen The path to 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.
|
||||
* @param checMetaData Tell if we check metadata before adding book to the
|
||||
* library.
|
||||
* @return The id of the book if the book has been added to the library.
|
||||
* Else, an empty string.
|
||||
*/
|
||||
std::string addBookFromPathAndGetId(const std::string& pathToOpen,
|
||||
const std::string& pathToSave = "",
|
||||
const std::string& url = "",
|
||||
const string libraryPath = "");
|
||||
bool writeFile(const string path);
|
||||
bool removeBookByIndex(const unsigned int bookIndex);
|
||||
bool removeBookById(const string id);
|
||||
bool setCurrentBookId(const string id);
|
||||
string getCurrentBookId();
|
||||
bool setBookIndex(const string id,
|
||||
const string path,
|
||||
const supportedIndexType type);
|
||||
bool setBookIndex(const string id, const string path);
|
||||
bool setBookPath(const string id, const string path);
|
||||
string addBookFromPathAndGetId(const string pathToOpen,
|
||||
const string pathToSave = "",
|
||||
const string url = "",
|
||||
const bool checkMetaData = false);
|
||||
|
||||
/**
|
||||
* Add a book to the library.
|
||||
*
|
||||
* @param pathToOpen The path to 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.
|
||||
* @param checMetaData Tell if we check metadata before adding book to the
|
||||
* library.
|
||||
* @return True if the book has been added to the library.
|
||||
*/
|
||||
|
||||
bool addBookFromPath(const std::string& pathToOpen,
|
||||
const std::string& pathToSave = "",
|
||||
const std::string& url = "",
|
||||
bool addBookFromPath(const string pathToOpen,
|
||||
const string pathToSave = "",
|
||||
const string url = "",
|
||||
const bool checkMetaData = false);
|
||||
Library cloneLibrary();
|
||||
bool getBookById(const string id, Book& book);
|
||||
bool getCurrentBook(Book& book);
|
||||
unsigned int getBookCount(const bool localBooks, const bool remoteBooks);
|
||||
bool updateBookLastOpenDateById(const string id);
|
||||
void removeBookPaths();
|
||||
bool listBooks(const supportedListMode mode,
|
||||
const supportedListSortBy sortBy,
|
||||
const unsigned int maxSize,
|
||||
const string language,
|
||||
const string creator,
|
||||
const string publisher,
|
||||
const string search);
|
||||
vector<string> getBooksLanguages();
|
||||
vector<string> getBooksCreators();
|
||||
vector<string> getBooksPublishers();
|
||||
vector<string> getBooksIds();
|
||||
|
||||
/**
|
||||
* Add all books from the directory tree into the library.
|
||||
*
|
||||
* @param path The path of the directory to scan.
|
||||
* @param verboseFlag Verbose logs flag.
|
||||
*/
|
||||
void addBooksFromDirectory(const std::string& path,
|
||||
const bool verboseFlag = false);
|
||||
string writableLibraryPath;
|
||||
|
||||
std::string writableLibraryPath;
|
||||
|
||||
bool m_hasSearchResult = false;
|
||||
uint64_t m_totalBooks = 0;
|
||||
uint64_t m_startIndex = 0;
|
||||
uint64_t m_itemsPerPage = 0;
|
||||
vector<std::string> bookIdList;
|
||||
|
||||
protected:
|
||||
kiwix::LibraryManipulator manipulator;
|
||||
kiwix::Library library;
|
||||
|
||||
bool readBookFromPath(const std::string& path, Book* book);
|
||||
bool readBookFromPath(const string path, Book* book = NULL);
|
||||
bool parseXmlDom(const pugi::xml_document& doc,
|
||||
bool readOnly,
|
||||
const std::string& libraryPath,
|
||||
bool trustLibrary);
|
||||
bool parseOpdsDom(const pugi::xml_document& doc,
|
||||
const std::string& urlHost);
|
||||
const bool readOnly,
|
||||
const string libraryPath);
|
||||
|
||||
private:
|
||||
void checkAndCleanBookPaths(Book& book, const string& libraryPath);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,30 @@
|
||||
headers = [
|
||||
'book.h',
|
||||
'bookmark.h',
|
||||
'common.h',
|
||||
'library.h',
|
||||
'manager.h',
|
||||
'downloader.h',
|
||||
'search_renderer.h',
|
||||
'server.h',
|
||||
'spelling_correction.h',
|
||||
'kiwixserve.h',
|
||||
'name_mapper.h',
|
||||
'tools.h',
|
||||
'version.h',
|
||||
'i18n.h'
|
||||
'reader.h',
|
||||
'searcher.h'
|
||||
]
|
||||
|
||||
if xapian_dep.found()
|
||||
headers += ['xapianSearcher.h']
|
||||
endif
|
||||
|
||||
install_headers(headers, subdir:'kiwix')
|
||||
|
||||
install_headers(
|
||||
'common/base64.h',
|
||||
'common/networkTools.h',
|
||||
'common/otherTools.h',
|
||||
'common/pathTools.h',
|
||||
'common/regexTools.h',
|
||||
'common/stringTools.h',
|
||||
subdir:'kiwix/common'
|
||||
)
|
||||
|
||||
if has_ctpp2_dep
|
||||
install_headers(
|
||||
'ctpp2/CTPP2VMStringLoader.hpp',
|
||||
subdir:'kiwix/ctpp2'
|
||||
)
|
||||
endif
|
||||
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
#ifndef KIWIX_NAMEMAPPER_H
|
||||
#define KIWIX_NAMEMAPPER_H
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
class Library;
|
||||
|
||||
class NameMapper {
|
||||
public:
|
||||
virtual ~NameMapper() = default;
|
||||
virtual std::string getNameForId(const std::string& id) const = 0;
|
||||
virtual std::string getIdForName(const std::string& name) const = 0;
|
||||
};
|
||||
|
||||
|
||||
class IdNameMapper : public NameMapper {
|
||||
public:
|
||||
virtual std::string getNameForId(const std::string& id) const { return id; };
|
||||
virtual std::string getIdForName(const std::string& name) const { return name; };
|
||||
};
|
||||
|
||||
class HumanReadableNameMapper : public NameMapper {
|
||||
private:
|
||||
std::map<std::string, std::string> m_idToName;
|
||||
std::map<std::string, std::string> m_nameToId;
|
||||
|
||||
public:
|
||||
HumanReadableNameMapper(const kiwix::Library& library, bool withAlias);
|
||||
virtual ~HumanReadableNameMapper() = default;
|
||||
virtual std::string getNameForId(const std::string& id) const;
|
||||
virtual std::string getIdForName(const std::string& name) const;
|
||||
|
||||
private:
|
||||
void mapName(const kiwix::Library& lib, std::string name, std::string id);
|
||||
};
|
||||
|
||||
class UpdatableNameMapper : public NameMapper {
|
||||
typedef std::shared_ptr<NameMapper> NameMapperHandle;
|
||||
public:
|
||||
UpdatableNameMapper(std::shared_ptr<Library> library, bool withAlias);
|
||||
|
||||
virtual std::string getNameForId(const std::string& id) const;
|
||||
virtual std::string getIdForName(const std::string& name) const;
|
||||
|
||||
void update();
|
||||
|
||||
private:
|
||||
NameMapperHandle currentNameMapper() const;
|
||||
|
||||
private:
|
||||
mutable std::mutex mutex;
|
||||
std::shared_ptr<Library> library;
|
||||
NameMapperHandle nameMapper;
|
||||
const bool withAlias;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
129
include/reader.h
Normal file
129
include/reader.h
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright 2011 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef KIWIX_READER_H
|
||||
#define KIWIX_READER_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <zim/article.h>
|
||||
#include <zim/file.h>
|
||||
#include <zim/fileiterator.h>
|
||||
#include <zim/zim.h>
|
||||
#include <exception>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include "common/pathTools.h"
|
||||
#include "common/stringTools.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
class Reader
|
||||
{
|
||||
public:
|
||||
Reader(const string zimFilePath);
|
||||
~Reader();
|
||||
|
||||
void reset();
|
||||
unsigned int getArticleCount() const;
|
||||
unsigned int getMediaCount() const;
|
||||
unsigned int getGlobalCount() const;
|
||||
string getZimFilePath() const;
|
||||
string getId() const;
|
||||
string getRandomPageUrl() const;
|
||||
string getFirstPageUrl() const;
|
||||
string getMainPageUrl() const;
|
||||
bool getMetatag(const string& url, string& content) const;
|
||||
string getTitle() const;
|
||||
string getDescription() const;
|
||||
string getLanguage() const;
|
||||
string getName() const;
|
||||
string getTags() const;
|
||||
string getDate() const;
|
||||
string getCreator() const;
|
||||
string getPublisher() const;
|
||||
string getOrigId() const;
|
||||
bool getFavicon(string& content, string& mimeType) const;
|
||||
bool getPageUrlFromTitle(const string& title, string& url) const;
|
||||
bool getMimeTypeByUrl(const string& url, string& mimeType) const;
|
||||
bool getContentByUrl(const string& url,
|
||||
string& content,
|
||||
string& title,
|
||||
unsigned int& contentLength,
|
||||
string& contentType) const;
|
||||
bool getContentByEncodedUrl(const string& url,
|
||||
string& content,
|
||||
string& title,
|
||||
unsigned int& contentLength,
|
||||
string& contentType,
|
||||
string& baseUrl) const;
|
||||
bool getContentByEncodedUrl(const string& url,
|
||||
string& content,
|
||||
string& title,
|
||||
unsigned int& contentLength,
|
||||
string& contentType) const;
|
||||
bool getContentByDecodedUrl(const string& url,
|
||||
string& content,
|
||||
string& title,
|
||||
unsigned int& contentLength,
|
||||
string& contentType,
|
||||
string& baseUrl) const;
|
||||
bool getContentByDecodedUrl(const string& url,
|
||||
string& content,
|
||||
string& title,
|
||||
unsigned int& contentLength,
|
||||
string& contentType) const;
|
||||
bool searchSuggestions(const string& prefix,
|
||||
unsigned int suggestionsCount,
|
||||
const bool reset = true);
|
||||
bool searchSuggestionsSmart(const string& prefix,
|
||||
unsigned int suggestionsCount);
|
||||
bool urlExists(const string& url) const;
|
||||
bool hasFulltextIndex() const;
|
||||
std::vector<std::string> getTitleVariants(const std::string& title) const;
|
||||
bool getNextSuggestion(string& title);
|
||||
bool getNextSuggestion(string& title, string& url);
|
||||
bool canCheckIntegrity() const;
|
||||
bool isCorrupted() const;
|
||||
bool parseUrl(const string& url, char* ns, string& title) const;
|
||||
unsigned int getFileSize() const;
|
||||
zim::File* getZimFileHandler() const;
|
||||
bool getArticleObjectByDecodedUrl(const string& url,
|
||||
zim::Article& article) const;
|
||||
|
||||
protected:
|
||||
zim::File* zimFileHandler;
|
||||
zim::size_type firstArticleOffset;
|
||||
zim::size_type lastArticleOffset;
|
||||
zim::size_type currentArticleOffset;
|
||||
zim::size_type nsACount;
|
||||
zim::size_type nsICount;
|
||||
std::string zimFilePath;
|
||||
|
||||
std::vector<std::vector<std::string>> suggestions;
|
||||
std::vector<std::vector<std::string>>::iterator suggestionsOffset;
|
||||
|
||||
private:
|
||||
std::map<const std::string, unsigned int> parseCounterMetadata() const;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
* Copyright 2011 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef KIWIX_SEARCH_RENDERER_H
|
||||
#define KIWIX_SEARCH_RENDERER_H
|
||||
|
||||
#include <string>
|
||||
#include <zim/search.h>
|
||||
#include "library.h"
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
class NameMapper;
|
||||
/**
|
||||
* The SearcherRenderer class is used to render a search result to a html page.
|
||||
*/
|
||||
class SearchRenderer
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Construct a SearchRenderer from a SearchResultSet.
|
||||
*
|
||||
* @param srs The `SearchResultSet` to render.
|
||||
* @param start The start offset used for the srs.
|
||||
* @param estimatedResultCount The estimatedResultCount of the whole search
|
||||
*/
|
||||
SearchRenderer(zim::SearchResultSet srs, unsigned int start, unsigned int estimatedResultCount);
|
||||
|
||||
~SearchRenderer();
|
||||
|
||||
/**
|
||||
* Set the search pattern used to do the search
|
||||
*/
|
||||
void setSearchPattern(const std::string& pattern);
|
||||
|
||||
/**
|
||||
* Set the querystring used to select books
|
||||
*/
|
||||
void setSearchBookQuery(const std::string& bookQuery);
|
||||
|
||||
/**
|
||||
* Set protocol prefix.
|
||||
*/
|
||||
void setProtocolPrefix(const std::string& prefix);
|
||||
|
||||
/**
|
||||
* Set search protocol prefix.
|
||||
*/
|
||||
void setSearchProtocolPrefix(const std::string& prefix);
|
||||
|
||||
/**
|
||||
* set result count per page
|
||||
*/
|
||||
void setPageLength(unsigned int pageLength){
|
||||
this->pageLength = pageLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* set user language
|
||||
*/
|
||||
void setUserLang(const std::string& lang){
|
||||
this->userlang = lang;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the html page with the resutls of the search.
|
||||
*
|
||||
* @param mapper The `NameMapper` to use to do the rendering.
|
||||
* @param library The `Library` to use to look up book details for search results.
|
||||
May be nullptr. In this case, bookName is not set in the rendered string.
|
||||
* @return The html string
|
||||
*/
|
||||
std::string getHtml(const NameMapper& mapper, const Library* library);
|
||||
|
||||
/**
|
||||
* Generate the xml page with the resutls of the search.
|
||||
*
|
||||
* @param mapper The `NameMapper` to use to do the rendering.
|
||||
* @param library The `Library` to use to look up book details for search results.
|
||||
May be nullptr. In this case, bookName is not set in the rendered string.
|
||||
* @return The xml string
|
||||
*/
|
||||
std::string getXml(const NameMapper& mapper, const Library* library);
|
||||
|
||||
protected: // function
|
||||
std::string renderTemplate(const std::string& tmpl_str, const NameMapper& mapper, const Library *library);
|
||||
|
||||
protected:
|
||||
std::string beautifyInteger(const unsigned int number);
|
||||
zim::SearchResultSet m_srs;
|
||||
std::string searchBookQuery;
|
||||
std::string searchPattern;
|
||||
std::string protocolPrefix;
|
||||
std::string searchProtocolPrefix;
|
||||
unsigned int pageLength;
|
||||
unsigned int estimatedResultCount;
|
||||
unsigned int resultStart;
|
||||
std::string userlang = "en";
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
104
include/searcher.h
Normal file
104
include/searcher.h
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2011 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef KIWIX_SEARCHER_H
|
||||
#define KIWIX_SEARCHER_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unicode/putil.h>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <locale>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <vector>
|
||||
#include "common/pathTools.h"
|
||||
#include "common/stringTools.h"
|
||||
#include "kiwix_config.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
class Reader;
|
||||
class Result
|
||||
{
|
||||
public:
|
||||
virtual ~Result(){};
|
||||
virtual std::string get_url() = 0;
|
||||
virtual std::string get_title() = 0;
|
||||
virtual int get_score() = 0;
|
||||
virtual std::string get_snippet() = 0;
|
||||
virtual std::string get_content() = 0;
|
||||
virtual int get_wordCount() = 0;
|
||||
virtual int get_size() = 0;
|
||||
virtual int get_readerIndex() = 0;
|
||||
};
|
||||
|
||||
struct SearcherInternal;
|
||||
class Searcher
|
||||
{
|
||||
public:
|
||||
Searcher();
|
||||
Searcher(const string& xapianDirectoryPath,
|
||||
Reader* reader,
|
||||
const string& humanReadableName);
|
||||
~Searcher();
|
||||
|
||||
void add_reader(Reader* reader, const std::string& humanReaderName);
|
||||
void search(std::string& search,
|
||||
unsigned int resultStart,
|
||||
unsigned int resultEnd,
|
||||
const bool verbose = false);
|
||||
void suggestions(std::string& search, const bool verbose = false);
|
||||
Result* getNextResult();
|
||||
void restart_search();
|
||||
unsigned int getEstimatedResultCount();
|
||||
bool setProtocolPrefix(const std::string prefix);
|
||||
bool setSearchProtocolPrefix(const std::string prefix);
|
||||
void reset();
|
||||
|
||||
#ifdef ENABLE_CTPP2
|
||||
string getHtml();
|
||||
#endif
|
||||
|
||||
protected:
|
||||
std::string beautifyInteger(const unsigned int number);
|
||||
void closeIndex();
|
||||
void searchInIndex(string& search,
|
||||
const unsigned int resultStart,
|
||||
const unsigned int resultEnd,
|
||||
const bool verbose = false);
|
||||
|
||||
std::vector<Reader*> readers;
|
||||
std::vector<std::string> humanReaderNames;
|
||||
SearcherInternal* internal;
|
||||
std::string searchPattern;
|
||||
std::string protocolPrefix;
|
||||
std::string searchProtocolPrefix;
|
||||
unsigned int resultCountPerPage;
|
||||
unsigned int estimatedResultCount;
|
||||
unsigned int resultStart;
|
||||
unsigned int resultEnd;
|
||||
std::string contentHumanReadableId;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
#ifndef KIWIX_SERVER_H
|
||||
#define KIWIX_SERVER_H
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include "tools.h"
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
class Library;
|
||||
class NameMapper;
|
||||
class InternalServer;
|
||||
|
||||
class Server {
|
||||
public:
|
||||
/**
|
||||
* The default constructor.
|
||||
*
|
||||
* @param library The library to serve.
|
||||
*/
|
||||
Server(std::shared_ptr<Library> library, std::shared_ptr<NameMapper> nameMapper=nullptr);
|
||||
|
||||
virtual ~Server();
|
||||
|
||||
/**
|
||||
* Serve the content.
|
||||
*/
|
||||
bool start();
|
||||
|
||||
/**
|
||||
* Stop the daemon.
|
||||
*/
|
||||
void stop();
|
||||
|
||||
void setRoot(const std::string& root);
|
||||
void setAddress(const std::string& addr);
|
||||
void setPort(int port) { m_port = port; }
|
||||
void setNbThreads(int threads) { m_nbThreads = threads; }
|
||||
void setMultiZimSearchLimit(unsigned int limit) { m_multizimSearchLimit = limit; }
|
||||
void setIpConnectionLimit(int limit) { m_ipConnectionLimit = limit; }
|
||||
void setVerbose(bool verbose) { m_verbose = verbose; }
|
||||
void setIndexTemplateString(const std::string& indexTemplateString) { m_indexTemplateString = indexTemplateString; }
|
||||
void setTaskbar(bool withTaskbar, bool withLibraryButton)
|
||||
{ m_withTaskbar = withTaskbar; m_withLibraryButton = withLibraryButton; }
|
||||
void setBlockExternalLinks(bool blockExternalLinks)
|
||||
{ m_blockExternalLinks = blockExternalLinks; }
|
||||
void setCatalogOnlyMode(bool enable) { m_catalogOnlyMode = enable; }
|
||||
void setContentServerUrl(std::string url) { m_contentServerUrl = url; }
|
||||
void setIpMode(IpMode mode) { m_ipMode = mode; }
|
||||
int getPort() const;
|
||||
IpAddress getAddress() const;
|
||||
IpMode getIpMode() const;
|
||||
std::vector<std::string> getServerAccessUrls() const;
|
||||
|
||||
protected:
|
||||
std::shared_ptr<Library> mp_library;
|
||||
std::shared_ptr<NameMapper> mp_nameMapper;
|
||||
std::string m_root = "";
|
||||
IpAddress m_addr;
|
||||
std::string m_indexTemplateString = "";
|
||||
int m_port = 80;
|
||||
int m_nbThreads = 1;
|
||||
unsigned int m_multizimSearchLimit = 0;
|
||||
bool m_verbose = false;
|
||||
bool m_withTaskbar = true;
|
||||
bool m_withLibraryButton = true;
|
||||
bool m_blockExternalLinks = false;
|
||||
IpMode m_ipMode = IpMode::AUTO;
|
||||
int m_ipConnectionLimit = 0;
|
||||
bool m_catalogOnlyMode = false;
|
||||
std::string m_contentServerUrl;
|
||||
std::unique_ptr<InternalServer> mp_server;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Veloman Yunkan
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef KIWIX_SPELLING_CORRECTION_H
|
||||
#define KIWIX_SPELLING_CORRECTION_H
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace zim
|
||||
{
|
||||
class Archive;
|
||||
}
|
||||
|
||||
namespace Xapian
|
||||
{
|
||||
class Database;
|
||||
}
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
class SpellingsDB
|
||||
{
|
||||
public: // functions
|
||||
SpellingsDB(const zim::Archive& archive, std::filesystem::path cacheDirPath);
|
||||
~SpellingsDB();
|
||||
|
||||
SpellingsDB(const SpellingsDB& ) = delete;
|
||||
void operator=(const SpellingsDB& ) = delete;
|
||||
|
||||
std::vector<std::string> getSpellingCorrections(const std::string& word, uint32_t maxCount) const;
|
||||
|
||||
private: // data
|
||||
std::unique_ptr<Xapian::Database> impl_;
|
||||
};
|
||||
|
||||
} // namespace kiwix
|
||||
|
||||
#endif // KIWIX_SPELLING_CORRECTION_H
|
||||
271
include/tools.h
271
include/tools.h
@@ -1,271 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
#ifndef KIWIX_TOOLS_H
|
||||
#define KIWIX_TOOLS_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <cstdint>
|
||||
#include "common.h"
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
struct IpAddress
|
||||
{
|
||||
std::string addr; // IPv4 address
|
||||
std::string addr6; // IPv6 address
|
||||
};
|
||||
|
||||
typedef std::pair<std::string, std::string> LangNameCodePair;
|
||||
typedef std::vector<LangNameCodePair> FeedLanguages;
|
||||
typedef std::vector<std::string> FeedCategories;
|
||||
|
||||
/**
|
||||
* Return the current directory.
|
||||
*
|
||||
* @return the current directory (utf8 encoded)
|
||||
*/
|
||||
std::string getCurrentDirectory();
|
||||
|
||||
/** Return the path of the executable
|
||||
*
|
||||
* Some application may be packaged in auto extractible archive (Appimage) and the
|
||||
* real executable is different of the path of the archive.
|
||||
* If `realPathOnly` is true, return the path of the real executable instead of the
|
||||
* archive launched by the user.
|
||||
*
|
||||
* @param realPathOnly If we must return the real path of the executable.
|
||||
* @return the path of the executable (utf8 encoded)
|
||||
*/
|
||||
std::string getExecutablePath(bool realPathOnly = false);
|
||||
|
||||
/** Tell if the path is a relative path.
|
||||
*
|
||||
* This function is provided as a small helper. It is probably better to use native tools
|
||||
* to manipulate paths.
|
||||
*
|
||||
* @param path A utf8 encoded path.
|
||||
* @return true if the path is relative.
|
||||
*/
|
||||
bool isRelativePath(const std::string& path);
|
||||
|
||||
/** Append a path to another one.
|
||||
*
|
||||
* This function is provided as a small helper. It is probably better to use native tools
|
||||
* to manipulate paths.
|
||||
*
|
||||
* @param basePath the base path.
|
||||
* @param relativePath a path to add to the base path, must be a relative path.
|
||||
* @return The concatenation of the paths, using the right separator.
|
||||
*/
|
||||
std::string appendToDirectory(const std::string& basePath, const std::string& relativePath);
|
||||
|
||||
/** Remove the last element of a path.
|
||||
*
|
||||
* This function is provided as a small helper. It is probably better to use native tools
|
||||
* to manipulate paths.
|
||||
*
|
||||
* @param path a path.
|
||||
* @return The parent directory (or empty string if none).
|
||||
*/
|
||||
std::string removeLastPathElement(const std::string& path);
|
||||
|
||||
/** Get the last element of a path.
|
||||
*
|
||||
* This function is provided as a small helper. It is probably better to use native tools
|
||||
* to manipulate paths.
|
||||
*
|
||||
* @param path a path.
|
||||
* @return The base name of the path or empty string if none (ending with a separator).
|
||||
*/
|
||||
std::string getLastPathElement(const std::string& path);
|
||||
|
||||
/** Compute the absolute path of a relative path based on another one
|
||||
*
|
||||
* Equivalent to appendToDirectory followed by a normalization of the path.
|
||||
*
|
||||
* This function is provided as a small helper. It is probably better to use native tools
|
||||
* to manipulate paths.
|
||||
*
|
||||
* @param path the base path (if empty, current directory is taken).
|
||||
* @param relativePath the relative path.
|
||||
* @return a absolute path.
|
||||
*/
|
||||
std::string computeAbsolutePath(const std::string& path, const std::string& relativePath);
|
||||
|
||||
/** Compute the relative path of a path relative to another one
|
||||
*
|
||||
* This function is provided as a small helper. It is probably better to use native tools
|
||||
* to manipulate paths.
|
||||
*
|
||||
* @param path the base path.
|
||||
* @param absolutePath the absolute path to find the relative path for.
|
||||
* @return a relative path (pointing to absolutePath, relative to path).
|
||||
*/
|
||||
std::string computeRelativePath(const std::string& path, const std::string& absolutePath);
|
||||
|
||||
/** Sleep the current thread.
|
||||
*
|
||||
* This function is provided as a small helper. It is probably better to use native tools.
|
||||
*
|
||||
* @param milliseconds The number of milliseconds to wait for.
|
||||
*/
|
||||
void sleep(unsigned int milliseconds);
|
||||
|
||||
/** Split a string
|
||||
*
|
||||
* This function is provided as a small helper. It is probably better to use native tools.
|
||||
*
|
||||
* Assuming text = "foo:;bar;baz,oups;"
|
||||
*
|
||||
* split(text, ":;", true, true) => ["foo", ":", ";", "bar", ";", "baz,oups", ";"]
|
||||
* split(text, ":;", true, false) => ["foo", "bar", "baz,oups"] (default)
|
||||
* split(text, ":;", false, true) => ["foo", ":", "", ";", "bar", ";", "baz,oups", ";", ""]
|
||||
* split(text, ":;", false, false) => ["foo", "", "bar", "baz,oups", ""]
|
||||
*
|
||||
* @param str The string to split.
|
||||
* @param delims A string of potential delimiters.
|
||||
* Each charater in the string can be a individual delimiters.
|
||||
* @param dropEmpty true if empty part must be dropped from the result.
|
||||
* @param keepDelim true if delimiter must be included from the result.
|
||||
* @return a list of part (potentially containing delimiters)
|
||||
*/
|
||||
std::vector<std::string> split(const std::string& str, const std::string& delims, bool dropEmpty=true, bool keepDelim = false);
|
||||
|
||||
/** Convert language code from iso2 code to iso3
|
||||
*
|
||||
* This function is provided as a small helper. It is probably better to use native tools
|
||||
* to manipulate locales.
|
||||
*
|
||||
* @param a2code a iso2 code string.
|
||||
* @return the corresponding iso3 code.
|
||||
* @throw std::out_of_range if iso2 code is not known.
|
||||
*/
|
||||
std::string converta2toa3(const std::string& a2code);
|
||||
|
||||
/** Extracts content from given file.
|
||||
*
|
||||
* This function provides content of a file provided it's path.
|
||||
*
|
||||
* @param path The absolute path provided in string format.
|
||||
* @return Content of corresponding file in string format.
|
||||
*/
|
||||
std::string getFileContent(const std::string& path);
|
||||
|
||||
/** Checks if file exists.
|
||||
*
|
||||
* This function returns boolean stating if file exists.
|
||||
*
|
||||
* @param path The absolute path provided in string format.
|
||||
* @return Boolean representing if file exists or not.
|
||||
*/
|
||||
bool fileExists(const std::string& path);
|
||||
|
||||
/** Checks if file is readable.
|
||||
*
|
||||
* This function returns boolean stating if file is readable.
|
||||
*
|
||||
* @param path The absolute path provided in string format.
|
||||
* @return Boolean representing if file is readale or not.
|
||||
*/
|
||||
bool fileReadable(const std::string& path);
|
||||
|
||||
/** Provides mimetype from filename.
|
||||
*
|
||||
* This function provides mimetype from file-name.
|
||||
*
|
||||
* @param filename string containing filename.
|
||||
* @return mimetype from filename in string format.
|
||||
*/
|
||||
std::string getMimeTypeForFile(const std::string& filename);
|
||||
|
||||
/** Provides all available network interfaces
|
||||
*
|
||||
* This function provides the available IPv4 and IPv6 network interfaces
|
||||
* as a map from the interface name to its IPv4 and/or IPv6 address(es).
|
||||
*/
|
||||
std::map<std::string, IpAddress> getNetworkInterfacesIPv4Or6();
|
||||
|
||||
/** Provides all available IPv4 network interfaces
|
||||
*
|
||||
* This function provides the available IPv4 network interfaces
|
||||
* as a map from the interface name to its IPv4 address.
|
||||
*
|
||||
* Provided for backward compatibility with libkiwix v13.1.0.
|
||||
*/
|
||||
std::map<std::string, std::string> getNetworkInterfaces();
|
||||
|
||||
/** Provides the best IP address
|
||||
* This function provides the best IP addresses for both ipv4 and ipv6 protocols,
|
||||
* in an IpAddress struct, based on the list given by getNetworkInterfacesIPv4Or6()
|
||||
*/
|
||||
IpAddress getBestPublicIps();
|
||||
|
||||
/** Provides the best IPv4 adddress
|
||||
* Equivalent to getBestPublicIp(false). Provided for backward compatibility
|
||||
* with libkiwix v13.1.0.
|
||||
*/
|
||||
std::string getBestPublicIp();
|
||||
|
||||
/** Converts file size to human readable format.
|
||||
*
|
||||
* This function will convert a number to its equivalent size using units.
|
||||
*
|
||||
* @param number file size in bytes.
|
||||
* @return a human-readable string representation of the size, e.g., "2.3 KB", "1.8 MB", "5.2 GB".
|
||||
*/
|
||||
std::string beautifyFileSize(uint64_t number);
|
||||
|
||||
/**
|
||||
* Load languages stored in an OPDS stream.
|
||||
*
|
||||
* @param content the OPDS stream.
|
||||
* @return vector containing pairs of language code and their corresponding full language name.
|
||||
*/
|
||||
FeedLanguages readLanguagesFromFeed(const std::string& content);
|
||||
|
||||
/**
|
||||
* Load categories stored in an OPDS stream .
|
||||
*
|
||||
* @param content the OPDS stream.
|
||||
* @return vector containing category strings.
|
||||
*/
|
||||
FeedCategories readCategoriesFromFeed(const std::string& content);
|
||||
|
||||
/**
|
||||
* Retrieve the full language name associated with a given ISO 639-3 language code.
|
||||
*
|
||||
* @param lang ISO 639-3 language code.
|
||||
* @return full language name.
|
||||
*/
|
||||
std::string getLanguageSelfName(const std::string& lang);
|
||||
|
||||
/**
|
||||
* Slugifies the filename by converting any characters reserved by the operating
|
||||
* system to '_'. Note filename is only the file name and not a path.
|
||||
*
|
||||
* @param filename Valid UTF-8 encoded file name string.
|
||||
* @return slugified string.
|
||||
*/
|
||||
std::string getSlugifiedFileName(const std::string& filename);
|
||||
}
|
||||
#endif // KIWIX_TOOLS_H
|
||||
98
include/xapianSearcher.h
Normal file
98
include/xapianSearcher.h
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2011 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef KIWIX_XAPIAN_SEARCHER_H
|
||||
#define KIWIX_XAPIAN_SEARCHER_H
|
||||
|
||||
#include <xapian.h>
|
||||
#include "reader.h"
|
||||
#include "searcher.h"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
class XapianSearcher;
|
||||
|
||||
class XapianResult : public Result
|
||||
{
|
||||
public:
|
||||
XapianResult(XapianSearcher* searcher, Xapian::MSetIterator& iterator);
|
||||
virtual ~XapianResult(){};
|
||||
|
||||
virtual std::string get_url();
|
||||
virtual std::string get_title();
|
||||
virtual int get_score();
|
||||
virtual std::string get_snippet();
|
||||
virtual std::string get_content();
|
||||
virtual int get_wordCount();
|
||||
virtual int get_size();
|
||||
virtual int get_readerIndex() { return 0; };
|
||||
|
||||
private:
|
||||
XapianSearcher* searcher;
|
||||
Xapian::MSetIterator iterator;
|
||||
Xapian::Document document;
|
||||
};
|
||||
|
||||
class NoXapianIndexInZim : public exception
|
||||
{
|
||||
virtual const char* what() const throw()
|
||||
{
|
||||
return "There is no fulltext index in the zim file";
|
||||
}
|
||||
};
|
||||
|
||||
class XapianSearcher
|
||||
{
|
||||
friend class XapianResult;
|
||||
|
||||
public:
|
||||
XapianSearcher(const string& xapianDirectoryPath, Reader* reader);
|
||||
virtual ~XapianSearcher(){};
|
||||
void searchInIndex(string& search,
|
||||
const unsigned int resultStart,
|
||||
const unsigned int resultEnd,
|
||||
const bool verbose = false);
|
||||
virtual Result* getNextResult();
|
||||
void restart_search();
|
||||
|
||||
Xapian::MSet results;
|
||||
|
||||
protected:
|
||||
void closeIndex();
|
||||
void openIndex(const string& xapianDirectoryPath);
|
||||
void setup_queryParser();
|
||||
|
||||
Reader* reader;
|
||||
Xapian::Database readableDatabase;
|
||||
std::string language;
|
||||
std::string stopwords;
|
||||
Xapian::QueryParser queryParser;
|
||||
Xapian::Stem stemmer;
|
||||
Xapian::SimpleStopper stopper;
|
||||
Xapian::MSetIterator current_result;
|
||||
std::map<std::string, int> valuesmap;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
10
kiwix.pc.in
Normal file
10
kiwix.pc.in
Normal file
@@ -0,0 +1,10 @@
|
||||
prefix=@prefix@
|
||||
libdir=${prefix}/lib64
|
||||
includedir=${prefix}/include
|
||||
|
||||
Name: libkiwix
|
||||
Description: A library that contains a lot of things used by used by other kiwix programs
|
||||
Version: @version@
|
||||
Requires: @requires@
|
||||
Libs: -L${libdir} -lkiwix @extra_libs@
|
||||
Cflags: -I${includedir}/ @extra_cflags@
|
||||
188
meson.build
188
meson.build
@@ -1,122 +1,110 @@
|
||||
project('libkiwix', 'cpp',
|
||||
version : '14.1.1',
|
||||
license : 'GPLv3+',
|
||||
default_options : ['c_std=c11', 'cpp_std=c++17', 'werror=true'])
|
||||
project('kiwix-lib', 'cpp',
|
||||
version : '1.0.2',
|
||||
license : 'GPL',
|
||||
default_options : ['c_std=c11', 'cpp_std=c++11'])
|
||||
|
||||
compiler = meson.get_compiler('cpp')
|
||||
static_deps = get_option('static-linkage') or get_option('default_library') == 'static'
|
||||
extra_libs = []
|
||||
find_library_in_compiler = meson.version().version_compare('>=0.31.0')
|
||||
|
||||
# Atomics as compiled by GCC or clang can lead to external references to
|
||||
# functions depending on the type size and the platform. LLVM provides them in
|
||||
# 'libcompiler_rt', which clang normally automatically links in, while GNU
|
||||
# provides them in 'libatomic', which GCC *does not* link in automatically (but
|
||||
# this is probably going to change, see
|
||||
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81358). Regardless of the setup
|
||||
# of the compiler driver itself (GCC or clang), we can thus assume that if some
|
||||
# atomic references can't be resolved, then 'libatomic' is missing.
|
||||
atomics_program = '''
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main() {
|
||||
volatile atomic_bool a_b(true);
|
||||
volatile atomic_ullong a_ull(-1);
|
||||
// Next two lines are to cover atomic<socket_t> from 'httplib.h'.
|
||||
volatile atomic<uint32_t> a_u32(-1);
|
||||
volatile atomic<uint64_t> a_u64(-1);
|
||||
|
||||
return atomic_load(&a_b) == false && atomic_load(&a_ull) == 0 &&
|
||||
atomic_load(&a_u32) == 0 && atomic_load(&a_u64) == 0;
|
||||
}
|
||||
'''
|
||||
if not compiler.links(atomics_program,
|
||||
name: 'compiler driver readily supports atomics')
|
||||
libatomic = compiler.find_library('atomic')
|
||||
compiler.links(atomics_program, name: 'atomics work with libatomic',
|
||||
dependencies: libatomic, required: true)
|
||||
extra_libs += ['-latomic']
|
||||
endif
|
||||
|
||||
# C++ std::thread is implemented using pthread on Linux by GCC, and on FreeBSD
|
||||
# for both GCC and LLVM.
|
||||
if (host_machine.system() == 'linux' and compiler.get_id() == 'gcc') or \
|
||||
host_machine.system() == 'freebsd'
|
||||
thread_dep = dependency('threads')
|
||||
else
|
||||
thread_dep = dependency('', required:false)
|
||||
endif
|
||||
static_deps = get_option('android') or get_option('default_library') == 'static'
|
||||
|
||||
thread_dep = dependency('threads')
|
||||
libicu_dep = dependency('icu-i18n', static:static_deps)
|
||||
if libicu_dep.version().version_compare('>= 76')
|
||||
libicu_deps = [libicu_dep, dependency('icu-uc', static:static_deps)]
|
||||
else
|
||||
libicu_deps = [libicu_dep]
|
||||
endif
|
||||
|
||||
libzim_dep = dependency('libzim', version : '>=3.0.0', static:static_deps)
|
||||
pugixml_dep = dependency('pugixml', static:static_deps)
|
||||
libcurl_dep = dependency('libcurl', static:static_deps)
|
||||
microhttpd_dep = dependency('libmicrohttpd', static:static_deps)
|
||||
zlib_dep = dependency('zlib', static:static_deps)
|
||||
xapian_dep = dependency('xapian-core', static:static_deps)
|
||||
|
||||
if compiler.has_header('mustache.hpp')
|
||||
extra_include = []
|
||||
elif compiler.has_header('mustache.hpp', args: '-I/usr/include/kainjow')
|
||||
extra_include = ['/usr/include/kainjow']
|
||||
ctpp2_include_path = ''
|
||||
has_ctpp2_dep = false
|
||||
ctpp2_prefix_install = get_option('ctpp2-install-prefix')
|
||||
ctpp2_link_args = []
|
||||
if ctpp2_prefix_install == ''
|
||||
if compiler.has_header('ctpp2/CTPP2Logger.hpp')
|
||||
if find_library_in_compiler
|
||||
ctpp2_lib = compiler.find_library('ctpp2')
|
||||
else
|
||||
ctpp2_lib = find_library('ctpp2')
|
||||
endif
|
||||
ctpp2_link_args = ['-lctpp2']
|
||||
if meson.is_cross_build() and host_machine.system() == 'windows'
|
||||
if find_library_in_compiler
|
||||
iconv_lib = compiler.find_library('iconv', required:false)
|
||||
else
|
||||
iconv_lib = find_library('iconv', required:false)
|
||||
endif
|
||||
if iconv_lib.found()
|
||||
ctpp2_link_args += ['-liconv']
|
||||
endif
|
||||
endif
|
||||
has_ctpp2_dep = true
|
||||
ctpp2_dep = declare_dependency(link_args:ctpp2_link_args)
|
||||
else
|
||||
message('ctpp2/CTPP2Logger.hpp not found. Compiling without CTPP2 support')
|
||||
endif
|
||||
else
|
||||
error('Cannot found header mustache.hpp')
|
||||
if not find_library_in_compiler
|
||||
error('For custom ctpp2_prefix_install you need a meson version >=0.31.0')
|
||||
endif
|
||||
ctpp2_include_path = ctpp2_prefix_install + '/include'
|
||||
ctpp2_include_args = ['-I'+ctpp2_include_path]
|
||||
if compiler.has_header('ctpp2/CTPP2Logger.hpp', args:ctpp2_include_args)
|
||||
ctpp2_include_dir = include_directories(ctpp2_include_path, is_system:true)
|
||||
ctpp2_lib_path = ctpp2_prefix_install+'/lib'
|
||||
ctpp2_lib = compiler.find_library('ctpp2', dirs:ctpp2_lib_path)
|
||||
ctpp2_link_args = ['-L'+ctpp2_lib_path, '-lctpp2']
|
||||
if meson.is_cross_build() and host_machine.system() == 'windows'
|
||||
iconv_lib = compiler.find_library('iconv', required:false)
|
||||
if iconv_lib.found()
|
||||
ctpp2_link_args += ['-liconv']
|
||||
endif
|
||||
endif
|
||||
has_ctpp2_dep = true
|
||||
ctpp2_dep = declare_dependency(include_directories:ctpp2_include_dir, link_args:ctpp2_link_args)
|
||||
else
|
||||
message('ctpp2/CTPP2Logger.hpp not found. Compiling without CTPP2 support')
|
||||
endif
|
||||
endif
|
||||
|
||||
libzim_dep = dependency('libzim', version:['>=9.4.0', '<10.0.0'], static:static_deps)
|
||||
xapian_dep = dependency('xapian-core', required:false, static:static_deps)
|
||||
|
||||
if not compiler.has_header_symbol('zim/zim.h', 'LIBZIM_WITH_XAPIAN', dependencies: libzim_dep)
|
||||
error('Libzim seems to be compiled without Xapian. Xapian support is mandatory.')
|
||||
all_deps = [thread_dep, libicu_dep, libzim_dep, xapian_dep, pugixml_dep]
|
||||
if has_ctpp2_dep
|
||||
all_deps += [ctpp2_dep]
|
||||
endif
|
||||
|
||||
|
||||
extra_cflags = ''
|
||||
if host_machine.system() == 'windows' and static_deps
|
||||
add_project_arguments('-DCURL_STATICLIB', language : 'cpp')
|
||||
extra_cflags += '-DCURL_STATICLIB'
|
||||
endif
|
||||
|
||||
if host_machine.system() == 'windows'
|
||||
add_project_arguments('-DNOMINMAX', language: 'cpp')
|
||||
extra_libs += ['-liphlpapi']
|
||||
endif
|
||||
|
||||
if build_machine.system() == 'windows'
|
||||
extra_libs += ['-lshlwapi', '-lwinmm']
|
||||
endif
|
||||
|
||||
|
||||
# Dependencies as string
|
||||
all_deps = [thread_dep, libzim_dep, pugixml_dep, libcurl_dep, microhttpd_dep, zlib_dep, xapian_dep]
|
||||
|
||||
# Dependencies as array
|
||||
all_deps += libicu_deps
|
||||
|
||||
inc = include_directories('include', extra_include)
|
||||
inc = include_directories('include')
|
||||
|
||||
conf = configuration_data()
|
||||
conf.set('LIBKIWIX_VERSION', '"@0@"'.format(meson.project_version()))
|
||||
conf.set('VERSION', '"@0@"'.format(meson.project_version()))
|
||||
conf.set('ENABLE_CTPP2', has_ctpp2_dep)
|
||||
|
||||
subdir('include')
|
||||
subdir('scripts')
|
||||
subdir('static')
|
||||
subdir('src')
|
||||
subdir('test')
|
||||
if get_option('doc')
|
||||
subdir('docs')
|
||||
|
||||
pkg_requires = ['libzim', 'icu-i18n', 'pugixml']
|
||||
if xapian_dep.found()
|
||||
pkg_requires += ['xapian-core']
|
||||
endif
|
||||
|
||||
pkg_mod = import('pkgconfig')
|
||||
pkg_mod.generate(libraries : [libkiwix] + extra_libs,
|
||||
version : meson.project_version(),
|
||||
name : 'libkiwix',
|
||||
filebase : 'libkiwix',
|
||||
description : 'A library that contains useful primitives that Kiwix readers have in common',
|
||||
extra_cflags: extra_cflags)
|
||||
extra_libs = []
|
||||
extra_cflags = ''
|
||||
if has_ctpp2_dep
|
||||
extra_libs += ctpp2_link_args
|
||||
if ctpp2_include_path != ''
|
||||
extra_cflags = '-I'+ctpp2_include_path
|
||||
endif
|
||||
endif
|
||||
|
||||
pkg_conf = configuration_data()
|
||||
pkg_conf.set('prefix', get_option('prefix'))
|
||||
pkg_conf.set('requires', ' '.join(pkg_requires))
|
||||
pkg_conf.set('extra_libs', ' '.join(extra_libs))
|
||||
pkg_conf.set('extra_cflags', extra_cflags)
|
||||
pkg_conf.set('version', meson.project_version())
|
||||
configure_file(output : 'kiwix.pc',
|
||||
configuration : pkg_conf,
|
||||
input : 'kiwix.pc.in',
|
||||
install_dir: get_option('libdir')+'/pkgconfig'
|
||||
)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
option('static-linkage', type : 'boolean', value : false,
|
||||
description : 'Link statically with the dependencies.')
|
||||
option('doc', type : 'boolean', value : false,
|
||||
description : 'Build the documentations.')
|
||||
option('ctpp2-install-prefix', type : 'string', value : '',
|
||||
description : 'Prefix where ctpp libs has been installed')
|
||||
option('android', type : 'boolean', value : false,
|
||||
description : 'Do we make a kiwix-lib for android')
|
||||
|
||||
8
scripts/ctpp2c.sh
Executable file
8
scripts/ctpp2c.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
|
||||
ctpp2c=$1
|
||||
SOURCE=$(pwd)/$2
|
||||
DEST=$3
|
||||
|
||||
$ctpp2c $SOURCE $DEST
|
||||
@@ -1,14 +0,0 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
# Compute 'src' path
|
||||
SCRIPT_DIR=$(dirname "$0")
|
||||
REPO_DIR=$(readlink -f "$SCRIPT_DIR"/..)
|
||||
DIRS="src include"
|
||||
|
||||
# Apply formating to all *.cpp and *.h files
|
||||
cd "$REPO_DIR"
|
||||
for FILE in $(find $DIRS -name '*.h' -o -name '*.cpp')
|
||||
do
|
||||
echo $FILE
|
||||
clang-format -i -style=file "$FILE"
|
||||
done
|
||||
@@ -1,148 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
'''
|
||||
Copyright 2022 Veloman Yunkan <veloman.yunkan@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or any
|
||||
later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
02110-1301, USA.
|
||||
'''
|
||||
|
||||
import argparse
|
||||
import os.path
|
||||
import re
|
||||
import json
|
||||
|
||||
def to_identifier(name):
|
||||
ident = re.sub(r'[^0-9a-zA-Z]', '_', name)
|
||||
if ident[0].isnumeric():
|
||||
return "_"+ident
|
||||
return ident
|
||||
|
||||
def lang_code(filename):
|
||||
filename = os.path.basename(filename)
|
||||
lang = to_identifier(os.path.splitext(filename)[0])
|
||||
print(filename, '->', lang)
|
||||
return lang
|
||||
|
||||
from string import Template
|
||||
|
||||
def expand_cxx_template(t, **kwargs):
|
||||
return Template(t).substitute(**kwargs)
|
||||
|
||||
def cxx_string_literal(s):
|
||||
# Taking advantage of the fact the JSON string escape rules match
|
||||
# those of C++
|
||||
return 'u8' + json.dumps(s)
|
||||
|
||||
string_table_cxx_template = '''
|
||||
const I18nString $TABLE_NAME[] = {
|
||||
$TABLE_ENTRIES
|
||||
};
|
||||
'''
|
||||
|
||||
lang_table_entry_cxx_template = '''
|
||||
{
|
||||
$LANG_STRING_LITERAL,
|
||||
ARRAY_ELEMENT_COUNT($STRING_TABLE_NAME),
|
||||
$STRING_TABLE_NAME
|
||||
}'''
|
||||
|
||||
cxxfile_template = '''// This file is automatically generated. Do not modify it.
|
||||
|
||||
#include "server/i18n_utils.h"
|
||||
|
||||
namespace kiwix {
|
||||
namespace i18n {
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
$STRING_DATA
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
#define ARRAY_ELEMENT_COUNT(a) (sizeof(a)/sizeof(a[0]))
|
||||
|
||||
extern const I18nStringTable stringTables[] = {
|
||||
$LANG_TABLE
|
||||
};
|
||||
|
||||
extern const size_t langCount = $LANG_COUNT;
|
||||
|
||||
} // namespace i18n
|
||||
} // namespace kiwix
|
||||
'''
|
||||
|
||||
class Resource:
|
||||
def __init__(self, filename):
|
||||
filename = filename.strip()
|
||||
self.filename = filename
|
||||
self.lang_code = lang_code(filename)
|
||||
with open(filename, 'r', encoding='utf-8') as f:
|
||||
self.data = f.read()
|
||||
|
||||
def get_string_table_name(self):
|
||||
return "string_table_for_" + self.lang_code
|
||||
|
||||
def get_string_table(self):
|
||||
table_entries = ",\n ".join(self.get_string_table_entries())
|
||||
return expand_cxx_template(string_table_cxx_template,
|
||||
TABLE_NAME=self.get_string_table_name(),
|
||||
TABLE_ENTRIES=table_entries)
|
||||
|
||||
def get_string_table_entries(self):
|
||||
d = json.loads(self.data)
|
||||
for k in sorted(d.keys()):
|
||||
if k != "@metadata":
|
||||
key_string = cxx_string_literal(k)
|
||||
value_string = cxx_string_literal(d[k])
|
||||
yield '{ ' + key_string + ', ' + value_string + ' }'
|
||||
|
||||
def get_lang_table_entry(self):
|
||||
return expand_cxx_template(lang_table_entry_cxx_template,
|
||||
LANG_STRING_LITERAL=cxx_string_literal(self.lang_code),
|
||||
STRING_TABLE_NAME=self.get_string_table_name())
|
||||
|
||||
|
||||
|
||||
def gen_c_file(resources):
|
||||
string_data = []
|
||||
lang_table = []
|
||||
for r in resources:
|
||||
string_data.append(r.get_string_table())
|
||||
lang_table.append(r.get_lang_table_entry())
|
||||
|
||||
return expand_cxx_template(cxxfile_template,
|
||||
STRING_DATA="\n".join(string_data),
|
||||
LANG_TABLE=",\n ".join(lang_table),
|
||||
LANG_COUNT=len(resources)
|
||||
)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--cxxfile',
|
||||
required=True,
|
||||
help='The Cpp file name to generate')
|
||||
parser.add_argument('i18n_resource_files', nargs='+',
|
||||
help='The list of resources to compile.')
|
||||
args = parser.parse_args()
|
||||
|
||||
resources = [Resource(filename) for filename in args.i18n_resource_files]
|
||||
|
||||
with open(args.cxxfile, 'w') as f:
|
||||
f.write(gen_c_file(resources))
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
.TH KIWIX-COMPILE-I18N "1" "January 2022" "Kiwix" "User Commands"
|
||||
.SH NAME
|
||||
kiwix-compile-i18n \- helper to compile Kiwix i18n (internationalization) data
|
||||
.SH SYNOPSIS
|
||||
\fBkiwix\-compile\-i18n\fR [\-h] \-\-cxxfile CXXFILE i18n_resource_files ...\fR
|
||||
.SH DESCRIPTION
|
||||
.TP
|
||||
i18n_resource_files ...
|
||||
The list of i18n 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
|
||||
.SH AUTHOR
|
||||
Veloman Yunkan <veloman.yunkan@gmail.com>
|
||||
@@ -52,60 +52,26 @@ resource_getter_template = """
|
||||
return RESOURCE::{identifier};
|
||||
"""
|
||||
|
||||
resource_cacheid_getter_template = """
|
||||
if (name == "{common_name}")
|
||||
return "{cacheid}";
|
||||
"""
|
||||
|
||||
resource_decl_template = """{namespaces_open}
|
||||
extern const std::string {identifier};
|
||||
{namespaces_close}"""
|
||||
|
||||
BINARY_RESOURCE_EXTENSIONS = {'.ico', '.png', '.ttf'}
|
||||
|
||||
TEXT_RESOURCE_EXTENSIONS = {
|
||||
'.css',
|
||||
'.html',
|
||||
'.js',
|
||||
'.json',
|
||||
'.svg',
|
||||
'.tmpl',
|
||||
'.webmanifest',
|
||||
'.xml',
|
||||
}
|
||||
|
||||
if not BINARY_RESOURCE_EXTENSIONS.isdisjoint(TEXT_RESOURCE_EXTENSIONS):
|
||||
raise RuntimeError(f"The following file type extensions are declared to be both binary and text: {BINARY_RESOURCE_EXTENSIONS.intersection(TEXT_RESOURCE_EXTENSIONS)}")
|
||||
|
||||
def is_binary_resource(filename):
|
||||
_, extension = os.path.splitext(filename)
|
||||
is_binary = extension in BINARY_RESOURCE_EXTENSIONS
|
||||
is_text = extension in TEXT_RESOURCE_EXTENSIONS
|
||||
if not is_binary and not is_text:
|
||||
# all file type extensions of static resources must be listed
|
||||
# in either BINARY_RESOURCE_EXTENSIONS or TEXT_RESOURCE_EXTENSIONS
|
||||
raise RuntimeError(f"Unknown file type extension: {extension}")
|
||||
return is_binary
|
||||
|
||||
class Resource:
|
||||
def __init__(self, base_dirs, filename, cacheid=None):
|
||||
filename = filename
|
||||
def __init__(self, base_dirs, filename):
|
||||
filename = filename.strip()
|
||||
self.filename = filename
|
||||
self.identifier = full_identifier(filename)
|
||||
self.cacheid = cacheid
|
||||
found = False
|
||||
for base_dir in base_dirs:
|
||||
try:
|
||||
with open(os.path.join(base_dir, filename), 'rb') as f:
|
||||
self.data = f.read()
|
||||
if not is_binary_resource(filename):
|
||||
self.data = self.data.replace(b"\r\n", b"\n")
|
||||
found = True
|
||||
break
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
if not found:
|
||||
raise Exception("Resource not found: {}".format(filename))
|
||||
raise Exception("Impossible to found {}".format(filename))
|
||||
|
||||
def dump_impl(self):
|
||||
nb_row = len(self.data)//16 + (1 if len(self.data) % 16 else 0)
|
||||
@@ -115,34 +81,28 @@ class Resource:
|
||||
data_identifier="_".join([""]+self.identifier),
|
||||
resource_content=",\n ".join(", ".join("{:#04x}".format(i) for i in r) for r in sliced),
|
||||
resource_len=len(self.data),
|
||||
namespaces_open=" ".join("namespace {} {{".format(id) for id in self.identifier[:-1]),
|
||||
namespaces_open=" ".join("namespace {} {{".format(id) for id in self.identifier[:-1]),
|
||||
namespaces_close=" ".join(["}"]*(len(self.identifier)-1)),
|
||||
identifier=self.identifier[-1],
|
||||
env_identifier="RES_"+"_".join(self.identifier)+"_PATH"
|
||||
)
|
||||
|
||||
|
||||
def dump_getter(self):
|
||||
return resource_getter_template.format(
|
||||
common_name=self.filename,
|
||||
identifier="::".join(self.identifier)
|
||||
)
|
||||
|
||||
def dump_cacheid_getter(self):
|
||||
return resource_cacheid_getter_template.format(
|
||||
common_name=self.filename,
|
||||
cacheid=self.cacheid
|
||||
)
|
||||
|
||||
def dump_decl(self):
|
||||
return resource_decl_template.format(
|
||||
namespaces_open=" ".join("namespace {} {{".format(id) for id in self.identifier[:-1]),
|
||||
namespaces_open=" ".join("namespace {} {{".format(id) for id in self.identifier[:-1]),
|
||||
namespaces_close=" ".join(["}"]*(len(self.identifier)-1)),
|
||||
identifier=self.identifier[-1]
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
master_c_template = """//This file is automatically generated. Do not modify it.
|
||||
master_c_template = """//This file is automaically generated. Do not modify it.
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <fstream>
|
||||
@@ -153,7 +113,7 @@ static std::string init_resource(const char* name, const unsigned char* content,
|
||||
char * resPath = getenv(name);
|
||||
if (NULL == resPath)
|
||||
return std::string(reinterpret_cast<const char*>(content), len);
|
||||
|
||||
|
||||
std::ifstream ifs(resPath);
|
||||
if (!ifs.good())
|
||||
return std::string(reinterpret_cast<const char*>(content), len);
|
||||
@@ -163,12 +123,7 @@ static std::string init_resource(const char* name, const unsigned char* content,
|
||||
|
||||
const std::string& getResource_{basename}(const std::string& name) {{
|
||||
{RESOURCES_GETTER}
|
||||
throw ResourceNotFound("Resource not found: " + name);
|
||||
}}
|
||||
|
||||
const char* getResourceCacheId_{basename}(const std::string& name) {{
|
||||
{RESOURCE_CACHEID_GETTER}
|
||||
return nullptr;
|
||||
throw ResourceNotFound("Resource not found.");
|
||||
}}
|
||||
|
||||
{RESOURCES}
|
||||
@@ -179,11 +134,10 @@ def gen_c_file(resources, basename):
|
||||
return master_c_template.format(
|
||||
RESOURCES="\n\n".join(r.dump_impl() for r in resources),
|
||||
RESOURCES_GETTER="\n\n".join(r.dump_getter() for r in resources),
|
||||
RESOURCE_CACHEID_GETTER="\n\n".join(r.dump_cacheid_getter() for r in resources if r.cacheid is not None),
|
||||
include_file=basename,
|
||||
basename=to_identifier(basename)
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
master_h_template = """//This file is automaically generated. Do not modify it.
|
||||
@@ -205,10 +159,8 @@ class ResourceNotFound : public std::runtime_error {{
|
||||
}};
|
||||
|
||||
const std::string& getResource_{basename}(const std::string& name);
|
||||
const char* getResourceCacheId_{basename}(const std::string& name);
|
||||
|
||||
#define getResource(a) (getResource_{basename}(a))
|
||||
#define getResourceCacheId(a) (getResourceCacheId_{basename}(a))
|
||||
|
||||
#endif // KIWIX_{BASENAME}
|
||||
|
||||
@@ -230,17 +182,15 @@ if __name__ == "__main__":
|
||||
parser.add_argument('--source_dir',
|
||||
help="Additional directory where to look for resources.",
|
||||
action='append')
|
||||
parser.add_argument('resource_files', nargs='+',
|
||||
parser.add_argument('resource_file',
|
||||
help='The list of resources to compile.')
|
||||
args = parser.parse_args()
|
||||
|
||||
base_dir = os.path.dirname(os.path.realpath(args.resource_file))
|
||||
source_dir = args.source_dir or []
|
||||
resources = []
|
||||
for resfile in args.resource_files:
|
||||
base_dir = os.path.dirname(os.path.realpath(resfile))
|
||||
with open(resfile, 'r') as f:
|
||||
resources += [Resource([base_dir]+source_dir, *line.strip().split())
|
||||
for line in f.readlines()]
|
||||
with open(args.resource_file, 'r') as f:
|
||||
resources = [Resource([base_dir]+source_dir, filename)
|
||||
for filename in f.readlines()]
|
||||
|
||||
h_identifier = to_identifier(os.path.basename(args.hfile))
|
||||
with open(args.hfile, 'w') as f:
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
.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>
|
||||
@@ -1,135 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
'''
|
||||
Copyright 2022 Veloman Yunkan <veloman.yunkan@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or any
|
||||
later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
02110-1301, USA.
|
||||
'''
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import os.path
|
||||
import re
|
||||
|
||||
def read_resource_file(resource_file_path):
|
||||
with open(resource_file_path, 'r') as f:
|
||||
return [line.strip() for line in f]
|
||||
|
||||
def list_resources(resource_file_path):
|
||||
for resource_path in read_resource_file(resource_file_path):
|
||||
print(resource_path)
|
||||
|
||||
def compute_resource_revision(resource_path):
|
||||
with open(os.path.join(OUT_DIR, resource_path), 'rb') as f:
|
||||
return hashlib.sha1(f.read()).hexdigest()[:8]
|
||||
|
||||
resource_revisions = {}
|
||||
|
||||
def get_resource_revision(res):
|
||||
if not res in resource_revisions:
|
||||
preprocess_resource(res)
|
||||
resource_revisions[res] = compute_resource_revision(res)
|
||||
return resource_revisions[res]
|
||||
|
||||
RESOURCE_WITH_CACHEID_URL_PATTERN=r'(?P<pre>.*/(?P<resource>skin/[^"?]+)\?)KIWIXCACHEID(?P<post>[^"]*)'
|
||||
|
||||
def set_cacheid(resource_matchobj):
|
||||
pre = resource_matchobj.group('pre')
|
||||
resource = resource_matchobj.group('resource')
|
||||
post = resource_matchobj.group('post')
|
||||
cacheid = 'cacheid=' + get_resource_revision(resource)
|
||||
return pre + cacheid + post
|
||||
|
||||
def preprocess_text(s):
|
||||
if 'KIWIXCACHEID' in s:
|
||||
s = re.sub(RESOURCE_WITH_CACHEID_URL_PATTERN, set_cacheid, s)
|
||||
assert not 'KIWIXCACHEID' in s
|
||||
return s
|
||||
|
||||
def get_preprocessed_resource(srcpath):
|
||||
"""Get the transformed content of a resource
|
||||
|
||||
If the resource at srcpath is modified by preprocessing then this function
|
||||
returns the transformed content of the resource. Otherwise it returns None.
|
||||
"""
|
||||
try:
|
||||
with open(srcpath, 'r') as resource_file:
|
||||
content = resource_file.read()
|
||||
preprocessed_content = preprocess_text(content)
|
||||
return preprocessed_content if preprocessed_content != content else None
|
||||
except UnicodeDecodeError:
|
||||
# It was a binary resource
|
||||
return None
|
||||
|
||||
|
||||
def symlink_resource(src, resource_path):
|
||||
if os.path.exists(resource_path):
|
||||
if os.path.islink(resource_path) and os.readlink(resource_path) == src:
|
||||
return
|
||||
os.remove(resource_path)
|
||||
os.symlink(src, resource_path)
|
||||
|
||||
def preprocess_resource(resource_path):
|
||||
print('Preprocessing', resource_path, '...')
|
||||
resource_dir = os.path.dirname(resource_path)
|
||||
if resource_dir != '':
|
||||
os.makedirs(os.path.join(OUT_DIR, resource_dir), exist_ok=True)
|
||||
srcpath = os.path.join(BASE_DIR, resource_path)
|
||||
outpath = os.path.join(OUT_DIR, resource_path)
|
||||
if os.path.exists(outpath):
|
||||
os.remove(outpath)
|
||||
preprocessed_content = get_preprocessed_resource(srcpath)
|
||||
if preprocessed_content is None:
|
||||
symlink_resource(srcpath, outpath)
|
||||
else:
|
||||
with open(outpath, 'w') as target:
|
||||
print(preprocessed_content, end='', file=target)
|
||||
|
||||
|
||||
def copy_resource_list_file(src_path, dst_path):
|
||||
with open(src_path, 'r') as src:
|
||||
with open(dst_path, 'w') as dst:
|
||||
for line in src:
|
||||
res = line.strip()
|
||||
if line.startswith("skin/") and res in resource_revisions:
|
||||
dst.write(res + " " + resource_revisions[res] + "\n")
|
||||
else:
|
||||
dst.write(line)
|
||||
|
||||
def preprocess_resources(resource_file_path):
|
||||
resource_filename = os.path.basename(resource_file_path)
|
||||
for resource in read_resource_file(resource_file_path):
|
||||
if resource.startswith('skin/'):
|
||||
get_resource_revision(resource)
|
||||
else:
|
||||
preprocess_resource(resource)
|
||||
copy_resource_list_file(resource_file_path, os.path.join(OUT_DIR, resource_filename))
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
commands = parser.add_mutually_exclusive_group()
|
||||
commands.add_argument('--list-all', action='store_true')
|
||||
commands.add_argument('--preprocess', action='store_true')
|
||||
parser.add_argument('--outdir')
|
||||
parser.add_argument('resource_file')
|
||||
args = parser.parse_args()
|
||||
BASE_DIR = os.path.dirname(os.path.realpath(args.resource_file))
|
||||
OUT_DIR = args.outdir
|
||||
|
||||
if args.list_all:
|
||||
list_resources(args.resource_file)
|
||||
elif args.preprocess:
|
||||
preprocess_resources(args.resource_file)
|
||||
@@ -1,13 +1,5 @@
|
||||
|
||||
res_manager = find_program('kiwix-resources')
|
||||
res_compiler = find_program('kiwix-compile-resources')
|
||||
intermediate_ctpp2c = find_program('ctpp2c.sh')
|
||||
|
||||
install_data(res_compiler.path(), install_dir:get_option('bindir'))
|
||||
|
||||
install_man('kiwix-compile-resources.1')
|
||||
|
||||
i18n_compiler = find_program('kiwix-compile-i18n')
|
||||
|
||||
install_data(i18n_compiler.path(), install_dir:get_option('bindir'))
|
||||
|
||||
install_man('kiwix-compile-i18n.1')
|
||||
|
||||
13
src/android/AndroidManifest.xml
Normal file
13
src/android/AndroidManifest.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
|
||||
package="kiwix.org.kiwixlib"
|
||||
>
|
||||
|
||||
<application android:allowBackup="true"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
11
src/android/gen_kiwix.sh
Executable file
11
src/android/gen_kiwix.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
BUILD_PATH=$(pwd)
|
||||
|
||||
javac -d $BUILD_PATH/src/android $1 $2 $3 $4
|
||||
|
||||
cd $BUILD_PATH/src/android
|
||||
javah -jni org.kiwix.kiwixlib.JNIKiwix
|
||||
cd $BUILD_PATH
|
||||
559
src/android/kiwix.cpp
Normal file
559
src/android/kiwix.cpp
Normal file
@@ -0,0 +1,559 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <jni.h>
|
||||
#include "org_kiwix_kiwixlib_JNIKiwix.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "common/base64.h"
|
||||
#include "reader.h"
|
||||
#include "searcher.h"
|
||||
#include "unicode/putil.h"
|
||||
|
||||
#include <android/log.h>
|
||||
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "kiwix", __VA_ARGS__)
|
||||
|
||||
#include <xapian.h>
|
||||
#include <zim/article.h>
|
||||
#include <zim/error.h>
|
||||
#include <zim/file.h>
|
||||
#include <zim/zim.h>
|
||||
|
||||
/* global variables */
|
||||
kiwix::Reader* reader = NULL;
|
||||
kiwix::Searcher* searcher = NULL;
|
||||
|
||||
static pthread_mutex_t readerLock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_mutex_t searcherLock = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
/* c2jni type conversion functions */
|
||||
jboolean c2jni(const bool& val)
|
||||
{
|
||||
return val ? JNI_TRUE : JNI_FALSE;
|
||||
}
|
||||
jstring c2jni(const std::string& val, JNIEnv* env)
|
||||
{
|
||||
return env->NewStringUTF(val.c_str());
|
||||
}
|
||||
|
||||
jint c2jni(const int val)
|
||||
{
|
||||
return (jint)val;
|
||||
}
|
||||
jint c2jni(const unsigned val)
|
||||
{
|
||||
return (unsigned)val;
|
||||
}
|
||||
/* jni2c type conversion functions */
|
||||
bool jni2c(const jboolean& val)
|
||||
{
|
||||
return val == JNI_TRUE;
|
||||
}
|
||||
std::string jni2c(const jstring& val, JNIEnv* env)
|
||||
{
|
||||
return std::string(env->GetStringUTFChars(val, 0));
|
||||
}
|
||||
|
||||
int jni2c(const jint val)
|
||||
{
|
||||
return (int)val;
|
||||
}
|
||||
/* Method to deal with variable passed by reference */
|
||||
void setStringObjValue(const std::string& value, const jobject obj, JNIEnv* env)
|
||||
{
|
||||
jclass objClass = env->GetObjectClass(obj);
|
||||
jfieldID objFid = env->GetFieldID(objClass, "value", "Ljava/lang/String;");
|
||||
env->SetObjectField(obj, objFid, c2jni(value, env));
|
||||
}
|
||||
|
||||
void setIntObjValue(const int value, const jobject obj, JNIEnv* env)
|
||||
{
|
||||
jclass objClass = env->GetObjectClass(obj);
|
||||
jfieldID objFid = env->GetFieldID(objClass, "value", "I");
|
||||
env->SetIntField(obj, objFid, value);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
/* Kiwix library functions */
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwix_getMainPage(JNIEnv* env, jobject obj)
|
||||
{
|
||||
jstring url;
|
||||
|
||||
pthread_mutex_lock(&readerLock);
|
||||
if (reader != NULL) {
|
||||
try {
|
||||
std::string cUrl = reader->getMainPageUrl();
|
||||
url = c2jni(cUrl, env);
|
||||
} catch (...) {
|
||||
std::cerr << "Unable to get ZIM main page" << std::endl;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&readerLock);
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_getId(JNIEnv* env,
|
||||
jobject obj)
|
||||
{
|
||||
jstring id;
|
||||
|
||||
pthread_mutex_lock(&readerLock);
|
||||
if (reader != NULL) {
|
||||
try {
|
||||
std::string cId = reader->getId();
|
||||
id = c2jni(cId, env);
|
||||
} catch (...) {
|
||||
std::cerr << "Unable to get ZIM id" << std::endl;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&readerLock);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_getFileSize(JNIEnv* env,
|
||||
jobject obj)
|
||||
{
|
||||
jint size;
|
||||
|
||||
pthread_mutex_lock(&readerLock);
|
||||
if (reader != NULL) {
|
||||
try {
|
||||
int cSize = reader->getFileSize();
|
||||
size = c2jni(cSize);
|
||||
} catch (...) {
|
||||
std::cerr << "Unable to get ZIM file size" << std::endl;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&readerLock);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwix_getCreator(JNIEnv* env, jobject obj)
|
||||
{
|
||||
jstring creator;
|
||||
|
||||
pthread_mutex_lock(&readerLock);
|
||||
if (reader != NULL) {
|
||||
try {
|
||||
std::string cCreator = reader->getCreator();
|
||||
creator = c2jni(cCreator, env);
|
||||
} catch (...) {
|
||||
std::cerr << "Unable to get ZIM creator" << std::endl;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&readerLock);
|
||||
|
||||
return creator;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwix_getPublisher(JNIEnv* env, jobject obj)
|
||||
{
|
||||
jstring publisher;
|
||||
|
||||
pthread_mutex_lock(&readerLock);
|
||||
if (reader != NULL) {
|
||||
try {
|
||||
std::string cPublisher = reader->getPublisher();
|
||||
publisher = c2jni(cPublisher, env);
|
||||
} catch (...) {
|
||||
std::cerr << "Unable to get ZIM creator" << std::endl;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&readerLock);
|
||||
|
||||
return publisher;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_getName(JNIEnv* env,
|
||||
jobject obj)
|
||||
{
|
||||
jstring name;
|
||||
|
||||
pthread_mutex_lock(&readerLock);
|
||||
if (reader != NULL) {
|
||||
try {
|
||||
std::string cName = reader->getName();
|
||||
name = c2jni(cName, env);
|
||||
} catch (...) {
|
||||
std::cerr << "Unable to get ZIM name" << std::endl;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&readerLock);
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwix_getFavicon(JNIEnv* env, jobject obj)
|
||||
{
|
||||
jstring favicon;
|
||||
|
||||
pthread_mutex_lock(&readerLock);
|
||||
if (reader != NULL) {
|
||||
try {
|
||||
std::string cContent;
|
||||
std::string cMime;
|
||||
reader->getFavicon(cContent, cMime);
|
||||
favicon
|
||||
= c2jni(base64_encode(
|
||||
reinterpret_cast<const unsigned char*>(cContent.c_str()),
|
||||
cContent.length()),
|
||||
env);
|
||||
} catch (...) {
|
||||
std::cerr << "Unable to get ZIM favicon" << std::endl;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&readerLock);
|
||||
|
||||
return favicon;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_getDate(JNIEnv* env,
|
||||
jobject obj)
|
||||
{
|
||||
jstring date;
|
||||
|
||||
pthread_mutex_lock(&readerLock);
|
||||
if (reader != NULL) {
|
||||
try {
|
||||
std::string cDate = reader->getDate();
|
||||
date = c2jni(cDate, env);
|
||||
} catch (...) {
|
||||
std::cerr << "Unable to get ZIM date" << std::endl;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&readerLock);
|
||||
|
||||
return date;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwix_getLanguage(JNIEnv* env, jobject obj)
|
||||
{
|
||||
jstring language;
|
||||
|
||||
pthread_mutex_lock(&readerLock);
|
||||
if (reader != NULL) {
|
||||
try {
|
||||
std::string cLanguage = reader->getLanguage();
|
||||
language = c2jni(cLanguage, env);
|
||||
} catch (...) {
|
||||
std::cerr << "Unable to get ZIM language" << std::endl;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&readerLock);
|
||||
|
||||
return language;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_getMimeType(
|
||||
JNIEnv* env, jobject obj, jstring url)
|
||||
{
|
||||
jstring mimeType;
|
||||
|
||||
pthread_mutex_lock(&readerLock);
|
||||
if (reader != NULL) {
|
||||
std::string cUrl = jni2c(url, env);
|
||||
try {
|
||||
std::string cMimeType;
|
||||
reader->getMimeTypeByUrl(cUrl, cMimeType);
|
||||
mimeType = c2jni(cMimeType, env);
|
||||
} catch (...) {
|
||||
std::cerr << "Unable to get mime-type for url " << cUrl << std::endl;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&readerLock);
|
||||
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwix_loadZIM(JNIEnv* env, jobject obj, jstring path)
|
||||
{
|
||||
jboolean retVal = JNI_TRUE;
|
||||
std::string cPath = jni2c(path, env);
|
||||
|
||||
pthread_mutex_lock(&readerLock);
|
||||
try {
|
||||
if (reader != NULL) {
|
||||
delete reader;
|
||||
}
|
||||
reader = new kiwix::Reader(cPath);
|
||||
} catch (...) {
|
||||
std::cerr << "Unable to load ZIM " << cPath << std::endl;
|
||||
reader = NULL;
|
||||
retVal = JNI_FALSE;
|
||||
}
|
||||
pthread_mutex_unlock(&readerLock);
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
JNIEXPORT jbyteArray JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_getContent(
|
||||
JNIEnv* env, jobject obj, jstring url, jobject titleObj, jobject mimeTypeObj, jobject sizeObj)
|
||||
{
|
||||
/* Default values */
|
||||
setStringObjValue("", titleObj, env);
|
||||
setStringObjValue("", mimeTypeObj, env);
|
||||
setIntObjValue(0, sizeObj, env);
|
||||
jbyteArray data = env->NewByteArray(0);
|
||||
|
||||
/* Retrieve the content */
|
||||
if (reader != NULL) {
|
||||
std::string cUrl = jni2c(url, env);
|
||||
std::string cData;
|
||||
std::string cTitle;
|
||||
std::string cMimeType;
|
||||
unsigned int cSize = 0;
|
||||
|
||||
pthread_mutex_lock(&readerLock);
|
||||
try {
|
||||
if (reader->getContentByUrl(cUrl, cData, cTitle, cSize, cMimeType)) {
|
||||
data = env->NewByteArray(cSize);
|
||||
env->SetByteArrayRegion(
|
||||
data, 0, cSize, reinterpret_cast<const jbyte*>(cData.c_str()));
|
||||
setStringObjValue(cMimeType, mimeTypeObj, env);
|
||||
setStringObjValue(cTitle, titleObj, env);
|
||||
setIntObjValue(cSize, sizeObj, env);
|
||||
}
|
||||
} catch (...) {
|
||||
std::cerr << "Unable to get content for url " << cUrl << std::endl;
|
||||
}
|
||||
pthread_mutex_unlock(&readerLock);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_searchSuggestions(
|
||||
JNIEnv* env, jobject obj, jstring prefix, jint count)
|
||||
{
|
||||
jboolean retVal = JNI_FALSE;
|
||||
std::string cPrefix = jni2c(prefix, env);
|
||||
unsigned int cCount = jni2c(count);
|
||||
|
||||
pthread_mutex_lock(&readerLock);
|
||||
try {
|
||||
if (reader != NULL) {
|
||||
if (reader->searchSuggestionsSmart(cPrefix, cCount)) {
|
||||
retVal = JNI_TRUE;
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
std::cerr << "Unable to search suggestions for pattern " << cPrefix
|
||||
<< std::endl;
|
||||
}
|
||||
pthread_mutex_unlock(&readerLock);
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_getNextSuggestion(
|
||||
JNIEnv* env, jobject obj, jobject titleObj)
|
||||
{
|
||||
jboolean retVal = JNI_FALSE;
|
||||
std::string cTitle;
|
||||
|
||||
pthread_mutex_lock(&readerLock);
|
||||
try {
|
||||
if (reader != NULL) {
|
||||
if (reader->getNextSuggestion(cTitle)) {
|
||||
setStringObjValue(cTitle, titleObj, env);
|
||||
retVal = JNI_TRUE;
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
std::cerr << "Unable to get next suggestion" << std::endl;
|
||||
}
|
||||
pthread_mutex_unlock(&readerLock);
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_getPageUrlFromTitle(
|
||||
JNIEnv* env, jobject obj, jstring title, jobject urlObj)
|
||||
{
|
||||
jboolean retVal = JNI_FALSE;
|
||||
std::string cTitle = jni2c(title, env);
|
||||
std::string cUrl;
|
||||
|
||||
pthread_mutex_lock(&readerLock);
|
||||
try {
|
||||
if (reader != NULL) {
|
||||
if (reader->getPageUrlFromTitle(cTitle, cUrl)) {
|
||||
setStringObjValue(cUrl, urlObj, env);
|
||||
retVal = JNI_TRUE;
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
std::cerr << "Unable to get URL for title " << cTitle << std::endl;
|
||||
}
|
||||
pthread_mutex_unlock(&readerLock);
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_getTitle(
|
||||
JNIEnv* env, jobject obj, jobject titleObj)
|
||||
{
|
||||
jboolean retVal = JNI_FALSE;
|
||||
std::string cTitle;
|
||||
|
||||
pthread_mutex_lock(&readerLock);
|
||||
try {
|
||||
if (reader != NULL) {
|
||||
std::string cTitle = reader->getTitle();
|
||||
setStringObjValue(cTitle, titleObj, env);
|
||||
retVal = JNI_TRUE;
|
||||
}
|
||||
} catch (...) {
|
||||
std::cerr << "Unable to get ZIM title" << std::endl;
|
||||
}
|
||||
pthread_mutex_unlock(&readerLock);
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwix_getDescription(JNIEnv* env, jobject obj)
|
||||
{
|
||||
jstring description;
|
||||
|
||||
pthread_mutex_lock(&readerLock);
|
||||
if (reader != NULL) {
|
||||
try {
|
||||
std::string cDescription = reader->getDescription();
|
||||
description = c2jni(cDescription, env);
|
||||
} catch (...) {
|
||||
std::cerr << "Unable to get ZIM description" << std::endl;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&readerLock);
|
||||
|
||||
return description;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_getRandomPage(
|
||||
JNIEnv* env, jobject obj, jobject urlObj)
|
||||
{
|
||||
jboolean retVal = JNI_FALSE;
|
||||
std::string cUrl;
|
||||
|
||||
pthread_mutex_lock(&readerLock);
|
||||
try {
|
||||
if (reader != NULL) {
|
||||
std::string cUrl = reader->getRandomPageUrl();
|
||||
setStringObjValue(cUrl, urlObj, env);
|
||||
retVal = JNI_TRUE;
|
||||
}
|
||||
} catch (...) {
|
||||
std::cerr << "Unable to get random page" << std::endl;
|
||||
}
|
||||
pthread_mutex_unlock(&readerLock);
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_setDataDirectory(
|
||||
JNIEnv* env, jobject obj, jstring dirStr)
|
||||
{
|
||||
std::string cPath = jni2c(dirStr, env);
|
||||
|
||||
pthread_mutex_lock(&readerLock);
|
||||
try {
|
||||
u_setDataDirectory(cPath.c_str());
|
||||
} catch (...) {
|
||||
std::cerr << "Unable to set data directory " << cPath << std::endl;
|
||||
}
|
||||
pthread_mutex_unlock(&readerLock);
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_loadFulltextIndex(
|
||||
JNIEnv* env, jobject obj, jstring path)
|
||||
{
|
||||
jboolean retVal = JNI_TRUE;
|
||||
std::string cPath = jni2c(path, env);
|
||||
|
||||
pthread_mutex_lock(&searcherLock);
|
||||
try {
|
||||
if (searcher != NULL) {
|
||||
delete searcher;
|
||||
}
|
||||
if (!reader || !reader->hasFulltextIndex()) {
|
||||
// Use old API (no embedded full text index).
|
||||
searcher = new kiwix::Searcher(cPath, reader, "");
|
||||
} else {
|
||||
// Use the new API. We don't care about the human readable name as
|
||||
// we don't use it (in android).
|
||||
searcher = new kiwix::Searcher();
|
||||
searcher->add_reader(reader, "");
|
||||
}
|
||||
} catch (...) {
|
||||
searcher = NULL;
|
||||
retVal = JNI_FALSE;
|
||||
std::cerr << "Unable to load full text index " << cPath << std::endl;
|
||||
}
|
||||
pthread_mutex_unlock(&searcherLock);
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_indexedQuery(
|
||||
JNIEnv* env, jclass obj, jstring query, jint count)
|
||||
{
|
||||
std::string cQuery = jni2c(query, env);
|
||||
unsigned int cCount = jni2c(count);
|
||||
kiwix::Result* p_result;
|
||||
std::string result;
|
||||
|
||||
pthread_mutex_lock(&searcherLock);
|
||||
try {
|
||||
if (searcher != NULL) {
|
||||
searcher->search(cQuery, 0, count);
|
||||
while ((p_result = searcher->getNextResult())
|
||||
&& !(p_result->get_title().empty())
|
||||
&& !(p_result->get_url().empty())) {
|
||||
result += p_result->get_title() + "\n";
|
||||
delete p_result;
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
std::cerr << "Unable to make indexed query " << cQuery << std::endl;
|
||||
}
|
||||
pthread_mutex_unlock(&searcherLock);
|
||||
|
||||
return env->NewStringUTF(result.c_str());
|
||||
}
|
||||
17
src/android/meson.build
Normal file
17
src/android/meson.build
Normal file
@@ -0,0 +1,17 @@
|
||||
|
||||
jni_generator = find_program('gen_kiwix.sh')
|
||||
|
||||
kiwix_jni = custom_target('jni',
|
||||
input: ['org/kiwix/kiwixlib/JNIKiwix.java',
|
||||
'org/kiwix/kiwixlib/JNIKiwixInt.java',
|
||||
'org/kiwix/kiwixlib/JNIKiwixString.java',
|
||||
'org/kiwix/kiwixlib/JNIKiwixBool.java'],
|
||||
output: ['org_kiwix_kiwixlib_JNIKiwix.h'],
|
||||
command:[jni_generator, '@INPUT@']
|
||||
)
|
||||
|
||||
kiwix_sources += ['android/kiwix.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')
|
||||
74
src/android/org/kiwix/kiwixlib/JNIKiwix.java
Normal file
74
src/android/org/kiwix/kiwixlib/JNIKiwix.java
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package org.kiwix.kiwixlib;
|
||||
|
||||
import org.kiwix.kiwixlib.JNIKiwixString;
|
||||
import org.kiwix.kiwixlib.JNIKiwixBool;
|
||||
import org.kiwix.kiwixlib.JNIKiwixInt;
|
||||
|
||||
public class JNIKiwix
|
||||
{
|
||||
static { System.loadLibrary("kiwix"); }
|
||||
public native String getMainPage();
|
||||
|
||||
public native String getId();
|
||||
|
||||
public native String getLanguage();
|
||||
|
||||
public native String getMimeType(String url);
|
||||
|
||||
public native boolean loadZIM(String path);
|
||||
|
||||
public native boolean loadFulltextIndex(String path);
|
||||
|
||||
public native byte[] getContent(String url, JNIKiwixString title, JNIKiwixString mimeType, JNIKiwixInt size);
|
||||
|
||||
public native boolean searchSuggestions(String prefix, int count);
|
||||
|
||||
public native boolean getNextSuggestion(JNIKiwixString title);
|
||||
|
||||
public native boolean getPageUrlFromTitle(String title, JNIKiwixString url);
|
||||
|
||||
public native boolean getTitle(JNIKiwixString title);
|
||||
|
||||
public native String getDescription();
|
||||
|
||||
public native String getDate();
|
||||
|
||||
public native String getFavicon();
|
||||
|
||||
public native String getCreator();
|
||||
|
||||
public native String getPublisher();
|
||||
|
||||
public native String getName();
|
||||
|
||||
public native int getFileSize();
|
||||
|
||||
public native int getArticleCount();
|
||||
|
||||
public native int getMediaCount();
|
||||
|
||||
public native boolean getRandomPage(JNIKiwixString url);
|
||||
|
||||
public native void setDataDirectory(String icuDataDir);
|
||||
|
||||
public static native String indexedQuery(String db, int count);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
* Copyright (C) 2013 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
|
||||
@@ -17,14 +17,9 @@
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef KIWIX_NETWORKTOOLS_H
|
||||
#define KIWIX_NETWORKTOOLS_H
|
||||
package org.kiwix.kiwixlib;
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace kiwix
|
||||
public class JNIKiwixBool
|
||||
{
|
||||
std::string download(const std::string& url);
|
||||
public boolean value;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
* Copyright (C) 2013 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
|
||||
@@ -17,8 +17,9 @@
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <microhttpd.h>
|
||||
package org.kiwix.kiwixlib;
|
||||
|
||||
#if MHD_VERSION < 0x00097002
|
||||
typedef int MHD_Result;
|
||||
#endif
|
||||
public class JNIKiwixInt
|
||||
{
|
||||
public int value;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2021 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
* Copyright (C) 2013 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
|
||||
@@ -17,18 +17,9 @@
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef KIWIX_VERSION_H
|
||||
#define KIWIX_VERSION_H
|
||||
package org.kiwix.kiwixlib;
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
namespace kiwix
|
||||
public class JNIKiwixString
|
||||
{
|
||||
typedef std::vector<std::pair<std::string, std::string>> LibVersions;
|
||||
LibVersions getVersions();
|
||||
void printVersions(std::ostream& out = std::cout);
|
||||
public String value;
|
||||
}
|
||||
|
||||
#endif // KIWIX_VERSION_H
|
||||
3
src/android/res/values/strings.xml
Normal file
3
src/android/res/values/strings.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">Kiwix Lib</string>
|
||||
</resources>
|
||||
313
src/aria2.cpp
313
src/aria2.cpp
@@ -1,313 +0,0 @@
|
||||
|
||||
|
||||
#include "aria2.h"
|
||||
#include "xmlrpc.h"
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include "tools.h"
|
||||
#include "tools/pathTools.h"
|
||||
#include "tools/stringTools.h"
|
||||
#include "tools/otherTools.h"
|
||||
#include "downloader.h" // For AriaError
|
||||
|
||||
#ifdef _WIN32
|
||||
# define ARIA2_CMD "aria2c.exe"
|
||||
#else
|
||||
# define ARIA2_CMD "aria2c"
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
|
||||
#define LOG_ARIA_ERROR() \
|
||||
{ \
|
||||
std::cerr << "ERROR: aria2 RPC request failed. (" << res << ")." << std::endl; \
|
||||
std::cerr << (curlErrorBuffer[0] ? curlErrorBuffer : curl_easy_strerror(res)) << std::endl; \
|
||||
}
|
||||
|
||||
namespace kiwix {
|
||||
|
||||
namespace {
|
||||
|
||||
void pauseAnyActiveDownloads(const std::string& ariaSessionFilePath)
|
||||
{
|
||||
std::ifstream inputFile(ariaSessionFilePath);
|
||||
if ( !inputFile )
|
||||
return;
|
||||
|
||||
std::ostringstream ss;
|
||||
std::string line;
|
||||
while ( std::getline(inputFile, line) ) {
|
||||
if ( !startsWith(line, " pause=") ) {
|
||||
ss << line << "\n";
|
||||
}
|
||||
if ( !line.empty() && line[0] != ' ' && line[0] != '#' ) {
|
||||
ss << " pause=true\n";
|
||||
}
|
||||
}
|
||||
|
||||
std::ofstream outputFile(ariaSessionFilePath);
|
||||
outputFile << ss.str();
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
Aria2::Aria2(std::string sessionFileDir):
|
||||
mp_aria(nullptr),
|
||||
m_port(42042),
|
||||
m_secret(getNewRpcSecret())
|
||||
{
|
||||
std::vector<const char*> callCmd;
|
||||
|
||||
std::string rpc_port = "--rpc-listen-port=" + to_string(m_port);
|
||||
std::string session_file = appendToDirectory(sessionFileDir, "kiwix.session");
|
||||
pauseAnyActiveDownloads(session_file);
|
||||
std::string session = "--save-session=" + session_file;
|
||||
std::string inputFile = "--input-file=" + session_file;
|
||||
// std::string log_dir = "--log=\"" + logDir + "\"";
|
||||
#ifdef _WIN32
|
||||
int pid = GetCurrentProcessId();
|
||||
#else
|
||||
pid_t pid = getpid();
|
||||
#endif
|
||||
std::string stop_with_pid = "--stop-with-process=" + to_string(pid);
|
||||
std::string rpc_secret = "--rpc-secret=" + m_secret;
|
||||
m_secret = "token:"+m_secret;
|
||||
|
||||
std::string aria2cmd = appendToDirectory(
|
||||
removeLastPathElement(getExecutablePath(true)),
|
||||
ARIA2_CMD);
|
||||
if (fileExists(aria2cmd)) {
|
||||
// A local aria2c exe exists (packaged with kiwix-desktop), use it.
|
||||
callCmd.push_back(aria2cmd.c_str());
|
||||
} else {
|
||||
// Try to use a potential installed aria2c.
|
||||
callCmd.push_back(ARIA2_CMD);
|
||||
}
|
||||
callCmd.push_back("--follow-metalink=mem");
|
||||
callCmd.push_back("--enable-rpc");
|
||||
callCmd.push_back(rpc_secret.c_str());
|
||||
callCmd.push_back(rpc_port.c_str());
|
||||
if (fileReadable(session_file)) {
|
||||
callCmd.push_back(inputFile.c_str());
|
||||
}
|
||||
callCmd.push_back(session.c_str());
|
||||
// callCmd.push_back(log_dir.c_str());
|
||||
callCmd.push_back("--auto-save-interval=10");
|
||||
callCmd.push_back(stop_with_pid.c_str());
|
||||
callCmd.push_back("--allow-overwrite=true");
|
||||
callCmd.push_back("--dht-entry-point=router.bittorrent.com:6881");
|
||||
callCmd.push_back("--dht-entry-point6=router.bittorrent.com:6881");
|
||||
callCmd.push_back("--quiet=true");
|
||||
callCmd.push_back("--bt-enable-lpd=true");
|
||||
callCmd.push_back("--always-resume=true");
|
||||
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) {
|
||||
launchCmd.append(cmd).append(" ");
|
||||
}
|
||||
mp_aria = Subprocess::run(callCmd);
|
||||
|
||||
CURL* p_curl = curl_easy_init();
|
||||
char curlErrorBuffer[CURL_ERROR_SIZE];
|
||||
|
||||
curl_easy_setopt(p_curl, CURLOPT_URL, "http://localhost/rpc");
|
||||
curl_easy_setopt(p_curl, CURLOPT_PORT, m_port);
|
||||
curl_easy_setopt(p_curl, CURLOPT_POST, 1L);
|
||||
curl_easy_setopt(p_curl, CURLOPT_ERRORBUFFER, curlErrorBuffer);
|
||||
curl_easy_setopt(p_curl, CURLOPT_TIMEOUT_MS, 100);
|
||||
|
||||
typedef std::chrono::duration<double> Seconds;
|
||||
|
||||
const double MAX_WAITING_TIME_SECONDS = 1;
|
||||
const auto t0 = std::chrono::steady_clock::now();
|
||||
bool maxWaitingTimeWasExceeded = false;
|
||||
|
||||
CURLcode res = CURLE_OK;
|
||||
while ( !maxWaitingTimeWasExceeded ) {
|
||||
sleep(10);
|
||||
curlErrorBuffer[0] = 0;
|
||||
res = curl_easy_perform(p_curl);
|
||||
if (res == CURLE_OK) {
|
||||
break;
|
||||
}
|
||||
|
||||
const auto dt = std::chrono::steady_clock::now() - t0;
|
||||
const double elapsedTime = std::chrono::duration_cast<Seconds>(dt).count();
|
||||
maxWaitingTimeWasExceeded = elapsedTime > MAX_WAITING_TIME_SECONDS;
|
||||
}
|
||||
curl_easy_cleanup(p_curl);
|
||||
if ( maxWaitingTimeWasExceeded ) {
|
||||
LOG_ARIA_ERROR();
|
||||
throw std::runtime_error("Cannot connect to aria2c rpc. Aria2c launch cmd : " + launchCmd);
|
||||
}
|
||||
}
|
||||
|
||||
void Aria2::close()
|
||||
{
|
||||
saveSession();
|
||||
shutdown();
|
||||
}
|
||||
|
||||
size_t write_callback_to_iss(char* ptr, size_t size, size_t nmemb, void* userdata)
|
||||
{
|
||||
auto outStream = static_cast<std::stringstream*>(userdata);
|
||||
outStream->write(ptr, nmemb);
|
||||
return nmemb;
|
||||
}
|
||||
|
||||
std::string Aria2::doRequest(const MethodCall& methodCall)
|
||||
{
|
||||
auto requestContent = methodCall.toString();
|
||||
std::stringstream outStream;
|
||||
CURLcode res;
|
||||
long response_code;
|
||||
char curlErrorBuffer[CURL_ERROR_SIZE];
|
||||
CURL* p_curl = curl_easy_init();
|
||||
curl_easy_setopt(p_curl, CURLOPT_URL, "http://localhost/rpc");
|
||||
curl_easy_setopt(p_curl, CURLOPT_PORT, m_port);
|
||||
curl_easy_setopt(p_curl, CURLOPT_POST, 1L);
|
||||
curl_easy_setopt(p_curl, CURLOPT_ERRORBUFFER, curlErrorBuffer);
|
||||
curl_easy_setopt(p_curl, CURLOPT_POSTFIELDSIZE, requestContent.size());
|
||||
curl_easy_setopt(p_curl, CURLOPT_POSTFIELDS, requestContent.c_str());
|
||||
curl_easy_setopt(p_curl, CURLOPT_WRITEFUNCTION, &write_callback_to_iss);
|
||||
curl_easy_setopt(p_curl, CURLOPT_WRITEDATA, &outStream);
|
||||
curlErrorBuffer[0] = 0;
|
||||
res = curl_easy_perform(p_curl);
|
||||
if (res != CURLE_OK) {
|
||||
LOG_ARIA_ERROR();
|
||||
curl_easy_cleanup(p_curl);
|
||||
throw std::runtime_error("Cannot perform request");
|
||||
}
|
||||
curl_easy_getinfo(p_curl, CURLINFO_RESPONSE_CODE, &response_code);
|
||||
curl_easy_cleanup(p_curl);
|
||||
|
||||
auto responseContent = outStream.str();
|
||||
if (response_code != 200) {
|
||||
std::cerr << "ERROR: Invalid return code (" << response_code << ") from aria :" << std::endl;
|
||||
std::cerr << responseContent << std::endl;
|
||||
throw std::runtime_error("Invalid return code from aria");
|
||||
}
|
||||
|
||||
MethodResponse response(responseContent);
|
||||
if (response.isFault()) {
|
||||
throw AriaError(response.getFault().getFaultString());
|
||||
}
|
||||
return responseContent;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
std::string Aria2::tellStatus(const std::string& gid, const std::vector<std::string>& statusKey)
|
||||
{
|
||||
MethodCall methodCall("aria2.tellStatus", m_secret);
|
||||
methodCall.newParamValue().set(gid);
|
||||
if (!statusKey.empty()) {
|
||||
auto statusArray = methodCall.newParamValue().getArray();
|
||||
for (auto& key : statusKey) {
|
||||
statusArray.addValue().set(key);
|
||||
}
|
||||
}
|
||||
return doRequest(methodCall);
|
||||
}
|
||||
|
||||
std::string Aria2::getNewRpcSecret()
|
||||
{
|
||||
std::string uuid = gen_uuid("");
|
||||
uuid.erase(std::remove(uuid.begin(), uuid.end(), '-'));
|
||||
return uuid.substr(0, 9);
|
||||
}
|
||||
|
||||
std::vector<std::string> Aria2::tellActive()
|
||||
{
|
||||
MethodCall methodCall("aria2.tellActive", m_secret);
|
||||
auto statusArray = methodCall.newParamValue().getArray();
|
||||
statusArray.addValue().set(std::string("gid"));
|
||||
auto responseContent = doRequest(methodCall);
|
||||
MethodResponse response(responseContent);
|
||||
std::vector<std::string> activeGID;
|
||||
int index = 0;
|
||||
while(true) {
|
||||
try {
|
||||
auto structNode = response.getParamValue(0).getArray().getValue(index++).getStruct();
|
||||
auto gidNode = structNode.getMember("gid");
|
||||
activeGID.push_back(gidNode.getValue().getAsS());
|
||||
} catch (InvalidRPCNode& e) { break; }
|
||||
}
|
||||
return activeGID;
|
||||
}
|
||||
|
||||
std::vector<std::string> Aria2::tellWaiting()
|
||||
{
|
||||
MethodCall methodCall("aria2.tellWaiting", m_secret);
|
||||
methodCall.newParamValue().set(0);
|
||||
methodCall.newParamValue().set(99); // max number of downloads to be returned, don't know how to set this properly assumed that there will not be more than 99 paused downloads.
|
||||
auto statusArray = methodCall.newParamValue().getArray();
|
||||
statusArray.addValue().set(std::string("gid"));
|
||||
auto responseContent = doRequest(methodCall);
|
||||
MethodResponse response(responseContent);
|
||||
std::vector<std::string> waitingGID;
|
||||
int index = 0;
|
||||
while(true) {
|
||||
try {
|
||||
auto structNode = response.getParamValue(0).getArray().getValue(index++).getStruct();
|
||||
auto gidNode = structNode.getMember("gid");
|
||||
waitingGID.push_back(gidNode.getValue().getAsS());
|
||||
} catch (InvalidRPCNode& e) { break; }
|
||||
}
|
||||
return waitingGID;
|
||||
}
|
||||
|
||||
void Aria2::saveSession()
|
||||
{
|
||||
MethodCall methodCall("aria2.saveSession", m_secret);
|
||||
doRequest(methodCall);
|
||||
std::cout << "session saved" << std::endl;
|
||||
}
|
||||
|
||||
void Aria2::shutdown()
|
||||
{
|
||||
MethodCall methodCall("aria2.shutdown", m_secret);
|
||||
doRequest(methodCall);
|
||||
}
|
||||
|
||||
void Aria2::pause(const std::string& gid)
|
||||
{
|
||||
MethodCall methodCall("aria2.pause", m_secret);
|
||||
methodCall.newParamValue().set(gid);
|
||||
doRequest(methodCall);
|
||||
}
|
||||
|
||||
void Aria2::unpause(const std::string& gid)
|
||||
{
|
||||
MethodCall methodCall("aria2.unpause", m_secret);
|
||||
methodCall.newParamValue().set(gid);
|
||||
doRequest(methodCall);
|
||||
}
|
||||
|
||||
void Aria2::remove(const std::string& gid)
|
||||
{
|
||||
MethodCall methodCall("aria2.remove", m_secret);
|
||||
methodCall.newParamValue().set(gid);
|
||||
doRequest(methodCall);
|
||||
}
|
||||
|
||||
} // end namespace kiwix
|
||||
46
src/aria2.h
46
src/aria2.h
@@ -1,46 +0,0 @@
|
||||
|
||||
|
||||
#ifndef KIWIXLIB_ARIA2_H_
|
||||
#define KIWIXLIB_ARIA2_H_
|
||||
|
||||
#ifdef _WIN32
|
||||
// winsock2.h need to be included before windows.h (included by curl.h)
|
||||
# include <winsock2.h>
|
||||
#endif
|
||||
|
||||
#include "subprocess.h"
|
||||
#include "xmlrpc.h"
|
||||
|
||||
#include <memory>
|
||||
#include <curl/curl.h>
|
||||
|
||||
namespace kiwix {
|
||||
|
||||
class Aria2
|
||||
{
|
||||
private:
|
||||
std::unique_ptr<Subprocess> mp_aria;
|
||||
int m_port;
|
||||
std::string m_secret;
|
||||
std::string doRequest(const MethodCall& methodCall);
|
||||
|
||||
public:
|
||||
explicit Aria2(std::string sessionFileDir);
|
||||
virtual ~Aria2() = default;
|
||||
void close();
|
||||
|
||||
std::string addUri(const std::vector<std::string>& uri, const std::vector<std::pair<std::string, std::string>>& options = {});
|
||||
std::string tellStatus(const std::string& gid, const std::vector<std::string>& statusKey);
|
||||
static std::string getNewRpcSecret();
|
||||
std::vector<std::string> tellActive();
|
||||
std::vector<std::string> tellWaiting();
|
||||
void saveSession();
|
||||
void shutdown();
|
||||
void pause(const std::string& gid);
|
||||
void unpause(const std::string& gid);
|
||||
void remove(const std::string& gid);
|
||||
};
|
||||
|
||||
}; //end namespace kiwix
|
||||
|
||||
#endif // KIWIXLIB_ARIA2_H_
|
||||
296
src/book.cpp
296
src/book.cpp
@@ -1,296 +0,0 @@
|
||||
/*
|
||||
* Copyright 2011 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "book.h"
|
||||
|
||||
#include "tools.h"
|
||||
#include "tools/base64.h"
|
||||
#include "tools/regexTools.h"
|
||||
#include "tools/networkTools.h"
|
||||
#include "tools/otherTools.h"
|
||||
#include "tools/stringTools.h"
|
||||
#include "tools/pathTools.h"
|
||||
#include "tools/archiveTools.h"
|
||||
|
||||
#include <zim/archive.h>
|
||||
#include <zim/item.h>
|
||||
#include <pugixml.hpp>
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
/* Constructor */
|
||||
Book::Book() :
|
||||
m_pathValid(false),
|
||||
m_readOnly(false)
|
||||
{
|
||||
}
|
||||
|
||||
/* Destructor */
|
||||
Book::~Book()
|
||||
{
|
||||
}
|
||||
|
||||
Book::Illustrations Book::getIllustrations() const
|
||||
{
|
||||
return m_illustrations;
|
||||
}
|
||||
|
||||
bool Book::update(const kiwix::Book& other)
|
||||
{
|
||||
if (m_readOnly)
|
||||
return false;
|
||||
|
||||
if (m_id != other.m_id)
|
||||
return false;
|
||||
|
||||
*this = other;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Book::update(const zim::Archive& archive) {
|
||||
m_path = archive.getFilename();
|
||||
m_pathValid = true;
|
||||
m_id = std::string(archive.getUuid());
|
||||
m_title = getArchiveTitle(archive);
|
||||
m_description = getMetaDescription(archive);
|
||||
m_language = getMetaLanguage(archive);
|
||||
m_creator = getMetaCreator(archive);
|
||||
m_publisher = getMetaPublisher(archive);
|
||||
m_date = getMetaDate(archive);
|
||||
m_name = getMetaName(archive);
|
||||
m_flavour = getMetaFlavour(archive);
|
||||
m_tags = getMetaTags(archive);
|
||||
m_category = getCategoryFromTags();
|
||||
m_articleCount = archive.getArticleCount();
|
||||
m_mediaCount = archive.getMediaCount();
|
||||
m_size = static_cast<uint64_t>(getArchiveFileSize(archive)) << 10;
|
||||
|
||||
m_illustrations.clear();
|
||||
for ( const auto& illustrationInfo : archive.getIllustrationInfos() ) {
|
||||
const auto illustration = std::make_shared<Illustration>();
|
||||
const zim::Item illustrationItem = archive.getIllustrationItem(illustrationInfo);
|
||||
illustration->width = illustrationInfo.width;
|
||||
illustration->height = illustrationInfo.height;
|
||||
illustration->mimeType = illustrationItem.getMimetype();
|
||||
illustration->data = illustrationItem.getData();
|
||||
// NOTE: illustration->url is left uninitialized
|
||||
m_illustrations.push_back(illustration);
|
||||
}
|
||||
}
|
||||
|
||||
#define ATTR(name) node.attribute(name).value()
|
||||
void Book::updateFromXml(const pugi::xml_node& node, const std::string& baseDir)
|
||||
{
|
||||
m_id = ATTR("id");
|
||||
std::string path = ATTR("path");
|
||||
if (isRelativePath(path)) {
|
||||
path = computeAbsolutePath(baseDir, path);
|
||||
}
|
||||
m_path = path;
|
||||
m_pathValid = fileReadable(path);
|
||||
m_title = ATTR("title");
|
||||
m_description = ATTR("description");
|
||||
m_language = ATTR("language");
|
||||
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;
|
||||
const std::string faviconMimeType = ATTR("faviconMimeType");
|
||||
const std::string faviconBase64EncodedData = ATTR("favicon");
|
||||
if ( !faviconMimeType.empty() && !faviconBase64EncodedData.empty() ) {
|
||||
const auto favicon = std::make_shared<Illustration>();
|
||||
favicon->data = base64_decode(faviconBase64EncodedData);
|
||||
favicon->mimeType = faviconMimeType;
|
||||
favicon->url = ATTR("faviconUrl");
|
||||
m_illustrations.assign(1, favicon);
|
||||
}
|
||||
try {
|
||||
m_downloadId = ATTR("downloadId");
|
||||
} catch(...) {}
|
||||
const auto catattr = node.attribute("category");
|
||||
m_category = catattr.empty() ? getCategoryFromTags() : catattr.value();
|
||||
}
|
||||
#undef ATTR
|
||||
|
||||
|
||||
static std::string fromOpdsDate(const std::string& date)
|
||||
{
|
||||
//The opds date use the standard <YYYY>-<MM>-<DD>T<HH>:<mm>:<SS>Z
|
||||
//and we want <YYYY>-<MM>-<DD>. That's easy, let's take the first 10 char
|
||||
return date.substr(0, 10);
|
||||
}
|
||||
|
||||
|
||||
#define VALUE(name) node.child(name).child_value()
|
||||
void Book::updateFromOpds(const pugi::xml_node& node, const std::string& urlHost)
|
||||
{
|
||||
m_id = VALUE("id");
|
||||
if (!m_id.compare(0, 9, "urn:uuid:")) {
|
||||
m_id.erase(0, 9);
|
||||
}
|
||||
// No path on opds.
|
||||
m_title = VALUE("title");
|
||||
m_description = VALUE("summary");
|
||||
m_language = VALUE("language");
|
||||
m_creator = node.child("author").child("name").child_value();
|
||||
m_publisher = node.child("publisher").child("name").child_value();
|
||||
const std::string dcIssuedDate = VALUE("dc:issued");
|
||||
m_date = dcIssuedDate.empty() ? VALUE("updated") : dcIssuedDate;
|
||||
m_date = fromOpdsDate(m_date);
|
||||
m_name = VALUE("name");
|
||||
m_flavour = VALUE("flavour");
|
||||
m_tags = VALUE("tags");
|
||||
const auto catnode = node.child("category");
|
||||
m_category = catnode.empty() ? getCategoryFromTags() : catnode.child_value();
|
||||
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();
|
||||
|
||||
if (rel == "http://opds-spec.org/acquisition/open-access") {
|
||||
m_url = linkNode.attribute("href").value();
|
||||
m_size = strtoull(linkNode.attribute("length").value(), 0, 0);
|
||||
}
|
||||
if (rel == "http://opds-spec.org/image/thumbnail") {
|
||||
const auto favicon = std::make_shared<Illustration>();
|
||||
favicon->data.clear();
|
||||
favicon->url = urlHost + linkNode.attribute("href").value();
|
||||
favicon->mimeType = linkNode.attribute("type").value();
|
||||
m_illustrations.assign(1, favicon);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#undef VALUE
|
||||
|
||||
std::string Book::getHumanReadableIdFromPath() const
|
||||
{
|
||||
std::string id = m_path;
|
||||
if (!id.empty()) {
|
||||
id = kiwix::removeAccents(id);
|
||||
|
||||
#ifdef _WIN32
|
||||
id = replaceRegex(id, "", "^.*\\\\");
|
||||
#else
|
||||
id = replaceRegex(id, "", "^.*/");
|
||||
#endif
|
||||
|
||||
id = replaceRegex(id, "", "\\.zim[a-z]*$");
|
||||
id = replaceRegex(id, "_", " ");
|
||||
id = replaceRegex(id, "plus", "\\+");
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
void Book::setPath(const std::string& path)
|
||||
{
|
||||
m_path = isRelativePath(path)
|
||||
? computeAbsolutePath(getCurrentDirectory(), path)
|
||||
: path;
|
||||
}
|
||||
|
||||
const Book::Illustration Book::missingDefaultIllustration;
|
||||
|
||||
std::shared_ptr<const Book::Illustration> Book::getIllustration(unsigned int size) const
|
||||
{
|
||||
for ( const auto& ilPtr : m_illustrations ) {
|
||||
if (ilPtr->width == size && ilPtr->height == size) {
|
||||
return ilPtr;
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("Cannot find illustration");
|
||||
}
|
||||
|
||||
const Book::Illustration& Book::getDefaultIllustration() const
|
||||
{
|
||||
try {
|
||||
return *getIllustration(48);
|
||||
} catch (...) {
|
||||
return missingDefaultIllustration;
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& Book::Illustration::getData() const
|
||||
{
|
||||
if (data.empty() && !url.empty()) {
|
||||
const std::lock_guard<std::mutex> l(mutex);
|
||||
if ( data.empty() ) {
|
||||
try {
|
||||
data = download(url);
|
||||
} catch(...) {
|
||||
std::cerr << "Cannot download favicon from " << url;
|
||||
}
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
const std::string& Book::getFavicon() const {
|
||||
return getDefaultIllustration().getData();
|
||||
}
|
||||
|
||||
const std::string& Book::getFaviconUrl() const
|
||||
{
|
||||
return getDefaultIllustration().url;
|
||||
}
|
||||
|
||||
const std::string& Book::getFaviconMimeType() const
|
||||
{
|
||||
return getDefaultIllustration().mimeType;
|
||||
}
|
||||
|
||||
std::string Book::getTagStr(const std::string& tagName) const {
|
||||
return getTagValueFromTagList(convertTags(m_tags), tagName);
|
||||
}
|
||||
|
||||
bool Book::getTagBool(const std::string& tagName) const {
|
||||
return convertStrToBool(getTagStr(tagName));
|
||||
}
|
||||
|
||||
std::string Book::getCategory() const
|
||||
{
|
||||
return m_category;
|
||||
}
|
||||
|
||||
std::string Book::getCategoryFromTags() const
|
||||
{
|
||||
try
|
||||
{
|
||||
return getTagStr("category");
|
||||
}
|
||||
catch ( const std::out_of_range& )
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<std::string> Book::getLanguages() const
|
||||
{
|
||||
return kiwix::split(m_language, ",");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 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 "bookmark.h"
|
||||
#include "book.h"
|
||||
|
||||
#include <pugixml.hpp>
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
/* Constructor */
|
||||
Bookmark::Bookmark()
|
||||
{
|
||||
}
|
||||
|
||||
Bookmark::Bookmark(const Book& book, const std::string& path, const std::string& title):
|
||||
m_bookId(book.getId()),
|
||||
m_bookTitle(book.getTitle()),
|
||||
m_bookName(book.getName()),
|
||||
m_bookFlavour(book.getFlavour()),
|
||||
m_url(path),
|
||||
m_title(title),
|
||||
m_language(book.getCommaSeparatedLanguages()),
|
||||
m_date(book.getDate())
|
||||
{}
|
||||
|
||||
/* Destructor */
|
||||
Bookmark::~Bookmark()
|
||||
{
|
||||
}
|
||||
|
||||
void Bookmark::updateFromXml(const pugi::xml_node& node)
|
||||
{
|
||||
auto bookNode = node.child("book");
|
||||
m_bookId = bookNode.child("id").child_value();
|
||||
m_bookTitle = bookNode.child("title").child_value();
|
||||
m_bookName = bookNode.child("name").child_value();
|
||||
m_bookFlavour = bookNode.child("flavour").child_value();
|
||||
m_language = bookNode.child("language").child_value();
|
||||
m_date = bookNode.child("date").child_value();
|
||||
m_title = node.child("title").child_value();
|
||||
m_url = node.child("url").child_value();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
base64.cpp and base64.h
|
||||
|
||||
Copyright (C) 2004-2008 René Nyffenegger
|
||||
@@ -24,10 +24,10 @@
|
||||
René Nyffenegger rene.nyffenegger@adp-gmbh.ch
|
||||
*/
|
||||
|
||||
#include <tools/base64.h>
|
||||
#include <common/base64.h>
|
||||
#include <iostream>
|
||||
|
||||
static const std::string base64_chars =
|
||||
static const std::string base64_chars =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789+/";
|
||||
@@ -37,10 +37,8 @@ static inline bool is_base64(unsigned char c) {
|
||||
return (isalnum(c) || (c == '+') || (c == '/'));
|
||||
}
|
||||
|
||||
std::string base64_encode(const std::string& inString) {
|
||||
std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) {
|
||||
std::string ret;
|
||||
auto in_len = inString.size();
|
||||
const unsigned char* bytes_to_encode = reinterpret_cast<const unsigned char*>(inString.data());
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
unsigned char char_array_3[3];
|
||||
160
src/common/networkTools.cpp
Normal file
160
src/common/networkTools.cpp
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright 2012 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <common/networkTools.h>
|
||||
|
||||
std::map<std::string, std::string> kiwix::getNetworkInterfaces()
|
||||
{
|
||||
std::map<std::string, std::string> interfaces;
|
||||
|
||||
#ifdef _WIN32
|
||||
SOCKET sd = WSASocket(AF_INET, SOCK_DGRAM, 0, 0, 0, 0);
|
||||
if (sd == SOCKET_ERROR) {
|
||||
std::cerr << "Failed to get a socket. Error " << WSAGetLastError()
|
||||
<< std::endl;
|
||||
return interfaces;
|
||||
}
|
||||
|
||||
INTERFACE_INFO InterfaceList[20];
|
||||
unsigned long nBytesReturned;
|
||||
if (WSAIoctl(sd,
|
||||
SIO_GET_INTERFACE_LIST,
|
||||
0,
|
||||
0,
|
||||
&InterfaceList,
|
||||
sizeof(InterfaceList),
|
||||
&nBytesReturned,
|
||||
0,
|
||||
0)
|
||||
== SOCKET_ERROR) {
|
||||
std::cerr << "Failed calling WSAIoctl: error " << WSAGetLastError()
|
||||
<< std::endl;
|
||||
return interfaces;
|
||||
}
|
||||
|
||||
int nNumInterfaces = nBytesReturned / sizeof(INTERFACE_INFO);
|
||||
for (int i = 0; i < nNumInterfaces; ++i) {
|
||||
sockaddr_in* pAddress;
|
||||
pAddress = (sockaddr_in*)&(InterfaceList[i].iiAddress);
|
||||
|
||||
/* Add to the map */
|
||||
std::string interfaceName = std::string(inet_ntoa(pAddress->sin_addr));
|
||||
std::string interfaceIp = std::string(inet_ntoa(pAddress->sin_addr));
|
||||
interfaces.insert(
|
||||
std::pair<std::string, std::string>(interfaceName, interfaceIp));
|
||||
}
|
||||
#else
|
||||
/* Get Network interfaces information */
|
||||
char buf[16384];
|
||||
struct ifconf ifconf;
|
||||
int fd = socket(PF_INET, SOCK_DGRAM, 0); /* Only IPV4 */
|
||||
ifconf.ifc_len = sizeof buf;
|
||||
ifconf.ifc_buf = buf;
|
||||
if (ioctl(fd, SIOCGIFCONF, &ifconf) != 0) {
|
||||
perror("ioctl(SIOCGIFCONF)");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* Go through each interface */
|
||||
int i;
|
||||
size_t len;
|
||||
struct ifreq* ifreq;
|
||||
ifreq = ifconf.ifc_req;
|
||||
for (i = 0; i < ifconf.ifc_len;) {
|
||||
if (ifreq->ifr_addr.sa_family == AF_INET) {
|
||||
/* Get the network interface ip */
|
||||
char host[128] = {0};
|
||||
const int error = getnameinfo(&(ifreq->ifr_addr),
|
||||
sizeof ifreq->ifr_addr,
|
||||
host,
|
||||
sizeof host,
|
||||
0,
|
||||
0,
|
||||
NI_NUMERICHOST);
|
||||
if (!error) {
|
||||
std::string interfaceName = std::string(ifreq->ifr_name);
|
||||
std::string interfaceIp = std::string(host);
|
||||
/* Add to the map */
|
||||
interfaces.insert(
|
||||
std::pair<std::string, std::string>(interfaceName, interfaceIp));
|
||||
} else {
|
||||
perror("getnameinfo()");
|
||||
}
|
||||
}
|
||||
|
||||
/* some systems have ifr_addr.sa_len and adjust the length that
|
||||
* way, but not mine. weird */
|
||||
#ifndef __linux__
|
||||
len = IFNAMSIZ + ifreq->ifr_addr.sa_len;
|
||||
#else
|
||||
len = sizeof *ifreq;
|
||||
#endif
|
||||
ifreq = (struct ifreq*)((char*)ifreq + len);
|
||||
i += len;
|
||||
}
|
||||
#endif
|
||||
return interfaces;
|
||||
}
|
||||
|
||||
std::string kiwix::getBestPublicIp()
|
||||
{
|
||||
std::map<std::string, std::string> interfaces = kiwix::getNetworkInterfaces();
|
||||
|
||||
#ifndef _WIN32
|
||||
const char* const prioritizedNames[]
|
||||
= {"eth0", "eth1", "wlan0", "wlan1", "en0", "en1"};
|
||||
const int count = (sizeof prioritizedNames) / (sizeof prioritizedNames[0]);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
std::map<std::string, std::string>::const_iterator it
|
||||
= interfaces.find(prioritizedNames[i]);
|
||||
if (it != interfaces.end()) {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
for (std::map<std::string, std::string>::iterator iter = interfaces.begin();
|
||||
iter != interfaces.end();
|
||||
++iter) {
|
||||
std::string interfaceIp = iter->second;
|
||||
if (interfaceIp.length() >= 7 && interfaceIp.substr(0, 7) == "192.168") {
|
||||
return interfaceIp;
|
||||
}
|
||||
}
|
||||
|
||||
for (std::map<std::string, std::string>::iterator iter = interfaces.begin();
|
||||
iter != interfaces.end();
|
||||
++iter) {
|
||||
std::string interfaceIp = iter->second;
|
||||
if (interfaceIp.length() >= 7 && interfaceIp.substr(0, 7) == "172.16.") {
|
||||
return interfaceIp;
|
||||
}
|
||||
}
|
||||
|
||||
for (std::map<std::string, std::string>::iterator iter = interfaces.begin();
|
||||
iter != interfaces.end();
|
||||
++iter) {
|
||||
std::string interfaceIp = iter->second;
|
||||
if (interfaceIp.length() >= 3 && interfaceIp.substr(0, 3) == "10.") {
|
||||
return interfaceIp;
|
||||
}
|
||||
}
|
||||
|
||||
return "127.0.0.1";
|
||||
}
|
||||
29
src/common/otherTools.cpp
Normal file
29
src/common/otherTools.cpp
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2014 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <common/otherTools.h>
|
||||
|
||||
void kiwix::sleep(unsigned int milliseconds)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
Sleep(milliseconds);
|
||||
#else
|
||||
usleep(1000 * milliseconds);
|
||||
#endif
|
||||
}
|
||||
274
src/common/pathTools.cpp
Normal file
274
src/common/pathTools.cpp
Normal file
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
* Copyright 2011-2014 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <common/pathTools.h>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <limits.h>
|
||||
#include <mach-o/dyld.h>
|
||||
#elif _WIN32
|
||||
#include <direct.h>
|
||||
#include <windows.h>
|
||||
#include "shlwapi.h"
|
||||
#define getcwd _getcwd // stupid MSFT "deprecation" warning
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#define SEPARATOR "\\"
|
||||
#else
|
||||
#define SEPARATOR "/"
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifndef PATH_MAX
|
||||
#define PATH_MAX 1024
|
||||
#endif
|
||||
|
||||
bool isRelativePath(const string& path)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return path.empty() || path.substr(1, 2) == ":\\" ? false : true;
|
||||
#else
|
||||
return path.empty() || path.substr(0, 1) == "/" ? false : true;
|
||||
#endif
|
||||
}
|
||||
|
||||
string computeRelativePath(const string path, const string absolutePath)
|
||||
{
|
||||
std::vector<std::string> pathParts = kiwix::split(path, SEPARATOR);
|
||||
std::vector<std::string> absolutePathParts
|
||||
= kiwix::split(absolutePath, SEPARATOR);
|
||||
|
||||
unsigned int commonCount = 0;
|
||||
while (commonCount < pathParts.size()
|
||||
&& commonCount < absolutePathParts.size()
|
||||
&& pathParts[commonCount] == absolutePathParts[commonCount]) {
|
||||
if (!pathParts[commonCount].empty()) {
|
||||
commonCount++;
|
||||
}
|
||||
}
|
||||
|
||||
string relativePath;
|
||||
#ifdef _WIN32
|
||||
/* On Windows you have a token more because the root is represented
|
||||
by a letter */
|
||||
if (commonCount == 0) {
|
||||
relativePath = "../";
|
||||
}
|
||||
#endif
|
||||
|
||||
for (unsigned int i = commonCount; i < pathParts.size(); i++) {
|
||||
relativePath += "../";
|
||||
}
|
||||
for (unsigned int i = commonCount; i < absolutePathParts.size(); i++) {
|
||||
relativePath += absolutePathParts[i];
|
||||
relativePath += i + 1 < absolutePathParts.size() ? "/" : "";
|
||||
}
|
||||
|
||||
return relativePath;
|
||||
}
|
||||
|
||||
/* Warning: the relative path must be with slashes */
|
||||
string computeAbsolutePath(const string path, const string relativePath)
|
||||
{
|
||||
string absolutePath;
|
||||
|
||||
if (path.empty()) {
|
||||
char* path = NULL;
|
||||
size_t size = 0;
|
||||
|
||||
#ifdef _WIN32
|
||||
path = _getcwd(path, size);
|
||||
#else
|
||||
path = getcwd(path, size);
|
||||
#endif
|
||||
|
||||
absolutePath = string(path) + SEPARATOR;
|
||||
} else {
|
||||
absolutePath = path.substr(path.length() - 1, 1) == SEPARATOR
|
||||
? path
|
||||
: path + SEPARATOR;
|
||||
}
|
||||
|
||||
#if _WIN32
|
||||
char* cRelativePath = _strdup(relativePath.c_str());
|
||||
#else
|
||||
char* cRelativePath = strdup(relativePath.c_str());
|
||||
#endif
|
||||
char* token = strtok(cRelativePath, "/");
|
||||
|
||||
while (token != NULL) {
|
||||
if (string(token) == "..") {
|
||||
absolutePath = removeLastPathElement(absolutePath, true, false);
|
||||
token = strtok(NULL, "/");
|
||||
} else if (strcmp(token, ".") && strcmp(token, "")) {
|
||||
absolutePath += string(token);
|
||||
token = strtok(NULL, "/");
|
||||
if (token != NULL) {
|
||||
absolutePath += SEPARATOR;
|
||||
}
|
||||
} else {
|
||||
token = strtok(NULL, "/");
|
||||
}
|
||||
}
|
||||
|
||||
return absolutePath;
|
||||
}
|
||||
|
||||
string removeLastPathElement(const string path,
|
||||
const bool removePreSeparator,
|
||||
const bool removePostSeparator)
|
||||
{
|
||||
string newPath = path;
|
||||
size_t offset = newPath.find_last_of(SEPARATOR);
|
||||
if (removePreSeparator &&
|
||||
#ifndef _WIN32
|
||||
offset != newPath.find_first_of(SEPARATOR) &&
|
||||
#endif
|
||||
offset == newPath.length() - 1) {
|
||||
newPath = newPath.substr(0, offset);
|
||||
offset = newPath.find_last_of(SEPARATOR);
|
||||
}
|
||||
newPath = removePostSeparator ? newPath.substr(0, offset)
|
||||
: newPath.substr(0, offset + 1);
|
||||
return newPath;
|
||||
}
|
||||
|
||||
string appendToDirectory(const string& directoryPath, const string& filename)
|
||||
{
|
||||
string newPath = directoryPath + SEPARATOR + filename;
|
||||
return newPath;
|
||||
}
|
||||
|
||||
string getLastPathElement(const string& path)
|
||||
{
|
||||
return path.substr(path.find_last_of(SEPARATOR) + 1);
|
||||
}
|
||||
|
||||
unsigned int getFileSize(const string& path)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
struct _stat filestatus;
|
||||
_stat(path.c_str(), &filestatus);
|
||||
#else
|
||||
struct stat filestatus;
|
||||
stat(path.c_str(), &filestatus);
|
||||
#endif
|
||||
|
||||
return filestatus.st_size / 1024;
|
||||
}
|
||||
|
||||
string getFileSizeAsString(const string& path)
|
||||
{
|
||||
ostringstream convert;
|
||||
convert << getFileSize(path);
|
||||
return convert.str();
|
||||
}
|
||||
|
||||
bool fileExists(const string& path)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return PathFileExists(path.c_str());
|
||||
#else
|
||||
bool flag = false;
|
||||
fstream fin;
|
||||
fin.open(path.c_str(), ios::in);
|
||||
if (fin.is_open()) {
|
||||
flag = true;
|
||||
}
|
||||
fin.close();
|
||||
return flag;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool makeDirectory(const string& path)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
int status = _mkdir(path.c_str());
|
||||
#else
|
||||
int status = mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
|
||||
#endif
|
||||
return status == 0;
|
||||
}
|
||||
|
||||
/* Try to create a link and if does not work then make a copy */
|
||||
bool copyFile(const string& sourcePath, const string& destPath)
|
||||
{
|
||||
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 (exception& e) {
|
||||
cerr << e.what() << endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
string getExecutablePath()
|
||||
{
|
||||
char binRootPath[PATH_MAX];
|
||||
|
||||
#ifdef _WIN32
|
||||
GetModuleFileName(NULL, binRootPath, PATH_MAX);
|
||||
return std::string(binRootPath);
|
||||
#elif __APPLE__
|
||||
uint32_t max = (uint32_t)PATH_MAX;
|
||||
_NSGetExecutablePath(binRootPath, &max);
|
||||
return std::string(binRootPath);
|
||||
#else
|
||||
ssize_t size = readlink("/proc/self/exe", binRootPath, PATH_MAX);
|
||||
if (size != -1) {
|
||||
return std::string(binRootPath, size);
|
||||
}
|
||||
#endif
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
bool writeTextFile(const string& path, const string& content)
|
||||
{
|
||||
std::ofstream file;
|
||||
file.open(path.c_str());
|
||||
file << content;
|
||||
file.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
string getCurrentDirectory()
|
||||
{
|
||||
char* a_cwd = getcwd(NULL, 0);
|
||||
string s_cwd(a_cwd);
|
||||
free(a_cwd);
|
||||
return s_cwd;
|
||||
}
|
||||
93
src/common/regexTools.cpp
Normal file
93
src/common/regexTools.cpp
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2011 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <common/regexTools.h>
|
||||
|
||||
std::map<std::string, RegexMatcher*> regexCache;
|
||||
|
||||
RegexMatcher* buildRegex(const std::string& regex)
|
||||
{
|
||||
RegexMatcher* matcher;
|
||||
std::map<std::string, RegexMatcher*>::iterator itr = regexCache.find(regex);
|
||||
|
||||
/* Regex is in cache */
|
||||
if (itr != regexCache.end()) {
|
||||
matcher = itr->second;
|
||||
}
|
||||
|
||||
/* Regex needs to be parsed (and cached) */
|
||||
else {
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
UnicodeString uregex = UnicodeString(regex.c_str());
|
||||
matcher = new RegexMatcher(uregex, UREGEX_CASE_INSENSITIVE, status);
|
||||
regexCache[regex] = matcher;
|
||||
}
|
||||
|
||||
return matcher;
|
||||
}
|
||||
|
||||
/* todo */
|
||||
void freeRegexCache()
|
||||
{
|
||||
}
|
||||
bool matchRegex(const std::string& content, const std::string& regex)
|
||||
{
|
||||
ucnv_setDefaultName("UTF-8");
|
||||
UnicodeString ucontent = UnicodeString(content.c_str());
|
||||
RegexMatcher* matcher = buildRegex(regex);
|
||||
matcher->reset(ucontent);
|
||||
return matcher->find();
|
||||
}
|
||||
|
||||
std::string replaceRegex(const std::string& content,
|
||||
const std::string& replacement,
|
||||
const std::string& regex)
|
||||
{
|
||||
ucnv_setDefaultName("UTF-8");
|
||||
UnicodeString ucontent = UnicodeString(content.c_str());
|
||||
UnicodeString ureplacement = UnicodeString(replacement.c_str());
|
||||
RegexMatcher* matcher = buildRegex(regex);
|
||||
matcher->reset(ucontent);
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
UnicodeString uresult = matcher->replaceAll(ureplacement, status);
|
||||
std::string tmp;
|
||||
uresult.toUTF8String(tmp);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
std::string appendToFirstOccurence(const std::string& content,
|
||||
const std::string regex,
|
||||
const std::string& replacement)
|
||||
{
|
||||
ucnv_setDefaultName("UTF-8");
|
||||
UnicodeString ucontent = UnicodeString(content.c_str());
|
||||
UnicodeString ureplacement = UnicodeString(replacement.c_str());
|
||||
RegexMatcher* matcher = buildRegex(regex);
|
||||
matcher->reset(ucontent);
|
||||
|
||||
if (matcher->find()) {
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
ucontent.insert(matcher->end(status), ureplacement);
|
||||
std::string tmp;
|
||||
ucontent.toUTF8String(tmp);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
316
src/common/stringTools.cpp
Normal file
316
src/common/stringTools.cpp
Normal file
@@ -0,0 +1,316 @@
|
||||
/*
|
||||
* Copyright 2011 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <common/stringTools.h>
|
||||
|
||||
#include <unicode/normlzr.h>
|
||||
#include <unicode/rep.h>
|
||||
#include <unicode/translit.h>
|
||||
#include <unicode/ucnv.h>
|
||||
#include <unicode/uniset.h>
|
||||
#include <unicode/ustring.h>
|
||||
|
||||
/* tell ICU where to find its dat file (tables) */
|
||||
void kiwix::loadICUExternalTables()
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
std::string executablePath = getExecutablePath();
|
||||
std::string executableDirectory = removeLastPathElement(executablePath);
|
||||
std::string datPath
|
||||
= computeAbsolutePath(executableDirectory, "icudt49l.dat");
|
||||
try {
|
||||
u_setDataDirectory(datPath.c_str());
|
||||
} catch (exception& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string kiwix::removeAccents(const std::string& text)
|
||||
{
|
||||
loadICUExternalTables();
|
||||
ucnv_setDefaultName("UTF-8");
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
Transliterator* removeAccentsTrans = Transliterator::createInstance(
|
||||
"Lower; NFD; [:M:] remove; NFC", UTRANS_FORWARD, status);
|
||||
UnicodeString ustring = UnicodeString(text.c_str());
|
||||
removeAccentsTrans->transliterate(ustring);
|
||||
delete removeAccentsTrans;
|
||||
std::string unaccentedText;
|
||||
ustring.toUTF8String(unaccentedText);
|
||||
return unaccentedText;
|
||||
}
|
||||
|
||||
#ifndef __ANDROID__
|
||||
|
||||
/* Prepare integer for display */
|
||||
std::string kiwix::beautifyInteger(const unsigned int number)
|
||||
{
|
||||
std::stringstream numberStream;
|
||||
numberStream << number;
|
||||
std::string numberString = numberStream.str();
|
||||
|
||||
signed int offset = numberString.size() - 3;
|
||||
while (offset > 0) {
|
||||
numberString.insert(offset, ",");
|
||||
offset -= 3;
|
||||
}
|
||||
|
||||
return numberString;
|
||||
}
|
||||
|
||||
std::string kiwix::beautifyFileSize(const unsigned int number)
|
||||
{
|
||||
if (number > 1024 * 1024) {
|
||||
return kiwix::beautifyInteger(number / (1024 * 1024)) + " GB";
|
||||
} else {
|
||||
return kiwix::beautifyInteger(number / 1024 != 0 ? number / 1024 : 1)
|
||||
+ " MB";
|
||||
}
|
||||
}
|
||||
|
||||
void kiwix::printStringInHexadecimal(UnicodeString s)
|
||||
{
|
||||
std::cout << std::showbase << std::hex;
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = (char)((s.getTerminatedBuffer())[i]);
|
||||
if (c & 0x80) {
|
||||
std::cout << (c & 0xffff) << " ";
|
||||
} else {
|
||||
std::cout << c << " ";
|
||||
}
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
void kiwix::printStringInHexadecimal(const char* s)
|
||||
{
|
||||
std::cout << std::showbase << std::hex;
|
||||
for (char const* pc = s; *pc; ++pc) {
|
||||
if (*pc & 0x80) {
|
||||
std::cout << (*pc & 0xffff);
|
||||
} else {
|
||||
std::cout << *pc;
|
||||
}
|
||||
std::cout << ' ';
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
void kiwix::stringReplacement(std::string& str,
|
||||
const std::string& oldStr,
|
||||
const std::string& newStr)
|
||||
{
|
||||
size_t pos = 0;
|
||||
while ((pos = str.find(oldStr, pos)) != std::string::npos) {
|
||||
str.replace(pos, oldStr.length(), newStr);
|
||||
pos += newStr.length();
|
||||
}
|
||||
}
|
||||
|
||||
/* Encode string to avoid XSS attacks */
|
||||
std::string kiwix::encodeDiples(const std::string& str)
|
||||
{
|
||||
std::string result = str;
|
||||
kiwix::stringReplacement(result, "<", "<");
|
||||
kiwix::stringReplacement(result, ">", ">");
|
||||
return result;
|
||||
}
|
||||
|
||||
// Urlencode
|
||||
// based on javascript encodeURIComponent()
|
||||
|
||||
std::string char2hex(char dec)
|
||||
{
|
||||
char dig1 = (dec & 0xF0) >> 4;
|
||||
char dig2 = (dec & 0x0F);
|
||||
if (0 <= dig1 && dig1 <= 9) {
|
||||
dig1 += 48; // 0,48inascii
|
||||
}
|
||||
if (10 <= dig1 && dig1 <= 15) {
|
||||
dig1 += 97 - 10; // a,97inascii
|
||||
}
|
||||
if (0 <= dig2 && dig2 <= 9) {
|
||||
dig2 += 48;
|
||||
}
|
||||
if (10 <= dig2 && dig2 <= 15) {
|
||||
dig2 += 97 - 10;
|
||||
}
|
||||
|
||||
std::string r;
|
||||
r.append(&dig1, 1);
|
||||
r.append(&dig2, 1);
|
||||
return r;
|
||||
}
|
||||
|
||||
std::string kiwix::urlEncode(const std::string& c)
|
||||
{
|
||||
std::string escaped = "";
|
||||
int max = c.length();
|
||||
for (int i = 0; i < max; i++) {
|
||||
if ((48 <= c[i] && c[i] <= 57) || // 0-9
|
||||
(65 <= c[i] && c[i] <= 90)
|
||||
|| // abc...xyz
|
||||
(97 <= c[i] && c[i] <= 122)
|
||||
|| // ABC...XYZ
|
||||
(c[i] == '~' || c[i] == '!' || c[i] == '*' || c[i] == '(' || c[i] == ')'
|
||||
|| c[i] == '\'')) {
|
||||
escaped.append(&c[i], 1);
|
||||
} else {
|
||||
escaped.append("%");
|
||||
escaped.append(char2hex(c[i])); // converts char 255 to string "ff"
|
||||
}
|
||||
}
|
||||
return escaped;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static char charFromHex(std::string a)
|
||||
{
|
||||
std::istringstream Blat(a);
|
||||
int Z;
|
||||
Blat >> std::hex >> Z;
|
||||
return char(Z);
|
||||
}
|
||||
|
||||
std::string kiwix::urlDecode(const std::string& originalUrl)
|
||||
{
|
||||
std::string url = originalUrl;
|
||||
std::string::size_type pos = 0;
|
||||
while ((pos = url.find('%', pos)) != std::string::npos
|
||||
&& pos + 2 < url.length()) {
|
||||
url.replace(pos, 3, 1, charFromHex(url.substr(pos + 1, 2)));
|
||||
++pos;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
/* Split string in a token array */
|
||||
std::vector<std::string> kiwix::split(const std::string& str,
|
||||
const std::string& delims = " *-")
|
||||
{
|
||||
std::string::size_type lastPos = str.find_first_not_of(delims, 0);
|
||||
std::string::size_type pos = str.find_first_of(delims, lastPos);
|
||||
std::vector<std::string> tokens;
|
||||
|
||||
while (std::string::npos != pos || std::string::npos != lastPos) {
|
||||
tokens.push_back(str.substr(lastPos, pos - lastPos));
|
||||
lastPos = str.find_first_not_of(delims, pos);
|
||||
pos = str.find_first_of(delims, lastPos);
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
std::vector<std::string> kiwix::split(const char* lhs, const char* rhs)
|
||||
{
|
||||
const std::string m1(lhs), m2(rhs);
|
||||
return split(m1, m2);
|
||||
}
|
||||
|
||||
std::vector<std::string> kiwix::split(const char* lhs, const std::string& rhs)
|
||||
{
|
||||
return split(lhs, rhs.c_str());
|
||||
}
|
||||
|
||||
std::vector<std::string> kiwix::split(const std::string& lhs, const char* rhs)
|
||||
{
|
||||
return split(lhs.c_str(), rhs);
|
||||
}
|
||||
|
||||
std::string kiwix::ucFirst(const std::string& word)
|
||||
{
|
||||
if (word.empty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string result;
|
||||
|
||||
UnicodeString unicodeWord(word.c_str());
|
||||
UnicodeString unicodeFirstLetter = UnicodeString(unicodeWord, 0, 1).toUpper();
|
||||
unicodeWord.replace(0, 1, unicodeFirstLetter);
|
||||
unicodeWord.toUTF8String(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string kiwix::ucAll(const std::string& word)
|
||||
{
|
||||
if (word.empty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string result;
|
||||
|
||||
UnicodeString unicodeWord(word.c_str());
|
||||
unicodeWord.toUpper().toUTF8String(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string kiwix::lcFirst(const std::string& word)
|
||||
{
|
||||
if (word.empty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string result;
|
||||
|
||||
UnicodeString unicodeWord(word.c_str());
|
||||
UnicodeString unicodeFirstLetter = UnicodeString(unicodeWord, 0, 1).toLower();
|
||||
unicodeWord.replace(0, 1, unicodeFirstLetter);
|
||||
unicodeWord.toUTF8String(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string kiwix::lcAll(const std::string& word)
|
||||
{
|
||||
if (word.empty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string result;
|
||||
|
||||
UnicodeString unicodeWord(word.c_str());
|
||||
unicodeWord.toLower().toUTF8String(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string kiwix::toTitle(const std::string& word)
|
||||
{
|
||||
if (word.empty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string result;
|
||||
|
||||
UnicodeString unicodeWord(word.c_str());
|
||||
unicodeWord = unicodeWord.toTitle(0);
|
||||
unicodeWord.toUTF8String(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string kiwix::normalize(const std::string& word)
|
||||
{
|
||||
return kiwix::lcAll(word);
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
#mesondefine LIBKIWIX_VERSION
|
||||
#mesondefine VERSION
|
||||
|
||||
#mesondefine ENABLE_CTPP2
|
||||
|
||||
210
src/ctpp2/CTPP2VMStringLoader.cpp
Normal file
210
src/ctpp2/CTPP2VMStringLoader.cpp
Normal file
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Copyright 2013 Renaud Gaudin <reg@kiwix.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <ctpp2/CTPP2VMStringLoader.hpp>
|
||||
|
||||
namespace CTPP // C++ Template Engine
|
||||
{
|
||||
|
||||
//
|
||||
// Convert byte order
|
||||
//
|
||||
static void ConvertExecutable(VMExecutable * oCore)
|
||||
{
|
||||
// Code entry point
|
||||
oCore -> entry_point = Swap32(oCore -> entry_point);
|
||||
// Offset of code segment
|
||||
oCore -> code_offset = Swap32(oCore -> code_offset);
|
||||
|
||||
// Code segment size
|
||||
oCore -> code_size = Swap32(oCore -> code_size);
|
||||
|
||||
// Offset of static text segment
|
||||
oCore -> syscalls_offset = Swap32(oCore -> syscalls_offset);
|
||||
// Static text segment size
|
||||
oCore -> syscalls_data_size = Swap32(oCore -> syscalls_data_size);
|
||||
|
||||
// Offset of static text index segment
|
||||
oCore -> syscalls_index_offset = Swap32(oCore -> syscalls_index_offset);
|
||||
// Static text index segment size
|
||||
oCore -> syscalls_index_size = Swap32(oCore -> syscalls_index_size);
|
||||
|
||||
// Offset of static data segment
|
||||
oCore -> static_data_offset = Swap32(oCore -> static_data_offset);
|
||||
|
||||
// Static data segment size
|
||||
oCore -> static_data_data_size = Swap32(oCore -> static_data_data_size);
|
||||
|
||||
// Offset of static text segment
|
||||
oCore -> static_text_offset = Swap32(oCore -> static_text_offset);
|
||||
// Static text segment size
|
||||
oCore -> static_text_data_size = Swap32(oCore -> static_text_data_size);
|
||||
|
||||
// Offset of static text index segment
|
||||
oCore -> static_text_index_offset = Swap32(oCore -> static_text_index_offset);
|
||||
// Static text index segment size
|
||||
oCore -> static_text_index_size = Swap32(oCore -> static_text_index_size);
|
||||
|
||||
// Version 2.2+
|
||||
// Offset of static data bit index
|
||||
oCore -> static_data_bit_index_offset = Swap32(oCore -> static_data_bit_index_offset);
|
||||
/// Offset of static data bit index
|
||||
oCore -> static_data_bit_index_size = Swap32(oCore -> static_data_bit_index_size);
|
||||
|
||||
// Platform
|
||||
oCore -> platform = Swap64(oCore -> platform);
|
||||
|
||||
// Ugly-jolly hack!
|
||||
// ... dereferencing type-punned pointer will break strict-aliasing rules ...
|
||||
UINT_64 iTMP;
|
||||
memcpy(&iTMP, &(oCore -> ieee754double), sizeof(UINT_64));
|
||||
iTMP = Swap64(iTMP);
|
||||
memcpy(&(oCore -> ieee754double), &iTMP, sizeof(UINT_64));
|
||||
|
||||
// Cyclic Redundancy Check
|
||||
oCore -> crc = 0;
|
||||
|
||||
// Convert data structures
|
||||
|
||||
// Convert code segment
|
||||
VMInstruction * pInstructions = const_cast<VMInstruction *>(VMExecutable::GetCodeSeg(oCore));
|
||||
UINT_32 iI = 0;
|
||||
UINT_32 iSteps = oCore -> code_size / sizeof(VMInstruction);
|
||||
for(iI = 0; iI < iSteps; ++iI)
|
||||
{
|
||||
pInstructions -> instruction = Swap32(pInstructions -> instruction);
|
||||
pInstructions -> argument = Swap32(pInstructions -> argument);
|
||||
pInstructions -> reserved = Swap64(pInstructions -> reserved);
|
||||
++pInstructions;
|
||||
}
|
||||
|
||||
// Convert syscalls index
|
||||
TextDataIndex * pTextIndex = const_cast<TextDataIndex *>(VMExecutable::GetSyscallsIndexSeg(oCore));
|
||||
iSteps = oCore -> syscalls_index_size / sizeof(TextDataIndex);
|
||||
for(iI = 0; iI < iSteps; ++iI)
|
||||
{
|
||||
pTextIndex -> offset = Swap32(pTextIndex -> offset);
|
||||
pTextIndex -> length = Swap32(pTextIndex -> length);
|
||||
++pTextIndex;
|
||||
}
|
||||
|
||||
// Convert static text index
|
||||
pTextIndex = const_cast<TextDataIndex *>(VMExecutable::GetStaticTextIndexSeg(oCore));
|
||||
iSteps = oCore -> static_text_index_size / sizeof(TextDataIndex);
|
||||
for(iI = 0; iI < iSteps; ++iI)
|
||||
{
|
||||
pTextIndex -> offset = Swap32(pTextIndex -> offset);
|
||||
pTextIndex -> length = Swap32(pTextIndex -> length);
|
||||
++pTextIndex;
|
||||
}
|
||||
|
||||
// Convert static data
|
||||
StaticDataVar * pStaticDataVar = const_cast<StaticDataVar *>(VMExecutable::GetStaticDataSeg(oCore));
|
||||
iSteps = oCore -> static_data_data_size / sizeof(StaticDataVar);
|
||||
for(iI = 0; iI < iSteps; ++iI)
|
||||
{
|
||||
(*pStaticDataVar).i_data = Swap64((*pStaticDataVar).i_data);
|
||||
++pStaticDataVar;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Constructor
|
||||
//
|
||||
VMStringLoader::VMStringLoader(CCHAR_P rawContent, size_t rawContentSize)
|
||||
{
|
||||
oCore = (VMExecutable *)malloc(rawContentSize + 1);
|
||||
memcpy(oCore, rawContent, rawContentSize);
|
||||
|
||||
if (oCore -> magic[0] == 'C' &&
|
||||
oCore -> magic[1] == 'T' &&
|
||||
oCore -> magic[2] == 'P' &&
|
||||
oCore -> magic[3] == 'P')
|
||||
{
|
||||
// Check version
|
||||
if (oCore -> version[0] >= 1)
|
||||
{
|
||||
// Platform-dependent data (byte order)
|
||||
if (oCore -> platform == 0x4142434445464748ull)
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
fprintf(stderr, "Big/Little Endian conversion: Nothing to do\n");
|
||||
#endif
|
||||
|
||||
// Nothing to do, only check crc
|
||||
UINT_32 iCRC = oCore -> crc;
|
||||
oCore -> crc = 0;
|
||||
|
||||
// Calculate CRC of file
|
||||
// KELSON: next line used to refer to oStat.st_size
|
||||
// changed it to rawContentSize
|
||||
if (iCRC != crc32((UCCHAR_P)oCore, rawContentSize))
|
||||
{
|
||||
free(oCore);
|
||||
throw CTPPLogicError("CRC checksum invalid");
|
||||
}
|
||||
}
|
||||
// Platform-dependent data (byte order)
|
||||
else if (oCore -> platform == 0x4847464544434241ull)
|
||||
{
|
||||
// Need to reconvert data
|
||||
#ifdef _DEBUG
|
||||
fprintf(stderr, "Big/Little Endian conversion: Need to reconvert core\n");
|
||||
#endif
|
||||
ConvertExecutable(oCore);
|
||||
}
|
||||
else
|
||||
{
|
||||
free(oCore);
|
||||
throw CTPPLogicError("Conversion of middle-end architecture does not supported.");
|
||||
}
|
||||
|
||||
// Check IEEE 754 format
|
||||
if (oCore -> ieee754double != 15839800103804824402926068484019465486336.0)
|
||||
{
|
||||
free(oCore);
|
||||
throw CTPPLogicError("IEEE 754 format is broken, cannot convert file");
|
||||
}
|
||||
}
|
||||
|
||||
pVMMemoryCore = new VMMemoryCore(oCore);
|
||||
}
|
||||
else
|
||||
{
|
||||
free(oCore);
|
||||
throw CTPPLogicError("Not an CTPP bytecode file.");
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Get ready-to-run program
|
||||
//
|
||||
const VMMemoryCore * VMStringLoader::GetCore() const { return pVMMemoryCore; }
|
||||
|
||||
//
|
||||
// A destructor
|
||||
//
|
||||
VMStringLoader::~VMStringLoader() throw()
|
||||
{
|
||||
delete pVMMemoryCore;
|
||||
free(oCore);
|
||||
}
|
||||
|
||||
} // namespace CTPP
|
||||
// End.
|
||||
@@ -1,257 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 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 "downloader.h"
|
||||
#include "tools.h"
|
||||
#include "tools/pathTools.h"
|
||||
#include "tools/stringTools.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "aria2.h"
|
||||
#include "xmlrpc.h"
|
||||
#include "tools/otherTools.h"
|
||||
#include <pugixml.hpp>
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
void Download::updateStatus(bool follow)
|
||||
{
|
||||
if (m_status == Download::K_REMOVED)
|
||||
return;
|
||||
static std::vector<std::string> statusKey = {"status", "files", "totalLength",
|
||||
"completedLength", "followedBy",
|
||||
"downloadSpeed", "verifiedLength"};
|
||||
std::string strStatus;
|
||||
if(follow && !m_followedBy.empty()) {
|
||||
strStatus = mp_aria->tellStatus(m_followedBy, statusKey);
|
||||
} else {
|
||||
strStatus = mp_aria->tellStatus(m_did, statusKey);
|
||||
}
|
||||
// std::cout << strStatus << std::endl;
|
||||
MethodResponse response(strStatus);
|
||||
if (response.isFault()) {
|
||||
m_status = Download::K_UNKNOWN;
|
||||
return;
|
||||
}
|
||||
auto structNode = response.getParams().getParam(0).getValue().getStruct();
|
||||
auto _status = structNode.getMember("status").getValue().getAsS();
|
||||
auto status = _status == "active" ? Download::K_ACTIVE
|
||||
: _status == "waiting" ? Download::K_WAITING
|
||||
: _status == "paused" ? Download::K_PAUSED
|
||||
: _status == "error" ? Download::K_ERROR
|
||||
: _status == "complete" ? Download::K_COMPLETE
|
||||
: _status == "removed" ? Download::K_REMOVED
|
||||
: Download::K_UNKNOWN;
|
||||
if (status == K_COMPLETE) {
|
||||
try {
|
||||
auto followedByMember = structNode.getMember("followedBy");
|
||||
m_followedBy = followedByMember.getValue().getArray().getValue(0).getAsS();
|
||||
if (follow) {
|
||||
status = K_ACTIVE;
|
||||
updateStatus(true);
|
||||
return;
|
||||
}
|
||||
} catch (InvalidRPCNode& e) { }
|
||||
}
|
||||
m_status = status;
|
||||
m_totalLength = extractFromString<uint64_t>(structNode.getMember("totalLength").getValue().getAsS());
|
||||
m_completedLength = extractFromString<uint64_t>(structNode.getMember("completedLength").getValue().getAsS());
|
||||
m_downloadSpeed = extractFromString<uint64_t>(structNode.getMember("downloadSpeed").getValue().getAsS());
|
||||
try {
|
||||
auto verifiedLengthValue = structNode.getMember("verifiedLength").getValue();
|
||||
m_verifiedLength = extractFromString<uint64_t>(verifiedLengthValue.getAsS());
|
||||
} catch (InvalidRPCNode& e) { m_verifiedLength = 0; }
|
||||
auto filesMember = structNode.getMember("files");
|
||||
auto fileStruct = filesMember.getValue().getArray().getValue(0).getStruct();
|
||||
m_path = fileStruct.getMember("path").getValue().getAsS();
|
||||
auto urisArray = fileStruct.getMember("uris").getValue().getArray();
|
||||
int index = 0;
|
||||
m_uris.clear();
|
||||
while(true) {
|
||||
try {
|
||||
auto uriNode = urisArray.getValue(index++).getStruct().getMember("uri");
|
||||
m_uris.push_back(uriNode.getValue().getAsS());
|
||||
} catch(InvalidRPCNode& e) { break; }
|
||||
}
|
||||
}
|
||||
|
||||
void Download::resumeDownload()
|
||||
{
|
||||
if (!m_followedBy.empty())
|
||||
mp_aria->unpause(m_followedBy);
|
||||
else
|
||||
mp_aria->unpause(m_did);
|
||||
updateStatus(true);
|
||||
}
|
||||
|
||||
void Download::pauseDownload()
|
||||
{
|
||||
if (!m_followedBy.empty())
|
||||
mp_aria->pause(m_followedBy);
|
||||
else
|
||||
mp_aria->pause(m_did);
|
||||
updateStatus(true);
|
||||
}
|
||||
|
||||
void Download::cancelDownload()
|
||||
{
|
||||
if (!m_followedBy.empty())
|
||||
mp_aria->remove(m_followedBy);
|
||||
else
|
||||
mp_aria->remove(m_did);
|
||||
m_status = Download::K_REMOVED;
|
||||
}
|
||||
|
||||
/* Constructor */
|
||||
Downloader::Downloader(std::string sessionFileDir) :
|
||||
mp_aria(new Aria2(sessionFileDir))
|
||||
{
|
||||
try {
|
||||
for (auto gid : mp_aria->tellWaiting()) {
|
||||
m_knownDownloads[gid] = std::unique_ptr<Download>(new Download(mp_aria, gid));
|
||||
m_knownDownloads[gid]->updateStatus(false);
|
||||
}
|
||||
} catch (std::exception& e) {
|
||||
std::cerr << "aria2 tellWaiting failed : " << e.what() << std::endl;
|
||||
}
|
||||
try {
|
||||
for (auto gid : mp_aria->tellActive()) {
|
||||
if( m_knownDownloads.find(gid) == m_knownDownloads.end()) {
|
||||
m_knownDownloads[gid] = std::unique_ptr<Download>(new Download(mp_aria, gid));
|
||||
m_knownDownloads[gid]->updateStatus(false);
|
||||
}
|
||||
}
|
||||
} catch (std::exception& e) {
|
||||
std::cerr << "aria2 tellActive failed : " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
/* Destructor */
|
||||
Downloader::~Downloader()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
void Downloader::close()
|
||||
{
|
||||
if ( mp_aria ) {
|
||||
try {
|
||||
mp_aria->close();
|
||||
} catch (const std::exception& err) {
|
||||
std::cerr << "ERROR: Failed to save the downloader state: "
|
||||
<< err.what() << std::endl;
|
||||
}
|
||||
mp_aria.reset();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> Downloader::getDownloadIds() const {
|
||||
std::unique_lock<std::mutex> lock(m_lock);
|
||||
std::vector<std::string> ret;
|
||||
for(auto& p:m_knownDownloads) {
|
||||
ret.push_back(p.first);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
bool downloadCanBeReused(const Download& d,
|
||||
const std::string& uri,
|
||||
const Downloader::Options& /*options*/)
|
||||
{
|
||||
const auto& uris = d.getUris();
|
||||
const bool sameURI = std::find(uris.begin(), uris.end(), uri) != uris.end();
|
||||
|
||||
if ( !sameURI )
|
||||
return false;
|
||||
|
||||
switch ( d.getStatus() ) {
|
||||
case Download::K_ERROR:
|
||||
case Download::K_UNKNOWN:
|
||||
case Download::K_REMOVED:
|
||||
return false;
|
||||
|
||||
case Download::K_ACTIVE:
|
||||
case Download::K_WAITING:
|
||||
case Download::K_PAUSED:
|
||||
return true; // XXX: what if options are different?
|
||||
|
||||
case Download::K_COMPLETE:
|
||||
return fileExists(d.getPath()); // XXX: what if options are different?
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
std::shared_ptr<Download> Downloader::startDownload(const std::string& uri, const std::string& downloadDir, Options options)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_lock);
|
||||
options.erase(std::remove_if(options.begin(), options.end(), [](const auto& option) {
|
||||
return option.first == "dir";
|
||||
}), options.end());
|
||||
options.push_back({"dir", downloadDir});
|
||||
for (auto& p: m_knownDownloads) {
|
||||
auto& d = p.second;
|
||||
if ( downloadCanBeReused(*d, uri, options) )
|
||||
return d;
|
||||
}
|
||||
std::vector<std::string> uris = {uri};
|
||||
auto gid = mp_aria->addUri(uris, options);
|
||||
m_knownDownloads[gid] = std::make_shared<Download>(mp_aria, gid);
|
||||
return m_knownDownloads[gid];
|
||||
}
|
||||
|
||||
std::shared_ptr<Download> Downloader::getDownload(const std::string& did)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_lock);
|
||||
try {
|
||||
return m_knownDownloads.at(did);
|
||||
} catch(std::exception& e) {
|
||||
for (auto gid : mp_aria->tellWaiting()) {
|
||||
if (gid == did) {
|
||||
m_knownDownloads[gid] = std::make_shared<Download>(mp_aria, gid);
|
||||
return m_knownDownloads[gid];
|
||||
}
|
||||
}
|
||||
for (auto gid : mp_aria->tellActive()) {
|
||||
if (gid == did) {
|
||||
m_knownDownloads[gid] = std::make_shared<Download>(mp_aria, gid);
|
||||
return m_knownDownloads[gid];
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
size_t Downloader::getNbDownload() const {
|
||||
std::unique_lock<std::mutex> lock(m_lock);
|
||||
return m_knownDownloads.size();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
#include "html_dumper.h"
|
||||
#include "libkiwix-resources.h"
|
||||
#include "tools/otherTools.h"
|
||||
#include "tools.h"
|
||||
#include "tools/regexTools.h"
|
||||
#include "server/i18n_utils.h"
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
/* Constructor */
|
||||
HTMLDumper::HTMLDumper(const Library* library, const NameMapper* nameMapper)
|
||||
: LibraryDumper(library, nameMapper)
|
||||
{
|
||||
}
|
||||
/* Destructor */
|
||||
HTMLDumper::~HTMLDumper()
|
||||
{
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
std::string humanFriendlyTitle(std::string title)
|
||||
{
|
||||
std::string humanFriendlyString = replaceRegex(title, "_", " ");
|
||||
humanFriendlyString[0] = toupper(humanFriendlyString[0]);
|
||||
return humanFriendlyString;
|
||||
}
|
||||
|
||||
kainjow::mustache::object getLangTag(const std::vector<std::string>& bookLanguages) {
|
||||
std::string langShortString = "";
|
||||
std::string langFullString = "???";
|
||||
|
||||
//if more than 1 languages then show "mul" else show the language
|
||||
if(bookLanguages.size() > 1) {
|
||||
std::vector<std::string> mulLanguages;
|
||||
langShortString = "mul";
|
||||
for (const auto& lang : bookLanguages) {
|
||||
const std::string fullLang = getLanguageSelfName(lang);
|
||||
mulLanguages.push_back(fullLang);
|
||||
}
|
||||
langFullString = kiwix::join(mulLanguages, ",");
|
||||
} else if(bookLanguages.size() == 1) {
|
||||
langShortString = bookLanguages[0];
|
||||
langFullString = getLanguageSelfName(langShortString);
|
||||
}
|
||||
|
||||
kainjow::mustache::object langTag;
|
||||
langTag["langShortString"] = langShortString;
|
||||
langTag["langFullString"] = langFullString;
|
||||
return langTag;
|
||||
}
|
||||
|
||||
kainjow::mustache::list getTagList(std::string tags)
|
||||
{
|
||||
const auto tagsList = kiwix::split(tags, ";", true, false);
|
||||
kainjow::mustache::list finalTagList;
|
||||
for (auto tag : tagsList) {
|
||||
if (tag[0] != '_')
|
||||
finalTagList.push_back(kainjow::mustache::object{
|
||||
{"tag", tag}
|
||||
});
|
||||
}
|
||||
return finalTagList;
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
std::string HTMLDumper::dumpPlainHTML(kiwix::Filter filter) const
|
||||
{
|
||||
kainjow::mustache::list booksData;
|
||||
const auto filteredBooks = library->filter(filter);
|
||||
const auto searchQuery = filter.getQuery();
|
||||
auto languages = getLanguageData();
|
||||
auto categories = getCategoryData();
|
||||
|
||||
for (auto &category : categories) {
|
||||
const auto categoryName = category.get("name")->string_value();
|
||||
if (categoryName == filter.getCategory()) {
|
||||
category["selected"] = true;
|
||||
}
|
||||
category["hf_name"] = humanFriendlyTitle(categoryName);
|
||||
}
|
||||
|
||||
for (auto &language : languages) {
|
||||
if (language.get("lang_code")->string_value() == filter.getLang()) {
|
||||
language["selected"] = true;
|
||||
}
|
||||
}
|
||||
|
||||
for ( const auto& bookId : filteredBooks ) {
|
||||
const auto bookObj = library->getBookById(bookId);
|
||||
const auto bookTitle = bookObj.getTitle();
|
||||
std::string contentId = "";
|
||||
try {
|
||||
contentId = urlEncode(nameMapper->getNameForId(bookId));
|
||||
} catch (...) {}
|
||||
const auto bookDescription = bookObj.getDescription();
|
||||
const auto bookIconUrl = rootLocation + "/catalog/v2/illustration/" + bookId + "/?size=48";
|
||||
const auto tags = bookObj.getTags();
|
||||
const auto downloadAvailable = (bookObj.getUrl() != "");
|
||||
const auto langTagObj = getLangTag(bookObj.getLanguages());
|
||||
std::string faviconAttr = "style=background-image:url(" + bookIconUrl + ")";
|
||||
booksData.push_back(kainjow::mustache::object{
|
||||
{"id", contentId},
|
||||
{"title", bookTitle},
|
||||
{"description", bookDescription},
|
||||
{"langTag", langTagObj},
|
||||
{"faviconAttr", faviconAttr},
|
||||
{"tagList", getTagList(tags)},
|
||||
{"downloadAvailable", downloadAvailable}
|
||||
});
|
||||
}
|
||||
|
||||
auto getTranslation = i18n::GetTranslatedStringWithMsgId(m_userLang);
|
||||
|
||||
const auto translations = kainjow::mustache::object{
|
||||
getTranslation("search"),
|
||||
getTranslation("download"),
|
||||
getTranslation("count-of-matching-books", {{"COUNT", to_string(filteredBooks.size())}}),
|
||||
getTranslation("book-filtering-all-categories"),
|
||||
getTranslation("book-filtering-all-languages"),
|
||||
getTranslation("powered-by-kiwix-html"),
|
||||
getTranslation("welcome-to-kiwix-server"),
|
||||
getTranslation("preview-book"),
|
||||
getTranslation("welcome-page-overzealous-filter", {{"URL", "?lang="}})
|
||||
};
|
||||
|
||||
return render_template(
|
||||
RESOURCE::templates::no_js_library_page_html,
|
||||
kainjow::mustache::object{
|
||||
{"root", rootLocation},
|
||||
{"contentAccessUrl", onlyAsNonEmptyMustacheValue(contentAccessUrl)},
|
||||
{"books", booksData },
|
||||
{"searchQuery", searchQuery},
|
||||
{"languages", languages},
|
||||
{"categories", categories},
|
||||
{"noResults", filteredBooks.size() == 0},
|
||||
{"translations", translations}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace kiwix
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 Nikhil Tanwar <2002nikhiltanwar@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef KIWIX_HTML_DUMPER_H
|
||||
#define KIWIX_HTML_DUMPER_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "library_dumper.h"
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
/**
|
||||
* A class to dump Library in HTML format.
|
||||
*/
|
||||
class HTMLDumper : public LibraryDumper
|
||||
{
|
||||
public:
|
||||
HTMLDumper(const Library* library, const NameMapper* NameMapper);
|
||||
~HTMLDumper();
|
||||
|
||||
|
||||
/**
|
||||
* Dump library in HTML
|
||||
*
|
||||
* @return HTML content
|
||||
*/
|
||||
std::string dumpPlainHTML(kiwix::Filter filter) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // KIWIX_HTML_DUMPER_H
|
||||
@@ -1,82 +0,0 @@
|
||||
#include "kiwixserve.h"
|
||||
#include "subprocess.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
# define KIWIXSERVE_CMD "kiwix-serve.exe"
|
||||
# include <windows.h>
|
||||
#else
|
||||
# define KIWIXSERVE_CMD "kiwix-serve"
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "tools.h"
|
||||
#include "tools/pathTools.h"
|
||||
#include "tools/stringTools.h"
|
||||
|
||||
namespace kiwix {
|
||||
|
||||
KiwixServe::KiwixServe(const std::string& libraryPath, int port)
|
||||
: m_port(port),
|
||||
m_libraryPath(libraryPath)
|
||||
{
|
||||
}
|
||||
|
||||
KiwixServe::~KiwixServe()
|
||||
{
|
||||
shutDown();
|
||||
}
|
||||
|
||||
void KiwixServe::run()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
int pid = GetCurrentProcessId();
|
||||
#else
|
||||
pid_t pid = getpid();
|
||||
|
||||
#endif
|
||||
|
||||
std::vector<const char*> callCmd;
|
||||
std::string kiwixServeCmd = appendToDirectory(
|
||||
removeLastPathElement(getExecutablePath(true)),
|
||||
KIWIXSERVE_CMD);
|
||||
if (fileExists(kiwixServeCmd)) {
|
||||
// A local kiwix-serve exe exists (packaged with kiwix-desktop), use it.
|
||||
callCmd.push_back(kiwixServeCmd.c_str());
|
||||
} else {
|
||||
// Try to use a potential installed kiwix-serve.
|
||||
callCmd.push_back(KIWIXSERVE_CMD);
|
||||
}
|
||||
std::string attachProcessOpt = "-a" + to_string(pid);
|
||||
std::string portOpt = "-p" + to_string(m_port);
|
||||
callCmd.push_back(attachProcessOpt.c_str());
|
||||
callCmd.push_back(portOpt.c_str());
|
||||
callCmd.push_back("-l");
|
||||
callCmd.push_back(m_libraryPath.c_str());
|
||||
mp_kiwixServe = Subprocess::run(callCmd);
|
||||
}
|
||||
|
||||
void KiwixServe::shutDown()
|
||||
{
|
||||
if (mp_kiwixServe)
|
||||
mp_kiwixServe->kill();
|
||||
}
|
||||
|
||||
bool KiwixServe::isRunning()
|
||||
{
|
||||
if (mp_kiwixServe) {
|
||||
return (mp_kiwixServe->isRunning());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int KiwixServe::setPort(int port)
|
||||
{
|
||||
if (port >= 1 && port <= 65535) {
|
||||
m_port = port;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
return m_port;
|
||||
}
|
||||
|
||||
}
|
||||
1124
src/library.cpp
1124
src/library.cpp
File diff suppressed because it is too large
Load Diff
@@ -1,61 +0,0 @@
|
||||
#include "library_dumper.h"
|
||||
#include "tools/stringTools.h"
|
||||
#include "tools/otherTools.h"
|
||||
#include "tools.h"
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
/* Constructor */
|
||||
LibraryDumper::LibraryDumper(const Library* library, const NameMapper* nameMapper)
|
||||
: library(library),
|
||||
nameMapper(nameMapper)
|
||||
{
|
||||
}
|
||||
/* Destructor */
|
||||
LibraryDumper::~LibraryDumper()
|
||||
{
|
||||
}
|
||||
|
||||
void LibraryDumper::setOpenSearchInfo(int totalResults, int startIndex, int count)
|
||||
{
|
||||
m_totalResults = totalResults;
|
||||
m_startIndex = startIndex,
|
||||
m_count = count;
|
||||
}
|
||||
|
||||
kainjow::mustache::list LibraryDumper::getCategoryData() const
|
||||
{
|
||||
const auto now = gen_date_str();
|
||||
kainjow::mustache::list categoryData;
|
||||
for ( const auto& category : library->getBooksCategories() ) {
|
||||
const auto urlencodedCategoryName = urlEncode(category);
|
||||
categoryData.push_back(kainjow::mustache::object{
|
||||
{"name", category},
|
||||
{"urlencoded_name", urlencodedCategoryName},
|
||||
{"updated", now},
|
||||
{"id", gen_uuid(libraryId + "/categories/" + urlencodedCategoryName)}
|
||||
});
|
||||
}
|
||||
return categoryData;
|
||||
}
|
||||
|
||||
kainjow::mustache::list LibraryDumper::getLanguageData() const
|
||||
{
|
||||
const auto now = gen_date_str();
|
||||
kainjow::mustache::list languageData;
|
||||
for ( const auto& langAndBookCount : library->getBooksLanguagesWithCounts() ) {
|
||||
const std::string languageCode = langAndBookCount.first;
|
||||
const int bookCount = langAndBookCount.second;
|
||||
const auto languageSelfName = getLanguageSelfName(languageCode);
|
||||
languageData.push_back(kainjow::mustache::object{
|
||||
{"lang_code", languageCode},
|
||||
{"lang_self_name", languageSelfName},
|
||||
{"book_count", to_string(bookCount)},
|
||||
{"updated", now},
|
||||
{"id", gen_uuid(libraryId + "/languages/" + languageCode)}
|
||||
});
|
||||
}
|
||||
return languageData;
|
||||
}
|
||||
|
||||
} // namespace kiwix
|
||||
@@ -1,99 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 Nikhil Tanwar <2002nikhiltanwar@gmail.com>
|
||||
* Copyright 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.
|
||||
*/
|
||||
|
||||
#ifndef KIWIX_LIBRARY_DUMPER_H
|
||||
#define KIWIX_LIBRARY_DUMPER_H
|
||||
|
||||
#include <string>
|
||||
|
||||
|
||||
#include "library.h"
|
||||
#include "name_mapper.h"
|
||||
#include <mustache.hpp>
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
/**
|
||||
* A base class to dump Library in various formats.
|
||||
*
|
||||
*/
|
||||
class LibraryDumper
|
||||
{
|
||||
public:
|
||||
LibraryDumper(const Library* library, const NameMapper* NameMapper);
|
||||
~LibraryDumper();
|
||||
|
||||
void setLibraryId(const std::string& id) { this->libraryId = id;}
|
||||
|
||||
/**
|
||||
* Set the root location used when generating url.
|
||||
*
|
||||
* @param rootLocation the root location to use.
|
||||
*/
|
||||
void setRootLocation(const std::string& rootLocation) { this->rootLocation = rootLocation; }
|
||||
|
||||
/**
|
||||
* Set the URL for accessing book content
|
||||
*
|
||||
* @param url the URL of the /content endpoint of the content server
|
||||
*/
|
||||
void setContentAccessUrl(const std::string& url) { this->contentAccessUrl = url; }
|
||||
|
||||
/**
|
||||
* Set some informations about the search results.
|
||||
*
|
||||
* @param totalResult the total number of results of the search.
|
||||
* @param startIndex the start index of the result.
|
||||
* @param count the number of result of the current set (or page).
|
||||
*/
|
||||
void setOpenSearchInfo(int totalResult, int startIndex, int count);
|
||||
|
||||
/**
|
||||
* Sets user default language
|
||||
*
|
||||
* @param userLang the user language to be set
|
||||
*/
|
||||
void setUserLanguage(std::string userLang) { this->m_userLang = userLang; }
|
||||
|
||||
/**
|
||||
* Get the data of categories
|
||||
*/
|
||||
kainjow::mustache::list getCategoryData() const;
|
||||
|
||||
/**
|
||||
* Get the data of languages
|
||||
*/
|
||||
kainjow::mustache::list getLanguageData() const;
|
||||
|
||||
protected:
|
||||
const kiwix::Library* const library;
|
||||
const kiwix::NameMapper* const nameMapper;
|
||||
std::string libraryId;
|
||||
std::string rootLocation;
|
||||
std::string contentAccessUrl;
|
||||
std::string m_userLang;
|
||||
int m_totalResults;
|
||||
int m_startIndex;
|
||||
int m_count;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // KIWIX_LIBRARY_DUMPER_H
|
||||
@@ -1,149 +0,0 @@
|
||||
/*
|
||||
* Copyright 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 "libxml_dumper.h"
|
||||
#include "book.h"
|
||||
|
||||
#include "tools.h"
|
||||
#include "tools/base64.h"
|
||||
#include "tools/stringTools.h"
|
||||
#include "tools/otherTools.h"
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
/* Constructor */
|
||||
LibXMLDumper::LibXMLDumper(const Library* library)
|
||||
: library(library)
|
||||
{
|
||||
}
|
||||
/* Destructor */
|
||||
LibXMLDumper::~LibXMLDumper()
|
||||
{
|
||||
}
|
||||
|
||||
#define ADD_ATTRIBUTE(node, name, value) { (node).append_attribute((name)) = (value).c_str(); }
|
||||
#define ADD_ATTR_NOT_EMPTY(node, name, value) { if (!(value).empty()) ADD_ATTRIBUTE(node, name, value); }
|
||||
|
||||
void LibXMLDumper::handleBook(Book book, pugi::xml_node root_node) {
|
||||
if (book.readOnly())
|
||||
return;
|
||||
|
||||
auto entry_node = root_node.append_child("book");
|
||||
ADD_ATTRIBUTE(entry_node, "id", book.getId());
|
||||
|
||||
if (!book.getPath().empty()) {
|
||||
ADD_ATTRIBUTE(entry_node, "path", computeRelativePath(baseDir, book.getPath()));
|
||||
}
|
||||
|
||||
if (book.getOrigId().empty()) {
|
||||
ADD_ATTR_NOT_EMPTY(entry_node, "title", book.getTitle());
|
||||
ADD_ATTR_NOT_EMPTY(entry_node, "description", book.getDescription());
|
||||
ADD_ATTR_NOT_EMPTY(entry_node, "language", book.getCommaSeparatedLanguages());
|
||||
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());
|
||||
try {
|
||||
auto defaultIllustration = book.getIllustration(48);
|
||||
ADD_ATTR_NOT_EMPTY(entry_node, "faviconMimeType", defaultIllustration->mimeType);
|
||||
ADD_ATTR_NOT_EMPTY(entry_node, "faviconUrl", defaultIllustration->url);
|
||||
if (!defaultIllustration->getData().empty())
|
||||
ADD_ATTRIBUTE(entry_node, "favicon", base64_encode(defaultIllustration->getData()));
|
||||
} catch(...) {}
|
||||
} else {
|
||||
ADD_ATTRIBUTE(entry_node, "origId", book.getOrigId());
|
||||
}
|
||||
|
||||
ADD_ATTR_NOT_EMPTY(entry_node, "date", book.getDate());
|
||||
ADD_ATTR_NOT_EMPTY(entry_node, "url", book.getUrl());
|
||||
|
||||
if (book.getArticleCount())
|
||||
ADD_ATTRIBUTE(entry_node, "articleCount", to_string(book.getArticleCount()));
|
||||
|
||||
if (book.getMediaCount())
|
||||
ADD_ATTRIBUTE(entry_node, "mediaCount", to_string(book.getMediaCount()));
|
||||
|
||||
if (book.getSize())
|
||||
ADD_ATTRIBUTE(entry_node, "size", to_string(book.getSize()>>10));
|
||||
|
||||
ADD_ATTR_NOT_EMPTY(entry_node, "downloadId", book.getDownloadId());
|
||||
}
|
||||
|
||||
#define ADD_TEXT_ENTRY(node, child, value) (node).append_child((child)).append_child(pugi::node_pcdata).set_value((value).c_str())
|
||||
|
||||
void LibXMLDumper::handleBookmark(Bookmark bookmark, pugi::xml_node root_node) {
|
||||
|
||||
auto entry_node = root_node.append_child("bookmark");
|
||||
auto book_node = entry_node.append_child("book");
|
||||
|
||||
try {
|
||||
auto book = library->getBookByIdThreadSafe(bookmark.getBookId());
|
||||
ADD_TEXT_ENTRY(book_node, "id", book.getId());
|
||||
ADD_TEXT_ENTRY(book_node, "title", book.getTitle());
|
||||
ADD_TEXT_ENTRY(book_node, "name", book.getName());
|
||||
ADD_TEXT_ENTRY(book_node, "flavour", book.getFlavour());
|
||||
ADD_TEXT_ENTRY(book_node, "language", book.getCommaSeparatedLanguages());
|
||||
ADD_TEXT_ENTRY(book_node, "date", book.getDate());
|
||||
} catch (...) {
|
||||
ADD_TEXT_ENTRY(book_node, "id", bookmark.getBookId());
|
||||
ADD_TEXT_ENTRY(book_node, "title", bookmark.getBookTitle());
|
||||
ADD_TEXT_ENTRY(book_node, "name", bookmark.getBookName());
|
||||
ADD_TEXT_ENTRY(book_node, "flavour", bookmark.getBookFlavour());
|
||||
ADD_TEXT_ENTRY(book_node, "language", bookmark.getLanguage());
|
||||
ADD_TEXT_ENTRY(book_node, "date", bookmark.getDate());
|
||||
}
|
||||
ADD_TEXT_ENTRY(entry_node, "title", bookmark.getTitle());
|
||||
ADD_TEXT_ENTRY(entry_node, "url", bookmark.getUrl());
|
||||
}
|
||||
|
||||
|
||||
std::string LibXMLDumper::dumpLibXMLContent(const std::vector<std::string>& bookIds)
|
||||
{
|
||||
pugi::xml_document doc;
|
||||
|
||||
/* Add the library node */
|
||||
pugi::xml_node libraryNode = doc.append_child("library");
|
||||
|
||||
libraryNode.append_attribute("version") = KIWIX_LIBRARY_VERSION;
|
||||
|
||||
if (library) {
|
||||
for (auto& bookId: bookIds) {
|
||||
handleBook(library->getBookById(bookId), libraryNode);
|
||||
}
|
||||
}
|
||||
return nodeToString(libraryNode);
|
||||
}
|
||||
|
||||
std::string LibXMLDumper::dumpLibXMLBookmark()
|
||||
{
|
||||
pugi::xml_document doc;
|
||||
|
||||
/* Add the library node */
|
||||
pugi::xml_node bookmarksNode = doc.append_child("bookmarks");
|
||||
|
||||
if (library) {
|
||||
for (auto& bookmark: library->getBookmarks(false)) {
|
||||
handleBookmark(bookmark, bookmarksNode);
|
||||
}
|
||||
}
|
||||
return nodeToString(bookmarksNode);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 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.
|
||||
*/
|
||||
|
||||
#ifndef KIWIX_LIBXML_DUMPER_H
|
||||
#define KIWIX_LIBXML_DUMPER_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <pugixml.hpp>
|
||||
|
||||
#include "library.h"
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
/**
|
||||
* A tool to dump a `Library` into a basic library.xml
|
||||
*
|
||||
*/
|
||||
class LibXMLDumper
|
||||
{
|
||||
public:
|
||||
LibXMLDumper() = default;
|
||||
LibXMLDumper(const Library* library);
|
||||
~LibXMLDumper();
|
||||
|
||||
/**
|
||||
* Dump the library.xml
|
||||
*
|
||||
* @param id The id of the library.
|
||||
* @return The library.xml content.
|
||||
*/
|
||||
std::string dumpLibXMLContent(const std::vector<std::string>& bookIds);
|
||||
|
||||
|
||||
/**
|
||||
* Dump the bookmark of the library.
|
||||
*
|
||||
* @return The bookmark.xml content.
|
||||
*/
|
||||
std::string dumpLibXMLBookmark();
|
||||
|
||||
/**
|
||||
* Set the base directory used.
|
||||
*
|
||||
* @param baseDir the base directory to use.
|
||||
*/
|
||||
void setBaseDir(const std::string& baseDir) { this->baseDir = baseDir; }
|
||||
|
||||
/**
|
||||
* Set the library to dump.
|
||||
*
|
||||
* @param library The library to dump.
|
||||
*/
|
||||
void setLibrary(const Library* library) { this->library = library; }
|
||||
|
||||
protected:
|
||||
const kiwix::Library* library;
|
||||
std::string baseDir;
|
||||
private:
|
||||
void handleBook(Book book, pugi::xml_node root_node);
|
||||
void handleBookmark(Bookmark bookmark, pugi::xml_node root_node);
|
||||
};
|
||||
}
|
||||
|
||||
#endif // KIWIX_OPDS_DUMPER_H
|
||||
790
src/manager.cpp
790
src/manager.cpp
@@ -19,189 +19,105 @@
|
||||
|
||||
#include "manager.h"
|
||||
|
||||
#include "tools.h"
|
||||
#include "tools/pathTools.h"
|
||||
|
||||
#include <pugixml.hpp>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <set>
|
||||
#include <queue>
|
||||
#include <cctype>
|
||||
#include <algorithm>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// LibraryManipulator
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
LibraryManipulator::LibraryManipulator(LibraryPtr library)
|
||||
: library(library)
|
||||
{}
|
||||
|
||||
LibraryManipulator::~LibraryManipulator()
|
||||
{}
|
||||
|
||||
bool LibraryManipulator::addBookToLibrary(const Book& book)
|
||||
{
|
||||
const auto ret = library->addBook(book);
|
||||
if ( ret ) {
|
||||
bookWasAddedToLibrary(book);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void LibraryManipulator::addBookmarkToLibrary(const Bookmark& bookmark)
|
||||
{
|
||||
library->addBookmark(bookmark);
|
||||
bookmarkWasAddedToLibrary(bookmark);
|
||||
}
|
||||
|
||||
uint32_t LibraryManipulator::removeBooksNotUpdatedSince(Library::Revision rev)
|
||||
{
|
||||
const auto n = library->removeBooksNotUpdatedSince(rev);
|
||||
if ( n != 0 ) {
|
||||
booksWereRemovedFromLibrary();
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
void LibraryManipulator::bookWasAddedToLibrary(const Book& book)
|
||||
{
|
||||
}
|
||||
|
||||
void LibraryManipulator::bookmarkWasAddedToLibrary(const Bookmark& bookmark)
|
||||
{
|
||||
}
|
||||
|
||||
void LibraryManipulator::booksWereRemovedFromLibrary()
|
||||
{
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Manager
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/* Constructor */
|
||||
Manager::Manager(LibraryManipulator manipulator):
|
||||
writableLibraryPath(""),
|
||||
manipulator(manipulator)
|
||||
Manager::Manager() : writableLibraryPath("")
|
||||
{
|
||||
}
|
||||
|
||||
Manager::Manager(LibraryPtr library) :
|
||||
writableLibraryPath(""),
|
||||
manipulator(LibraryManipulator(library))
|
||||
/* Destructor */
|
||||
Manager::~Manager()
|
||||
{
|
||||
}
|
||||
|
||||
bool Manager::parseXmlDom(const pugi::xml_document& doc,
|
||||
bool readOnly,
|
||||
const std::string& libraryPath,
|
||||
bool trustLibrary)
|
||||
const bool readOnly,
|
||||
const string libraryPath)
|
||||
{
|
||||
pugi::xml_node libraryNode = doc.child("library");
|
||||
|
||||
std::string libraryVersion = libraryNode.attribute("version").value();
|
||||
if (strlen(libraryNode.attribute("current").value()))
|
||||
this->setCurrentBookId(libraryNode.attribute("current").value());
|
||||
|
||||
string libraryVersion = libraryNode.attribute("version").value();
|
||||
|
||||
for (pugi::xml_node bookNode = libraryNode.child("book"); bookNode;
|
||||
bookNode = bookNode.next_sibling("book")) {
|
||||
bool ok = true;
|
||||
kiwix::Book book;
|
||||
|
||||
book.setReadOnly(readOnly);
|
||||
book.updateFromXml(bookNode,
|
||||
removeLastPathElement(libraryPath));
|
||||
book.readOnly = readOnly;
|
||||
book.id = bookNode.attribute("id").value();
|
||||
book.path = bookNode.attribute("path").value();
|
||||
book.last = (std::string(bookNode.attribute("last").value()) != "undefined"
|
||||
? bookNode.attribute("last").value()
|
||||
: "");
|
||||
book.indexPath = bookNode.attribute("indexPath").value();
|
||||
book.indexType = XAPIAN;
|
||||
book.title = bookNode.attribute("title").value();
|
||||
book.name = bookNode.attribute("name").value();
|
||||
book.tags = bookNode.attribute("tags").value();
|
||||
book.description = bookNode.attribute("description").value();
|
||||
book.language = bookNode.attribute("language").value();
|
||||
book.date = bookNode.attribute("date").value();
|
||||
book.creator = bookNode.attribute("creator").value();
|
||||
book.publisher = bookNode.attribute("publisher").value();
|
||||
book.url = bookNode.attribute("url").value();
|
||||
book.origId = bookNode.attribute("origId").value();
|
||||
book.articleCount = bookNode.attribute("articleCount").value();
|
||||
book.mediaCount = bookNode.attribute("mediaCount").value();
|
||||
book.size = bookNode.attribute("size").value();
|
||||
book.favicon = bookNode.attribute("favicon").value();
|
||||
book.faviconMimeType = bookNode.attribute("faviconMimeType").value();
|
||||
|
||||
if (!trustLibrary && !book.getPath().empty()) {
|
||||
this->readBookFromPath(book.getPath(), &book);
|
||||
}
|
||||
manipulator.addBookToLibrary(book);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Manager::readXml(const std::string& xml,
|
||||
bool readOnly,
|
||||
const std::string& libraryPath,
|
||||
bool trustLibrary)
|
||||
{
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result
|
||||
= doc.load_buffer((void*)xml.data(), xml.size());
|
||||
|
||||
if (result) {
|
||||
this->parseXmlDom(doc, readOnly, libraryPath, trustLibrary);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool Manager::parseOpdsDom(const pugi::xml_document& doc, const std::string& urlHost)
|
||||
{
|
||||
pugi::xml_node libraryNode = doc.child("feed");
|
||||
|
||||
try {
|
||||
m_totalBooks = strtoull(libraryNode.child("totalResults").child_value(), 0, 0);
|
||||
m_startIndex = strtoull(libraryNode.child("startIndex").child_value(), 0, 0);
|
||||
m_itemsPerPage = strtoull(libraryNode.child("itemsPerPage").child_value(), 0, 0);
|
||||
m_hasSearchResult = true;
|
||||
} catch(...) {
|
||||
m_hasSearchResult = false;
|
||||
}
|
||||
|
||||
for (pugi::xml_node entryNode = libraryNode.child("entry"); entryNode;
|
||||
entryNode = entryNode.next_sibling("entry")) {
|
||||
kiwix::Book book;
|
||||
|
||||
book.setReadOnly(false);
|
||||
book.updateFromOpds(entryNode, urlHost);
|
||||
/* Check absolute and relative paths */
|
||||
this->checkAndCleanBookPaths(book, libraryPath);
|
||||
|
||||
/* Update the book properties with the new importer */
|
||||
manipulator.addBookToLibrary(book);
|
||||
if (libraryVersion.empty()
|
||||
|| atoi(libraryVersion.c_str()) <= atoi(KIWIX_LIBRARY_VERSION)) {
|
||||
if (!book.path.empty()) {
|
||||
ok = this->readBookFromPath(book.pathAbsolute);
|
||||
}
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
library.addBook(book);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool Manager::readOpds(const std::string& content, const std::string& urlHost)
|
||||
bool Manager::readXml(const string xml,
|
||||
const bool readOnly,
|
||||
const string libraryPath)
|
||||
{
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result
|
||||
= doc.load_buffer((void*)content.data(), content.size());
|
||||
= doc.load_buffer_inplace((void*)xml.data(), xml.size());
|
||||
|
||||
if (result) {
|
||||
this->parseOpdsDom(doc, urlHost);
|
||||
return true;
|
||||
this->parseXmlDom(doc, readOnly, libraryPath);
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Manager::readFile(
|
||||
const std::string& path,
|
||||
bool readOnly,
|
||||
bool trustLibrary)
|
||||
bool Manager::readFile(const string path, const bool readOnly)
|
||||
{
|
||||
return this->readFile(path, path, readOnly);
|
||||
}
|
||||
|
||||
bool Manager::readFile(const string nativePath,
|
||||
const string UTF8Path,
|
||||
const bool readOnly)
|
||||
{
|
||||
bool retVal = true;
|
||||
pugi::xml_document doc;
|
||||
|
||||
#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
|
||||
pugi::xml_parse_result result = doc.load_file(nativePath.c_str());
|
||||
|
||||
if (result) {
|
||||
this->parseXmlDom(doc, readOnly, path, trustLibrary);
|
||||
this->parseXmlDom(doc, readOnly, UTF8Path);
|
||||
} else {
|
||||
retVal = false;
|
||||
}
|
||||
@@ -210,37 +126,154 @@ bool Manager::readFile(
|
||||
* able to know where to save the library if new content are
|
||||
* available */
|
||||
if (!readOnly) {
|
||||
this->writableLibraryPath = path;
|
||||
this->writableLibraryPath = UTF8Path;
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
bool Manager::writeFile(const string path)
|
||||
{
|
||||
pugi::xml_document doc;
|
||||
|
||||
/* Add the library node */
|
||||
pugi::xml_node libraryNode = doc.append_child("library");
|
||||
|
||||
if (!getCurrentBookId().empty()) {
|
||||
libraryNode.append_attribute("current") = getCurrentBookId().c_str();
|
||||
}
|
||||
|
||||
if (!library.version.empty())
|
||||
libraryNode.append_attribute("version") = library.version.c_str();
|
||||
|
||||
/* Add each book */
|
||||
std::vector<kiwix::Book>::iterator itr;
|
||||
for (itr = library.books.begin(); itr != library.books.end(); ++itr) {
|
||||
if (!itr->readOnly) {
|
||||
this->checkAndCleanBookPaths(*itr, path);
|
||||
|
||||
pugi::xml_node bookNode = libraryNode.append_child("book");
|
||||
bookNode.append_attribute("id") = itr->id.c_str();
|
||||
|
||||
if (!itr->path.empty()) {
|
||||
bookNode.append_attribute("path") = itr->path.c_str();
|
||||
}
|
||||
|
||||
if (!itr->last.empty() && itr->last != "undefined") {
|
||||
bookNode.append_attribute("last") = itr->last.c_str();
|
||||
}
|
||||
|
||||
if (!itr->indexPath.empty())
|
||||
bookNode.append_attribute("indexPath") = itr->indexPath.c_str();
|
||||
|
||||
if (!itr->indexPath.empty() || !itr->indexPathAbsolute.empty()) {
|
||||
if (itr->indexType == XAPIAN) {
|
||||
bookNode.append_attribute("indexType") = "xapian";
|
||||
}
|
||||
}
|
||||
|
||||
if (itr->origId.empty()) {
|
||||
if (!itr->title.empty())
|
||||
bookNode.append_attribute("title") = itr->title.c_str();
|
||||
|
||||
if (!itr->name.empty())
|
||||
bookNode.append_attribute("name") = itr->name.c_str();
|
||||
|
||||
if (!itr->tags.empty())
|
||||
bookNode.append_attribute("tags") = itr->tags.c_str();
|
||||
|
||||
if (!itr->description.empty())
|
||||
bookNode.append_attribute("description") = itr->description.c_str();
|
||||
|
||||
if (!itr->language.empty())
|
||||
bookNode.append_attribute("language") = itr->language.c_str();
|
||||
|
||||
if (!itr->creator.empty())
|
||||
bookNode.append_attribute("creator") = itr->creator.c_str();
|
||||
|
||||
if (!itr->publisher.empty())
|
||||
bookNode.append_attribute("publisher") = itr->publisher.c_str();
|
||||
|
||||
if (!itr->favicon.empty())
|
||||
bookNode.append_attribute("favicon") = itr->favicon.c_str();
|
||||
|
||||
if (!itr->faviconMimeType.empty())
|
||||
bookNode.append_attribute("faviconMimeType")
|
||||
= itr->faviconMimeType.c_str();
|
||||
}
|
||||
|
||||
if (!itr->date.empty()) {
|
||||
bookNode.append_attribute("date") = itr->date.c_str();
|
||||
}
|
||||
|
||||
if (!itr->url.empty()) {
|
||||
bookNode.append_attribute("url") = itr->url.c_str();
|
||||
}
|
||||
|
||||
if (!itr->origId.empty())
|
||||
bookNode.append_attribute("origId") = itr->origId.c_str();
|
||||
|
||||
if (!itr->articleCount.empty())
|
||||
bookNode.append_attribute("articleCount") = itr->articleCount.c_str();
|
||||
|
||||
if (!itr->mediaCount.empty())
|
||||
bookNode.append_attribute("mediaCount") = itr->mediaCount.c_str();
|
||||
|
||||
if (!itr->size.empty()) {
|
||||
bookNode.append_attribute("size") = itr->size.c_str();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* saving file */
|
||||
doc.save_file(path.c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Manager::setCurrentBookId(const string id)
|
||||
{
|
||||
if (library.current.empty() || library.current.top() != id) {
|
||||
if (id.empty() && !library.current.empty()) {
|
||||
library.current.pop();
|
||||
} else {
|
||||
library.current.push(id);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
string Manager::getCurrentBookId()
|
||||
{
|
||||
return library.current.empty() ? "" : library.current.top();
|
||||
}
|
||||
|
||||
/* Add a book to the library. Return empty string if failed, book id otherwise
|
||||
*/
|
||||
std::string Manager::addBookFromPathAndGetId(const std::string& pathToOpen,
|
||||
const std::string& pathToSave,
|
||||
const std::string& url,
|
||||
const bool checkMetaData)
|
||||
string Manager::addBookFromPathAndGetId(const string pathToOpen,
|
||||
const string pathToSave,
|
||||
const string url,
|
||||
const bool checkMetaData)
|
||||
{
|
||||
kiwix::Book book;
|
||||
|
||||
if (this->readBookFromPath(pathToOpen, &book)) {
|
||||
if (!pathToSave.empty() && pathToSave != pathToOpen) {
|
||||
book.setPath(isRelativePath(pathToSave)
|
||||
if (pathToSave != pathToOpen) {
|
||||
book.path = pathToSave;
|
||||
book.pathAbsolute
|
||||
= isRelativePath(pathToSave)
|
||||
? computeAbsolutePath(
|
||||
removeLastPathElement(writableLibraryPath),
|
||||
removeLastPathElement(writableLibraryPath, true, false),
|
||||
pathToSave)
|
||||
: pathToSave);
|
||||
: pathToSave;
|
||||
}
|
||||
|
||||
if (!checkMetaData
|
||||
|| (!book.getTitle().empty() && !book.getLanguages().empty()
|
||||
&& !book.getDate().empty())) {
|
||||
book.setUrl(url);
|
||||
manipulator.addBookToLibrary(book);
|
||||
return book.getId();
|
||||
|| (checkMetaData && !book.title.empty() && !book.language.empty()
|
||||
&& !book.date.empty())) {
|
||||
book.url = url;
|
||||
library.addBook(book);
|
||||
return book.id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,9 +282,9 @@ std::string Manager::addBookFromPathAndGetId(const std::string& pathToOpen,
|
||||
|
||||
/* Wrapper over Manager::addBookFromPath which return a bool instead of a string
|
||||
*/
|
||||
bool Manager::addBookFromPath(const std::string& pathToOpen,
|
||||
const std::string& pathToSave,
|
||||
const std::string& url,
|
||||
bool Manager::addBookFromPath(const string pathToOpen,
|
||||
const string pathToSave,
|
||||
const string url,
|
||||
const bool checkMetaData)
|
||||
{
|
||||
return !(
|
||||
@@ -259,114 +292,367 @@ bool Manager::addBookFromPath(const std::string& pathToOpen,
|
||||
.empty());
|
||||
}
|
||||
|
||||
void Manager::addBooksFromDirectory(const std::string& path,
|
||||
const bool verboseFlag)
|
||||
bool Manager::readBookFromPath(const string path, kiwix::Book* book)
|
||||
{
|
||||
std::set<std::string> iteratedDirs;
|
||||
std::queue<std::string> dirQueue;
|
||||
dirQueue.push(fs::absolute(path).u8string());
|
||||
int totalBooksAdded = 0;
|
||||
if (verboseFlag)
|
||||
std::cout << "Adding books from the directory tree: " << dirQueue.front() << std::endl;
|
||||
|
||||
while (!dirQueue.empty()) {
|
||||
const auto currentPath = dirQueue.front();
|
||||
dirQueue.pop();
|
||||
if (verboseFlag)
|
||||
std::cout << "Visiting directory: " << currentPath << std::endl;
|
||||
for (const auto& dirEntry : fs::directory_iterator(currentPath)) {
|
||||
auto resolvedPath = dirEntry.path();
|
||||
if (fs::is_symlink(dirEntry)) {
|
||||
try {
|
||||
resolvedPath = fs::canonical(dirEntry.path());
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Could not resolve symlink " << resolvedPath.u8string() << " to a valid path. Skipping..." << std::endl;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
const std::string pathString = resolvedPath.u8string();
|
||||
std::string resolvedPathExtension = resolvedPath.extension();
|
||||
std::transform(resolvedPathExtension.begin(), resolvedPathExtension.end(), resolvedPathExtension.begin(),
|
||||
[](unsigned char c){ return std::tolower(c); });
|
||||
if (fs::is_directory(resolvedPath)) {
|
||||
if (iteratedDirs.find(pathString) == iteratedDirs.end())
|
||||
dirQueue.push(pathString);
|
||||
else if (verboseFlag)
|
||||
std::cout << "Already iterated over " << pathString << ". Skipping..." << std::endl;
|
||||
} else if (resolvedPathExtension == ".zim" || resolvedPathExtension == ".zimaa") {
|
||||
if (!this->addBookFromPath(pathString, pathString, "", false)) {
|
||||
std::cerr << "Could not add " << pathString << " into the library." << std::endl;
|
||||
} else if (verboseFlag) {
|
||||
std::cout << "Added " << pathString << " into the library." << std::endl;
|
||||
totalBooksAdded++;
|
||||
}
|
||||
} else if (verboseFlag) {
|
||||
std::cout << "Skipped " << pathString << " - unsupported file type or permission denied." << std::endl;
|
||||
}
|
||||
}
|
||||
iteratedDirs.insert(currentPath);
|
||||
}
|
||||
|
||||
if (verboseFlag)
|
||||
std::cout << "Traversal completed. Total books added: " << totalBooksAdded << std::endl;
|
||||
}
|
||||
|
||||
bool Manager::readBookFromPath(const std::string& path, kiwix::Book* book)
|
||||
{
|
||||
std::string tmp_path = path;
|
||||
if (isRelativePath(path)) {
|
||||
tmp_path = computeAbsolutePath(getCurrentDirectory(), path);
|
||||
}
|
||||
try {
|
||||
zim::Archive archive(tmp_path);
|
||||
book->update(archive);
|
||||
book->setPathValid(true);
|
||||
kiwix::Reader* reader = new kiwix::Reader(path);
|
||||
|
||||
if (book != NULL) {
|
||||
book->path = path;
|
||||
book->pathAbsolute = path;
|
||||
book->id = reader->getId();
|
||||
book->description = reader->getDescription();
|
||||
book->language = reader->getLanguage();
|
||||
book->date = reader->getDate();
|
||||
book->creator = reader->getCreator();
|
||||
book->publisher = reader->getPublisher();
|
||||
book->title = reader->getTitle();
|
||||
book->name = reader->getName();
|
||||
book->tags = reader->getTags();
|
||||
book->origId = reader->getOrigId();
|
||||
std::ostringstream articleCountStream;
|
||||
articleCountStream << reader->getArticleCount();
|
||||
book->articleCount = articleCountStream.str();
|
||||
|
||||
std::ostringstream mediaCountStream;
|
||||
mediaCountStream << reader->getMediaCount();
|
||||
book->mediaCount = mediaCountStream.str();
|
||||
|
||||
ostringstream convert;
|
||||
convert << reader->getFileSize();
|
||||
book->size = convert.str();
|
||||
|
||||
string favicon;
|
||||
string faviconMimeType;
|
||||
if (reader->getFavicon(favicon, faviconMimeType)) {
|
||||
book->favicon = base64_encode(
|
||||
reinterpret_cast<const unsigned char*>(favicon.c_str()),
|
||||
favicon.length());
|
||||
book->faviconMimeType = faviconMimeType;
|
||||
}
|
||||
}
|
||||
|
||||
delete reader;
|
||||
} catch (const std::exception& e) {
|
||||
book->setPathValid(false);
|
||||
std::cerr << e.what() << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Manager::readBookmarkFile(const std::string& path)
|
||||
bool Manager::removeBookByIndex(const unsigned int bookIndex)
|
||||
{
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result = doc.load_file(path.c_str());
|
||||
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
pugi::xml_node libraryNode = doc.child("bookmarks");
|
||||
|
||||
for (pugi::xml_node node = libraryNode.child("bookmark"); node;
|
||||
node = node.next_sibling("bookmark")) {
|
||||
kiwix::Bookmark bookmark;
|
||||
|
||||
bookmark.updateFromXml(node);
|
||||
|
||||
manipulator.addBookmarkToLibrary(bookmark);
|
||||
}
|
||||
|
||||
return true;
|
||||
return this->library.removeBookByIndex(bookIndex);
|
||||
}
|
||||
|
||||
void Manager::reload(const Paths& paths)
|
||||
bool Manager::removeBookById(const string id)
|
||||
{
|
||||
const auto libRevision = manipulator.getLibrary()->getRevision();
|
||||
for (std::string path : paths) {
|
||||
if (!path.empty()) {
|
||||
if ( kiwix::isRelativePath(path) )
|
||||
path = kiwix::computeAbsolutePath(kiwix::getCurrentDirectory(), path);
|
||||
unsigned int bookIndex = 0;
|
||||
std::vector<kiwix::Book>::iterator itr;
|
||||
for (itr = library.books.begin(); itr != library.books.end(); ++itr) {
|
||||
if (itr->id == id) {
|
||||
return this->library.removeBookByIndex(bookIndex);
|
||||
}
|
||||
bookIndex++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!readFile(path, false, true)) {
|
||||
throw std::runtime_error("Failed to load the XML library file '" + path + "'.");
|
||||
vector<string> Manager::getBooksLanguages()
|
||||
{
|
||||
std::vector<string> booksLanguages;
|
||||
std::vector<kiwix::Book>::iterator itr;
|
||||
std::map<string, bool> booksLanguagesMap;
|
||||
|
||||
std::sort(
|
||||
library.books.begin(), library.books.end(), kiwix::Book::sortByLanguage);
|
||||
for (itr = library.books.begin(); itr != library.books.end(); ++itr) {
|
||||
if (booksLanguagesMap.find(itr->language) == booksLanguagesMap.end()) {
|
||||
if (itr->origId.empty()) {
|
||||
booksLanguagesMap[itr->language] = true;
|
||||
booksLanguages.push_back(itr->language);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
manipulator.removeBooksNotUpdatedSince(libRevision);
|
||||
return booksLanguages;
|
||||
}
|
||||
|
||||
vector<string> Manager::getBooksCreators()
|
||||
{
|
||||
std::vector<string> booksCreators;
|
||||
std::vector<kiwix::Book>::iterator itr;
|
||||
std::map<string, bool> booksCreatorsMap;
|
||||
|
||||
std::sort(
|
||||
library.books.begin(), library.books.end(), kiwix::Book::sortByCreator);
|
||||
for (itr = library.books.begin(); itr != library.books.end(); ++itr) {
|
||||
if (booksCreatorsMap.find(itr->creator) == booksCreatorsMap.end()) {
|
||||
if (itr->origId.empty()) {
|
||||
booksCreatorsMap[itr->creator] = true;
|
||||
booksCreators.push_back(itr->creator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return booksCreators;
|
||||
}
|
||||
|
||||
vector<string> Manager::getBooksIds()
|
||||
{
|
||||
std::vector<string> booksIds;
|
||||
std::vector<kiwix::Book>::iterator itr;
|
||||
|
||||
for (itr = library.books.begin(); itr != library.books.end(); ++itr) {
|
||||
booksIds.push_back(itr->id);
|
||||
}
|
||||
|
||||
return booksIds;
|
||||
}
|
||||
|
||||
vector<string> Manager::getBooksPublishers()
|
||||
{
|
||||
std::vector<string> booksPublishers;
|
||||
std::vector<kiwix::Book>::iterator itr;
|
||||
std::map<string, bool> booksPublishersMap;
|
||||
|
||||
std::sort(
|
||||
library.books.begin(), library.books.end(), kiwix::Book::sortByPublisher);
|
||||
for (itr = library.books.begin(); itr != library.books.end(); ++itr) {
|
||||
if (booksPublishersMap.find(itr->publisher) == booksPublishersMap.end()) {
|
||||
if (itr->origId.empty()) {
|
||||
booksPublishersMap[itr->publisher] = true;
|
||||
booksPublishers.push_back(itr->publisher);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return booksPublishers;
|
||||
}
|
||||
|
||||
kiwix::Library Manager::cloneLibrary()
|
||||
{
|
||||
return this->library;
|
||||
}
|
||||
bool Manager::getCurrentBook(Book& book)
|
||||
{
|
||||
string currentBookId = getCurrentBookId();
|
||||
if (currentBookId.empty()) {
|
||||
return false;
|
||||
} else {
|
||||
getBookById(currentBookId, book);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool Manager::getBookById(const string id, Book& book)
|
||||
{
|
||||
std::vector<kiwix::Book>::iterator itr;
|
||||
for (itr = library.books.begin(); itr != library.books.end(); ++itr) {
|
||||
if (itr->id == id) {
|
||||
book = *itr;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Manager::updateBookLastOpenDateById(const string id)
|
||||
{
|
||||
std::vector<kiwix::Book>::iterator itr;
|
||||
for (itr = library.books.begin(); itr != library.books.end(); ++itr) {
|
||||
if (itr->id == id) {
|
||||
char unixdate[12];
|
||||
sprintf(unixdate, "%d", (int)time(NULL));
|
||||
itr->last = unixdate;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Manager::setBookIndex(const string id,
|
||||
const string path,
|
||||
const supportedIndexType type)
|
||||
{
|
||||
std::vector<kiwix::Book>::iterator itr;
|
||||
for (itr = library.books.begin(); itr != library.books.end(); ++itr) {
|
||||
if (itr->id == id) {
|
||||
itr->indexPath = path;
|
||||
itr->indexPathAbsolute
|
||||
= isRelativePath(path)
|
||||
? computeAbsolutePath(
|
||||
removeLastPathElement(writableLibraryPath, true, false),
|
||||
path)
|
||||
: path;
|
||||
itr->indexType = type;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Manager::setBookIndex(const string id, const string path)
|
||||
{
|
||||
return this->setBookIndex(id, path, XAPIAN);
|
||||
}
|
||||
|
||||
bool Manager::setBookPath(const string id, const string path)
|
||||
{
|
||||
std::vector<kiwix::Book>::iterator itr;
|
||||
for (itr = library.books.begin(); itr != library.books.end(); ++itr) {
|
||||
if (itr->id == id) {
|
||||
itr->path = path;
|
||||
itr->pathAbsolute
|
||||
= isRelativePath(path)
|
||||
? computeAbsolutePath(
|
||||
removeLastPathElement(writableLibraryPath, true, false),
|
||||
path)
|
||||
: path;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Manager::removeBookPaths()
|
||||
{
|
||||
std::vector<kiwix::Book>::iterator itr;
|
||||
for (itr = library.books.begin(); itr != library.books.end(); ++itr) {
|
||||
itr->path = "";
|
||||
itr->pathAbsolute = "";
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int Manager::getBookCount(const bool localBooks,
|
||||
const bool remoteBooks)
|
||||
{
|
||||
unsigned int result = 0;
|
||||
std::vector<kiwix::Book>::iterator itr;
|
||||
for (itr = library.books.begin(); itr != library.books.end(); ++itr) {
|
||||
if ((!itr->path.empty() && localBooks)
|
||||
|| (itr->path.empty() && remoteBooks)) {
|
||||
result++;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Manager::listBooks(const supportedListMode mode,
|
||||
const supportedListSortBy sortBy,
|
||||
const unsigned int maxSize,
|
||||
const string language,
|
||||
const string creator,
|
||||
const string publisher,
|
||||
const string search)
|
||||
{
|
||||
this->bookIdList.clear();
|
||||
std::vector<kiwix::Book>::iterator itr;
|
||||
|
||||
/* Sort */
|
||||
if (sortBy == TITLE) {
|
||||
std::sort(
|
||||
library.books.begin(), library.books.end(), kiwix::Book::sortByTitle);
|
||||
} else if (sortBy == SIZE) {
|
||||
std::sort(
|
||||
library.books.begin(), library.books.end(), kiwix::Book::sortBySize);
|
||||
} else if (sortBy == DATE) {
|
||||
std::sort(
|
||||
library.books.begin(), library.books.end(), kiwix::Book::sortByDate);
|
||||
} else if (sortBy == CREATOR) {
|
||||
std::sort(
|
||||
library.books.begin(), library.books.end(), kiwix::Book::sortByCreator);
|
||||
} else if (sortBy == PUBLISHER) {
|
||||
std::sort(library.books.begin(),
|
||||
library.books.end(),
|
||||
kiwix::Book::sortByPublisher);
|
||||
}
|
||||
|
||||
/* Special sort for LASTOPEN */
|
||||
if (mode == LASTOPEN) {
|
||||
std::sort(library.books.begin(),
|
||||
library.books.end(),
|
||||
kiwix::Book::sortByLastOpen);
|
||||
for (itr = library.books.begin(); itr != library.books.end(); ++itr) {
|
||||
if (!itr->last.empty()) {
|
||||
this->bookIdList.push_back(itr->id);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Generate the list of book id */
|
||||
for (itr = library.books.begin(); itr != library.books.end(); ++itr) {
|
||||
bool ok = true;
|
||||
|
||||
if (mode == LOCAL && itr->path.empty()) {
|
||||
ok = false;
|
||||
}
|
||||
|
||||
if (ok == true && mode == REMOTE
|
||||
&& (!itr->path.empty() || itr->url.empty())) {
|
||||
ok = false;
|
||||
}
|
||||
|
||||
if (ok == true && maxSize != 0
|
||||
&& (unsigned int)atoi(itr->size.c_str()) > maxSize * 1024 * 1024) {
|
||||
ok = false;
|
||||
}
|
||||
|
||||
if (ok == true && !language.empty()
|
||||
&& !matchRegex(itr->language, language)) {
|
||||
ok = false;
|
||||
}
|
||||
|
||||
if (ok == true && !creator.empty() && itr->creator != creator) {
|
||||
ok = false;
|
||||
}
|
||||
|
||||
if (ok == true && !publisher.empty() && itr->publisher != publisher) {
|
||||
ok = false;
|
||||
}
|
||||
|
||||
if ((ok == true && !search.empty())
|
||||
&& !(matchRegex(itr->title, "\\Q" + search + "\\E")
|
||||
|| matchRegex(itr->description, "\\Q" + search + "\\E")
|
||||
|| matchRegex(itr->language, "\\Q" + search + "\\E"))) {
|
||||
ok = false;
|
||||
}
|
||||
|
||||
if (ok == true) {
|
||||
this->bookIdList.push_back(itr->id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Manager::checkAndCleanBookPaths(Book& book, const string& libraryPath)
|
||||
{
|
||||
if (!book.path.empty()) {
|
||||
if (isRelativePath(book.path)) {
|
||||
book.pathAbsolute = computeAbsolutePath(
|
||||
removeLastPathElement(libraryPath, true, false), book.path);
|
||||
} else {
|
||||
book.pathAbsolute = book.path;
|
||||
book.path = computeRelativePath(
|
||||
removeLastPathElement(libraryPath, true, false), book.pathAbsolute);
|
||||
}
|
||||
}
|
||||
|
||||
if (!book.indexPath.empty()) {
|
||||
if (isRelativePath(book.indexPath)) {
|
||||
book.indexPathAbsolute = computeAbsolutePath(
|
||||
removeLastPathElement(libraryPath, true, false), book.indexPath);
|
||||
} else {
|
||||
book.indexPathAbsolute = book.indexPath;
|
||||
book.indexPath
|
||||
= computeRelativePath(removeLastPathElement(libraryPath, true, false),
|
||||
book.indexPathAbsolute);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,60 +1,44 @@
|
||||
kiwix_sources = [
|
||||
'book.cpp',
|
||||
'bookmark.cpp',
|
||||
'library.cpp',
|
||||
'manager.cpp',
|
||||
'libxml_dumper.cpp',
|
||||
'opds_dumper.cpp',
|
||||
'html_dumper.cpp',
|
||||
'library_dumper.cpp',
|
||||
'downloader.cpp',
|
||||
'server.cpp',
|
||||
'search_renderer.cpp',
|
||||
'subprocess.cpp',
|
||||
'aria2.cpp',
|
||||
'tools/base64.cpp',
|
||||
'tools/pathTools.cpp',
|
||||
'tools/regexTools.cpp',
|
||||
'tools/stringTools.cpp',
|
||||
'tools/networkTools.cpp',
|
||||
'tools/opdsParsingTools.cpp',
|
||||
'tools/languageTools.cpp',
|
||||
'tools/otherTools.cpp',
|
||||
'tools/archiveTools.cpp',
|
||||
'kiwixserve.cpp',
|
||||
'name_mapper.cpp',
|
||||
'server/byte_range.cpp',
|
||||
'server/etag.cpp',
|
||||
'server/request_context.cpp',
|
||||
'server/response.cpp',
|
||||
'server/internalServer.cpp',
|
||||
'server/internalServer_catalog.cpp',
|
||||
'server/i18n.cpp',
|
||||
'opds_catalog.cpp',
|
||||
'spelling_correction.cpp',
|
||||
'version.cpp'
|
||||
'reader.cpp',
|
||||
'searcher.cpp',
|
||||
'common/base64.cpp',
|
||||
'common/pathTools.cpp',
|
||||
'common/regexTools.cpp',
|
||||
'common/stringTools.cpp',
|
||||
'common/networkTools.cpp',
|
||||
'common/otherTools.cpp',
|
||||
'xapian/htmlparse.cc',
|
||||
'xapian/myhtmlparse.cc'
|
||||
]
|
||||
kiwix_sources += lib_resources
|
||||
kiwix_sources += i18n_resources
|
||||
|
||||
if host_machine.system() == 'windows'
|
||||
kiwix_sources += 'subprocess_windows.cpp'
|
||||
else
|
||||
kiwix_sources += 'subprocess_unix.cpp'
|
||||
if xapian_dep.found()
|
||||
kiwix_sources += ['xapianSearcher.cpp']
|
||||
endif
|
||||
|
||||
install_dir = get_option('libdir')
|
||||
if get_option('android')
|
||||
subdir('android')
|
||||
install_dir = 'kiwix-lib/jniLibs/' + meson.get_cross_property('android_abi')
|
||||
else
|
||||
install_dir = get_option('libdir')
|
||||
endif
|
||||
|
||||
|
||||
if has_ctpp2_dep
|
||||
kiwix_sources += ['ctpp2/CTPP2VMStringLoader.cpp']
|
||||
endif
|
||||
|
||||
config_h = configure_file(output : 'kiwix_config.h',
|
||||
configuration : conf,
|
||||
input : 'config.h.in')
|
||||
install_headers(config_h, subdir:'kiwix')
|
||||
|
||||
libkiwix = library('kiwix',
|
||||
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,108 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 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 "name_mapper.h"
|
||||
#include "library.h"
|
||||
#include "tools/regexTools.h"
|
||||
#include <iostream>
|
||||
|
||||
namespace kiwix {
|
||||
|
||||
HumanReadableNameMapper::HumanReadableNameMapper(const kiwix::Library& library, bool withAlias) {
|
||||
for (auto& bookId: library.filter(kiwix::Filter())) {
|
||||
auto& currentBook = library.getBookById(bookId);
|
||||
auto bookName = currentBook.getHumanReadableIdFromPath();
|
||||
m_idToName[bookId] = bookName;
|
||||
mapName(library, bookName, bookId);
|
||||
|
||||
if (!withAlias)
|
||||
continue;
|
||||
|
||||
auto aliasName = replaceRegex(bookName, "", "_[[:digit:]]{4}-[[:digit:]]{2}$");
|
||||
if (aliasName != bookName) {
|
||||
mapName(library, aliasName, bookId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HumanReadableNameMapper::mapName(const Library& library, std::string name, std::string bookId) {
|
||||
if (m_nameToId.find(name) == m_nameToId.end()) {
|
||||
m_nameToId[name] = bookId;
|
||||
} else {
|
||||
const auto& currentBook = library.getBookById(bookId);
|
||||
auto alreadyPresentPath = library.getBookById(m_nameToId[name]).getPath();
|
||||
std::cerr << "Path collision: '" << alreadyPresentPath
|
||||
<< "' and '" << currentBook.getPath()
|
||||
<< "' can't share the same URL path '" << name << "'."
|
||||
<< " Therefore, only '" << alreadyPresentPath
|
||||
<< "' will be served." << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
std::string HumanReadableNameMapper::getNameForId(const std::string& id) const {
|
||||
return m_idToName.at(id);
|
||||
}
|
||||
|
||||
std::string HumanReadableNameMapper::getIdForName(const std::string& name) const {
|
||||
return m_nameToId.at(name);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// UpdatableNameMapper
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
UpdatableNameMapper::UpdatableNameMapper(LibraryPtr lib, bool withAlias)
|
||||
: library(lib)
|
||||
, withAlias(withAlias)
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
void UpdatableNameMapper::update()
|
||||
{
|
||||
const auto newNameMapper = new HumanReadableNameMapper(*library, withAlias);
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
nameMapper.reset(newNameMapper);
|
||||
}
|
||||
|
||||
UpdatableNameMapper::NameMapperHandle
|
||||
UpdatableNameMapper::currentNameMapper() const
|
||||
{
|
||||
// Return a copy of the handle to the current NameMapper object. It will
|
||||
// ensure that the object survives any call to UpdatableNameMapper::update()
|
||||
// made before the completion of any pending operation on that object.
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
return nameMapper;
|
||||
}
|
||||
|
||||
std::string UpdatableNameMapper::getNameForId(const std::string& id) const
|
||||
{
|
||||
// Ensure that the current nameMapper object survives a concurrent call
|
||||
// to UpdatableNameMapper::update()
|
||||
return currentNameMapper()->getNameForId(id);
|
||||
}
|
||||
|
||||
std::string UpdatableNameMapper::getIdForName(const std::string& name) const
|
||||
{
|
||||
// Ensure that the current nameMapper object survives a concurrent call
|
||||
// to UpdatableNameMapper::update()
|
||||
return currentNameMapper()->getIdForName(name);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 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 "opds_catalog.h"
|
||||
#include "tools/stringTools.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
const char opdsSearchEndpoint[] = "/catalog/v2/entries";
|
||||
|
||||
enum Separator { AMP };
|
||||
|
||||
std::ostringstream& operator<<(std::ostringstream& oss, Separator sep)
|
||||
{
|
||||
if ( oss.tellp() > 0 )
|
||||
oss << "&";
|
||||
return oss;
|
||||
}
|
||||
|
||||
std::string buildSearchString(const Filter& f)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
if ( f.hasQuery() )
|
||||
oss << AMP << "q=" << urlEncode(f.getQuery());
|
||||
|
||||
if ( f.hasCategory() )
|
||||
oss << AMP << "category=" << urlEncode(f.getCategory());
|
||||
|
||||
if ( f.hasLang() )
|
||||
oss << AMP << "lang=" << urlEncode(f.getLang());
|
||||
|
||||
if ( f.hasName() )
|
||||
oss << AMP << "name=" << urlEncode(f.getName());
|
||||
|
||||
if ( !f.getAcceptTags().empty() )
|
||||
oss << AMP << "tag=" << urlEncode(join(f.getAcceptTags(), ";"));
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
std::string getSearchUrl(const Filter& f)
|
||||
{
|
||||
const std::string searchString = buildSearchString(f);
|
||||
|
||||
if ( searchString.empty() )
|
||||
return opdsSearchEndpoint;
|
||||
else
|
||||
return opdsSearchEndpoint + ("?" + searchString);
|
||||
}
|
||||
|
||||
} // namespace kiwix
|
||||
@@ -1,215 +0,0 @@
|
||||
/*
|
||||
* Copyright 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 "opds_dumper.h"
|
||||
#include "book.h"
|
||||
|
||||
#include "libkiwix-resources.h"
|
||||
#include <mustache.hpp>
|
||||
|
||||
#include "tools/stringTools.h"
|
||||
#include "tools/otherTools.h"
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
/* Constructor */
|
||||
OPDSDumper::OPDSDumper(const Library* library, const NameMapper* nameMapper)
|
||||
: LibraryDumper(library, nameMapper)
|
||||
{
|
||||
}
|
||||
/* Destructor */
|
||||
OPDSDumper::~OPDSDumper()
|
||||
{
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
const std::string XML_HEADER(R"(<?xml version="1.0" encoding="UTF-8"?>)");
|
||||
|
||||
typedef kainjow::mustache::data MustacheData;
|
||||
typedef kainjow::mustache::list BooksData;
|
||||
typedef kainjow::mustache::list IllustrationInfo;
|
||||
|
||||
IllustrationInfo getBookIllustrationInfo(const Book& book)
|
||||
{
|
||||
kainjow::mustache::list illustrations;
|
||||
for ( const auto& illustration : book.getIllustrations() ) {
|
||||
// For now, we are handling only sizexsize@1 illustration.
|
||||
// So we can simply pass one size to mustache.
|
||||
illustrations.push_back(kainjow::mustache::object{
|
||||
{"icon_size", to_string(illustration->width)},
|
||||
{"icon_mimetype", illustration->mimeType}
|
||||
});
|
||||
}
|
||||
return illustrations;
|
||||
}
|
||||
|
||||
std::string fullEntryXML(const Book& book,
|
||||
const std::string& rootLocation,
|
||||
const std::string& contentAccessUrl,
|
||||
const std::string& contentId)
|
||||
{
|
||||
const auto bookDate = book.getDate() + "T00:00:00Z";
|
||||
const kainjow::mustache::object data{
|
||||
{"root", rootLocation},
|
||||
{"contentAccessUrl", onlyAsNonEmptyMustacheValue(contentAccessUrl)},
|
||||
{"id", book.getId()},
|
||||
{"name", book.getName()},
|
||||
{"title", book.getTitle()},
|
||||
{"description", book.getDescription()},
|
||||
{"language", book.getCommaSeparatedLanguages()},
|
||||
{"content_id", urlEncode(contentId)},
|
||||
{"updated", bookDate}, // XXX: this should be the entry update datetime
|
||||
{"book_date", bookDate},
|
||||
{"category", book.getCategory()},
|
||||
{"flavour", book.getFlavour()},
|
||||
{"tags", book.getTags()},
|
||||
{"article_count", to_string(book.getArticleCount())},
|
||||
{"media_count", to_string(book.getMediaCount())},
|
||||
{"author_name", book.getCreator()},
|
||||
{"publisher_name", book.getPublisher()},
|
||||
{"url", onlyAsNonEmptyMustacheValue(book.getUrl())},
|
||||
{"size", to_string(book.getSize())},
|
||||
{"icons", getBookIllustrationInfo(book)},
|
||||
};
|
||||
return render_template(RESOURCE::templates::catalog_v2_entry_xml, data);
|
||||
}
|
||||
|
||||
std::string partialEntryXML(const Book& book, const std::string& rootLocation)
|
||||
{
|
||||
const auto bookDate = book.getDate() + "T00:00:00Z";
|
||||
const kainjow::mustache::object data{
|
||||
{"root", rootLocation},
|
||||
{"endpoint_root", rootLocation + "/catalog/v2"},
|
||||
{"id", book.getId()},
|
||||
{"title", book.getTitle()},
|
||||
{"updated", bookDate}, // XXX: this should be the entry update datetime
|
||||
};
|
||||
const auto xmlTemplate = RESOURCE::templates::catalog_v2_partial_entry_xml;
|
||||
return render_template(xmlTemplate, data);
|
||||
}
|
||||
|
||||
BooksData getBooksData(const Library* library,
|
||||
const NameMapper* nameMapper,
|
||||
const std::vector<std::string>& bookIds,
|
||||
const std::string& rootLocation,
|
||||
const std::string& contentAccessUrl,
|
||||
bool partial)
|
||||
{
|
||||
BooksData booksData;
|
||||
for ( const auto& bookId : bookIds ) {
|
||||
try {
|
||||
const Book book = library->getBookByIdThreadSafe(bookId);
|
||||
const std::string contentId = nameMapper->getNameForId(bookId);
|
||||
const auto entryXML = partial
|
||||
? partialEntryXML(book, rootLocation)
|
||||
: fullEntryXML(book, rootLocation, contentAccessUrl, contentId);
|
||||
booksData.push_back(kainjow::mustache::object{ {"entry", entryXML} });
|
||||
} catch ( const std::out_of_range& ) {
|
||||
// the book was removed from the library since its id was obtained
|
||||
// ignore it
|
||||
}
|
||||
}
|
||||
|
||||
return booksData;
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
string OPDSDumper::dumpOPDSFeed(const std::vector<std::string>& bookIds, const std::string& query) const
|
||||
{
|
||||
const auto booksData = getBooksData(library, nameMapper, bookIds, rootLocation, contentAccessUrl, false);
|
||||
const kainjow::mustache::object template_data{
|
||||
{"date", gen_date_str()},
|
||||
{"root", rootLocation},
|
||||
{"feed_id", gen_uuid(libraryId + "/catalog/search?"+query)},
|
||||
{"filter", onlyAsNonEmptyMustacheValue(query)},
|
||||
{"totalResults", to_string(m_totalResults)},
|
||||
{"startIndex", to_string(m_startIndex)},
|
||||
{"itemsPerPage", to_string(m_count)},
|
||||
{"books", booksData }
|
||||
};
|
||||
|
||||
return render_template(RESOURCE::templates::catalog_entries_xml, template_data);
|
||||
}
|
||||
|
||||
string OPDSDumper::dumpOPDSFeedV2(const std::vector<std::string>& bookIds, const std::string& query, bool partial) const
|
||||
{
|
||||
const auto endpointRoot = rootLocation + "/catalog/v2";
|
||||
const auto booksData = getBooksData(library, nameMapper, bookIds, rootLocation, contentAccessUrl, partial);
|
||||
|
||||
const char* const endpoint = partial ? "/partial_entries" : "/entries";
|
||||
const std::string url = endpoint + (query.empty() ? "" : "?" + query);
|
||||
const kainjow::mustache::object template_data{
|
||||
{"date", gen_date_str()},
|
||||
{"endpoint_root", endpointRoot},
|
||||
{"feed_id", gen_uuid(libraryId + endpoint + "?" + query)},
|
||||
{"filter", onlyAsNonEmptyMustacheValue(query)},
|
||||
{"self_url", url},
|
||||
{"totalResults", to_string(m_totalResults)},
|
||||
{"startIndex", to_string(m_startIndex)},
|
||||
{"itemsPerPage", to_string(m_count)},
|
||||
{"books", booksData }
|
||||
};
|
||||
|
||||
return render_template(RESOURCE::templates::catalog_v2_entries_xml, template_data);
|
||||
}
|
||||
|
||||
std::string OPDSDumper::dumpOPDSCompleteEntry(const std::string& bookId) const
|
||||
{
|
||||
const auto book = library->getBookById(bookId);
|
||||
const std::string contentId = nameMapper->getNameForId(bookId);
|
||||
return XML_HEADER
|
||||
+ "\n"
|
||||
+ fullEntryXML(book, rootLocation, contentAccessUrl, contentId);
|
||||
}
|
||||
|
||||
std::string OPDSDumper::categoriesOPDSFeed() const
|
||||
{
|
||||
const auto now = gen_date_str();
|
||||
kainjow::mustache::list categoryData = getCategoryData();
|
||||
return render_template(
|
||||
RESOURCE::templates::catalog_v2_categories_xml,
|
||||
kainjow::mustache::object{
|
||||
{"date", now},
|
||||
{"endpoint_root", rootLocation + "/catalog/v2"},
|
||||
{"feed_id", gen_uuid(libraryId + "/categories")},
|
||||
{"categories", categoryData }
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
std::string OPDSDumper::languagesOPDSFeed() const
|
||||
{
|
||||
const auto now = gen_date_str();
|
||||
kainjow::mustache::list languageData = getLanguageData();
|
||||
return render_template(
|
||||
RESOURCE::templates::catalog_v2_languages_xml,
|
||||
kainjow::mustache::object{
|
||||
{"date", now},
|
||||
{"endpoint_root", rootLocation + "/catalog/v2"},
|
||||
{"feed_id", gen_uuid(libraryId + "/languages")},
|
||||
{"languages", languageData }
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Copyright 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.
|
||||
*/
|
||||
|
||||
#ifndef KIWIX_OPDS_DUMPER_H
|
||||
#define KIWIX_OPDS_DUMPER_H
|
||||
|
||||
#include <time.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include <pugixml.hpp>
|
||||
|
||||
#include "library.h"
|
||||
#include "name_mapper.h"
|
||||
#include "library_dumper.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
/**
|
||||
* A tool to dump a `Library` into a opds stream.
|
||||
*
|
||||
*/
|
||||
class OPDSDumper : public LibraryDumper
|
||||
{
|
||||
public:
|
||||
OPDSDumper(const Library* library, const NameMapper* NameMapper);
|
||||
~OPDSDumper();
|
||||
|
||||
/**
|
||||
* Dump the OPDS feed.
|
||||
*
|
||||
* @param bookIds the ids of the books to include in the feed
|
||||
* @param query the query used to obtain the list of book ids
|
||||
* @return The OPDS feed.
|
||||
*/
|
||||
std::string dumpOPDSFeed(const std::vector<std::string>& bookIds, const std::string& query) const;
|
||||
|
||||
/**
|
||||
* Dump the OPDS feed.
|
||||
*
|
||||
* @param bookIds the ids of the books to include in the feed
|
||||
* @param query the query used to obtain the list of book ids
|
||||
* @param partial whether the feed should include partial or complete entries
|
||||
* @return The OPDS feed.
|
||||
*/
|
||||
std::string dumpOPDSFeedV2(const std::vector<std::string>& bookIds, const std::string& query, bool partial) const;
|
||||
|
||||
/**
|
||||
* Dump the OPDS complete entry document.
|
||||
*
|
||||
* @param bookId the id of the book
|
||||
* @return The OPDS complete entry document.
|
||||
*/
|
||||
std::string dumpOPDSCompleteEntry(const std::string& bookId) const;
|
||||
|
||||
/**
|
||||
* Dump the categories OPDS feed.
|
||||
*
|
||||
* @return The OPDS feed.
|
||||
*/
|
||||
std::string categoriesOPDSFeed() const;
|
||||
|
||||
/**
|
||||
* Dump the languages OPDS feed.
|
||||
*
|
||||
* @return The OPDS feed.
|
||||
*/
|
||||
std::string languagesOPDSFeed() const;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // KIWIX_OPDS_DUMPER_H
|
||||
787
src/reader.cpp
Normal file
787
src/reader.cpp
Normal file
@@ -0,0 +1,787 @@
|
||||
/*
|
||||
* Copyright 2011 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "reader.h"
|
||||
#include <time.h>
|
||||
|
||||
#include <zim/search.h>
|
||||
|
||||
inline char hi(char v)
|
||||
{
|
||||
char hex[] = "0123456789abcdef";
|
||||
return hex[(v >> 4) & 0xf];
|
||||
}
|
||||
|
||||
inline char lo(char v)
|
||||
{
|
||||
char hex[] = "0123456789abcdef";
|
||||
return hex[v & 0xf];
|
||||
}
|
||||
|
||||
std::string hexUUID(std::string in)
|
||||
{
|
||||
std::ostringstream out;
|
||||
for (unsigned n = 0; n < 4; ++n) {
|
||||
out << hi(in[n]) << lo(in[n]);
|
||||
}
|
||||
out << '-';
|
||||
for (unsigned n = 4; n < 6; ++n) {
|
||||
out << hi(in[n]) << lo(in[n]);
|
||||
}
|
||||
out << '-';
|
||||
for (unsigned n = 6; n < 8; ++n) {
|
||||
out << hi(in[n]) << lo(in[n]);
|
||||
}
|
||||
out << '-';
|
||||
for (unsigned n = 8; n < 10; ++n) {
|
||||
out << hi(in[n]) << lo(in[n]);
|
||||
}
|
||||
out << '-';
|
||||
for (unsigned n = 10; n < 16; ++n) {
|
||||
out << hi(in[n]) << lo(in[n]);
|
||||
}
|
||||
std::string op = out.str();
|
||||
return op;
|
||||
}
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
/* Constructor */
|
||||
Reader::Reader(const string zimFilePath) : zimFileHandler(NULL)
|
||||
{
|
||||
string tmpZimFilePath = zimFilePath;
|
||||
|
||||
/* Remove potential trailing zimaa */
|
||||
size_t found = tmpZimFilePath.rfind("zimaa");
|
||||
if (found != string::npos && tmpZimFilePath.size() > 5
|
||||
&& found == tmpZimFilePath.size() - 5) {
|
||||
tmpZimFilePath.resize(tmpZimFilePath.size() - 2);
|
||||
}
|
||||
|
||||
this->zimFileHandler = new zim::File(tmpZimFilePath);
|
||||
|
||||
if (this->zimFileHandler != NULL) {
|
||||
this->firstArticleOffset
|
||||
= this->zimFileHandler->getNamespaceBeginOffset('A');
|
||||
this->lastArticleOffset = this->zimFileHandler->getNamespaceEndOffset('A');
|
||||
this->currentArticleOffset = this->firstArticleOffset;
|
||||
this->nsACount = this->zimFileHandler->getNamespaceCount('A');
|
||||
this->nsICount = this->zimFileHandler->getNamespaceCount('I');
|
||||
this->zimFilePath = zimFilePath;
|
||||
}
|
||||
|
||||
/* initialize random seed: */
|
||||
srand(time(NULL));
|
||||
}
|
||||
|
||||
/* Destructor */
|
||||
Reader::~Reader()
|
||||
{
|
||||
if (this->zimFileHandler != NULL) {
|
||||
delete this->zimFileHandler;
|
||||
}
|
||||
}
|
||||
|
||||
zim::File* Reader::getZimFileHandler() const
|
||||
{
|
||||
return this->zimFileHandler;
|
||||
}
|
||||
/* Reset the cursor for GetNextArticle() */
|
||||
void Reader::reset()
|
||||
{
|
||||
this->currentArticleOffset = this->firstArticleOffset;
|
||||
}
|
||||
std::map<const std::string, unsigned int> Reader::parseCounterMetadata() const
|
||||
{
|
||||
std::map<const std::string, unsigned int> counters;
|
||||
string mimeType, item, counterString;
|
||||
unsigned int counter;
|
||||
|
||||
zim::Article article = this->zimFileHandler->getArticle('M', "Counter");
|
||||
|
||||
if (article.good()) {
|
||||
stringstream ssContent(article.getData());
|
||||
|
||||
while (getline(ssContent, item, ';')) {
|
||||
stringstream ssItem(item);
|
||||
getline(ssItem, mimeType, '=');
|
||||
getline(ssItem, counterString, '=');
|
||||
if (!counterString.empty() && !mimeType.empty()) {
|
||||
sscanf(counterString.c_str(), "%u", &counter);
|
||||
counters.insert(pair<string, int>(mimeType, counter));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return counters;
|
||||
}
|
||||
|
||||
/* Get the count of articles which can be indexed/displayed */
|
||||
unsigned int Reader::getArticleCount() const
|
||||
{
|
||||
std::map<const std::string, unsigned int> counterMap
|
||||
= this->parseCounterMetadata();
|
||||
unsigned int counter = 0;
|
||||
|
||||
if (counterMap.empty()) {
|
||||
counter = this->nsACount;
|
||||
} else {
|
||||
auto it = counterMap.find("text/html");
|
||||
if (it != counterMap.end()) {
|
||||
counter = it->second;
|
||||
}
|
||||
}
|
||||
|
||||
return counter;
|
||||
}
|
||||
|
||||
/* Get the count of medias content in the ZIM file */
|
||||
unsigned int Reader::getMediaCount() const
|
||||
{
|
||||
std::map<const std::string, unsigned int> counterMap
|
||||
= this->parseCounterMetadata();
|
||||
unsigned int counter = 0;
|
||||
|
||||
if (counterMap.empty()) {
|
||||
counter = this->nsICount;
|
||||
} else {
|
||||
auto it = counterMap.find("image/jpeg");
|
||||
if (it != counterMap.end()) {
|
||||
counter += it->second;
|
||||
}
|
||||
|
||||
it = counterMap.find("image/gif");
|
||||
if (it != counterMap.end()) {
|
||||
counter += it->second;
|
||||
}
|
||||
|
||||
it = counterMap.find("image/png");
|
||||
if (it != counterMap.end()) {
|
||||
counter += it->second;
|
||||
}
|
||||
}
|
||||
return counter;
|
||||
}
|
||||
|
||||
/* Get the total of all items of a ZIM file, redirects included */
|
||||
unsigned int Reader::getGlobalCount() const
|
||||
{
|
||||
return this->zimFileHandler->getCountArticles();
|
||||
}
|
||||
|
||||
/* Return the UID of the ZIM file */
|
||||
string Reader::getId() const
|
||||
{
|
||||
std::ostringstream s;
|
||||
s << this->zimFileHandler->getFileheader().getUuid();
|
||||
return s.str();
|
||||
}
|
||||
|
||||
/* Return a page url from a title */
|
||||
bool Reader::getPageUrlFromTitle(const string& title, string& url) const
|
||||
{
|
||||
/* Extract the content from the zim file */
|
||||
zim::Article article = this->zimFileHandler->getArticleByTitle('A', title);
|
||||
|
||||
if (!article.good()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned int loopCounter = 0;
|
||||
while (article.isRedirect() && loopCounter++ < 42) {
|
||||
article = article.getRedirectArticle();
|
||||
}
|
||||
|
||||
url = article.getLongUrl();
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Return an URL from a title */
|
||||
string Reader::getRandomPageUrl() const
|
||||
{
|
||||
zim::Article article;
|
||||
zim::size_type idx;
|
||||
std::string mainPageUrl = this->getMainPageUrl();
|
||||
|
||||
do {
|
||||
idx = this->firstArticleOffset
|
||||
+ (zim::size_type)((double)rand() / ((double)RAND_MAX + 1)
|
||||
* this->nsACount);
|
||||
article = zimFileHandler->getArticle(idx);
|
||||
} while (article.getLongUrl() == mainPageUrl);
|
||||
|
||||
return article.getLongUrl();
|
||||
}
|
||||
|
||||
/* Return the welcome page URL */
|
||||
string Reader::getMainPageUrl() const
|
||||
{
|
||||
string url = "";
|
||||
|
||||
if (this->zimFileHandler->getFileheader().hasMainPage()) {
|
||||
zim::Article article = zimFileHandler->getArticle(
|
||||
this->zimFileHandler->getFileheader().getMainPage());
|
||||
url = article.getLongUrl();
|
||||
|
||||
if (url.empty()) {
|
||||
url = getFirstPageUrl();
|
||||
}
|
||||
} else {
|
||||
url = getFirstPageUrl();
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
bool Reader::getFavicon(string& content, string& mimeType) const
|
||||
{
|
||||
unsigned int contentLength = 0;
|
||||
string title;
|
||||
|
||||
this->getContentByUrl("/-/favicon.png", content, title, contentLength, mimeType);
|
||||
|
||||
if (content.empty()) {
|
||||
this->getContentByUrl("/I/favicon.png", content, title, contentLength, mimeType);
|
||||
|
||||
if (content.empty()) {
|
||||
this->getContentByUrl("/I/favicon", content, title, contentLength, mimeType);
|
||||
|
||||
if (content.empty()) {
|
||||
this->getContentByUrl("/-/favicon", content, title, contentLength, mimeType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return content.empty() ? false : true;
|
||||
}
|
||||
|
||||
string Reader::getZimFilePath() const
|
||||
{
|
||||
return this->zimFilePath;
|
||||
}
|
||||
/* Return a metatag value */
|
||||
bool Reader::getMetatag(const string& name, string& value) const
|
||||
{
|
||||
unsigned int contentLength = 0;
|
||||
string contentType = "";
|
||||
string title;
|
||||
|
||||
return this->getContentByUrl("/M/" + name, value, title, contentLength, contentType);
|
||||
}
|
||||
|
||||
string Reader::getTitle() const
|
||||
{
|
||||
string value;
|
||||
this->getMetatag("Title", value);
|
||||
if (value.empty()) {
|
||||
value = getLastPathElement(zimFileHandler->getFilename());
|
||||
std::replace(value.begin(), value.end(), '_', ' ');
|
||||
size_t pos = value.find(".zim");
|
||||
value = value.substr(0, pos);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
string Reader::getName() const
|
||||
{
|
||||
string value;
|
||||
this->getMetatag("Name", value);
|
||||
return value;
|
||||
}
|
||||
|
||||
string Reader::getTags() const
|
||||
{
|
||||
string value;
|
||||
this->getMetatag("Tags", value);
|
||||
return value;
|
||||
}
|
||||
|
||||
string Reader::getDescription() const
|
||||
{
|
||||
string value;
|
||||
this->getMetatag("Description", value);
|
||||
|
||||
/* Mediawiki Collection tends to use the "Subtitle" name */
|
||||
if (value.empty()) {
|
||||
this->getMetatag("Subtitle", value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
string Reader::getLanguage() const
|
||||
{
|
||||
string value;
|
||||
this->getMetatag("Language", value);
|
||||
return value;
|
||||
}
|
||||
|
||||
string Reader::getDate() const
|
||||
{
|
||||
string value;
|
||||
this->getMetatag("Date", value);
|
||||
return value;
|
||||
}
|
||||
|
||||
string Reader::getCreator() const
|
||||
{
|
||||
string value;
|
||||
this->getMetatag("Creator", value);
|
||||
return value;
|
||||
}
|
||||
|
||||
string Reader::getPublisher() const
|
||||
{
|
||||
string value;
|
||||
this->getMetatag("Publisher", value);
|
||||
return value;
|
||||
}
|
||||
|
||||
string Reader::getOrigId() const
|
||||
{
|
||||
string value;
|
||||
this->getMetatag("startfileuid", value);
|
||||
if (value.empty()) {
|
||||
return "";
|
||||
}
|
||||
std::string id = value;
|
||||
std::string origID;
|
||||
std::string temp = "";
|
||||
unsigned int k = 0;
|
||||
char tempArray[16] = "";
|
||||
for (unsigned int i = 0; i < id.size(); i++) {
|
||||
if (id[i] == '\n') {
|
||||
tempArray[k] = atoi(temp.c_str());
|
||||
temp = "";
|
||||
k++;
|
||||
} else {
|
||||
temp += id[i];
|
||||
}
|
||||
}
|
||||
origID = hexUUID(tempArray);
|
||||
return origID;
|
||||
}
|
||||
|
||||
/* Return the first page URL */
|
||||
string Reader::getFirstPageUrl() const
|
||||
{
|
||||
zim::size_type firstPageOffset = zimFileHandler->getNamespaceBeginOffset('A');
|
||||
zim::Article article = zimFileHandler->getArticle(firstPageOffset);
|
||||
return article.getLongUrl();
|
||||
}
|
||||
|
||||
bool Reader::parseUrl(const string& url, char* ns, string& title) const
|
||||
{
|
||||
/* Offset to visit the url */
|
||||
unsigned int urlLength = url.size();
|
||||
unsigned int offset = 0;
|
||||
|
||||
/* Ignore the '/' */
|
||||
while ((offset < urlLength) && (url[offset] == '/')) {
|
||||
offset++;
|
||||
}
|
||||
|
||||
/* Get namespace */
|
||||
while ((offset < urlLength) && (url[offset] != '/')) {
|
||||
*ns = url[offset];
|
||||
offset++;
|
||||
}
|
||||
|
||||
/* Ignore the '/' */
|
||||
while ((offset < urlLength) && (url[offset] == '/')) {
|
||||
offset++;
|
||||
}
|
||||
|
||||
/* Get content title */
|
||||
unsigned int titleOffset = offset;
|
||||
while (offset < urlLength) {
|
||||
offset++;
|
||||
}
|
||||
|
||||
/* unescape title */
|
||||
title = url.substr(titleOffset, offset - titleOffset);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Return article by url */
|
||||
bool Reader::getArticleObjectByDecodedUrl(const string& url,
|
||||
zim::Article& article) const
|
||||
{
|
||||
if (this->zimFileHandler == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Parse the url */
|
||||
char ns = 0;
|
||||
string urlStr;
|
||||
this->parseUrl(url, &ns, urlStr);
|
||||
|
||||
/* Main page */
|
||||
if (urlStr.empty() && ns == 0) {
|
||||
this->parseUrl(this->getMainPageUrl(), &ns, urlStr);
|
||||
}
|
||||
|
||||
/* Extract the content from the zim file */
|
||||
article = zimFileHandler->getArticle(ns, urlStr);
|
||||
return article.good();
|
||||
}
|
||||
|
||||
/* Return the mimeType without the content */
|
||||
bool Reader::getMimeTypeByUrl(const string& url, string& mimeType) const
|
||||
{
|
||||
if (this->zimFileHandler == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
zim::Article article;
|
||||
if (this->getArticleObjectByDecodedUrl(url, article)) {
|
||||
try {
|
||||
mimeType = article.getMimeType();
|
||||
} catch (exception& e) {
|
||||
cerr << "Unable to get the mimetype for " << url << ":" << e.what()
|
||||
<< endl;
|
||||
mimeType = "application/octet-stream";
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
mimeType = "";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Get a content from a zim file */
|
||||
bool Reader::getContentByUrl(const string& url,
|
||||
string& content,
|
||||
string& title,
|
||||
unsigned int& contentLength,
|
||||
string& contentType) const
|
||||
{
|
||||
return this->getContentByEncodedUrl(url, content, title, contentLength, contentType);
|
||||
}
|
||||
|
||||
bool Reader::getContentByEncodedUrl(const string& url,
|
||||
string& content,
|
||||
string& title,
|
||||
unsigned int& contentLength,
|
||||
string& contentType,
|
||||
string& baseUrl) const
|
||||
{
|
||||
return this->getContentByDecodedUrl(
|
||||
kiwix::urlDecode(url), content, title, contentLength, contentType, baseUrl);
|
||||
}
|
||||
|
||||
bool Reader::getContentByEncodedUrl(const string& url,
|
||||
string& content,
|
||||
string& title,
|
||||
unsigned int& contentLength,
|
||||
string& contentType) const
|
||||
{
|
||||
std::string stubRedirectUrl;
|
||||
return this->getContentByEncodedUrl(kiwix::urlDecode(url),
|
||||
content,
|
||||
title,
|
||||
contentLength,
|
||||
contentType,
|
||||
stubRedirectUrl);
|
||||
}
|
||||
|
||||
bool Reader::getContentByDecodedUrl(const string& url,
|
||||
string& content,
|
||||
string& title,
|
||||
unsigned int& contentLength,
|
||||
string& contentType) const
|
||||
{
|
||||
std::string stubRedirectUrl;
|
||||
return this->getContentByDecodedUrl(kiwix::urlDecode(url),
|
||||
content,
|
||||
title,
|
||||
contentLength,
|
||||
contentType,
|
||||
stubRedirectUrl);
|
||||
}
|
||||
|
||||
bool Reader::getContentByDecodedUrl(const string& url,
|
||||
string& content,
|
||||
string& title,
|
||||
unsigned int& contentLength,
|
||||
string& contentType,
|
||||
string& baseUrl) const
|
||||
{
|
||||
content = "";
|
||||
contentType = "";
|
||||
contentLength = 0;
|
||||
|
||||
zim::Article article;
|
||||
if (!this->getArticleObjectByDecodedUrl(url, article)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* If redirect */
|
||||
unsigned int loopCounter = 0;
|
||||
while (article.isRedirect() && loopCounter++ < 42) {
|
||||
article = article.getRedirectArticle();
|
||||
}
|
||||
|
||||
if (loopCounter < 42) {
|
||||
/* Compute base url (might be different from the url if redirects */
|
||||
baseUrl
|
||||
= "/" + std::string(1, article.getNamespace()) + "/" + article.getUrl();
|
||||
|
||||
/* Get the content mime-type */
|
||||
try {
|
||||
contentType
|
||||
= string(article.getMimeType().data(), article.getMimeType().size());
|
||||
} catch (exception& e) {
|
||||
cerr << "Unable to get the mimetype for " << baseUrl << ":" << e.what()
|
||||
<< endl;
|
||||
contentType = "application/octet-stream";
|
||||
}
|
||||
|
||||
/* Get the data */
|
||||
content = string(article.getData().data(), article.getArticleSize());
|
||||
title = article.getTitle();
|
||||
}
|
||||
|
||||
/* Try to set a stub HTML header/footer if necesssary */
|
||||
if (contentType.find("text/html") != string::npos
|
||||
&& content.find("<body") == std::string::npos
|
||||
&& content.find("<BODY") == std::string::npos) {
|
||||
content = "<html><head><title>" + article.getTitle() +
|
||||
"</title><meta http-equiv=\"Content-Type\" content=\"text/html; "
|
||||
"charset=utf-8\" /></head><body>" +
|
||||
content + "</body></html>";
|
||||
}
|
||||
|
||||
/* Get the data length */
|
||||
contentLength = article.getArticleSize();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Check if an article exists */
|
||||
bool Reader::urlExists(const string& url) const
|
||||
{
|
||||
char ns = 0;
|
||||
string titleStr;
|
||||
this->parseUrl(url, &ns, titleStr);
|
||||
titleStr = "/" + titleStr;
|
||||
zim::File::const_iterator findItr = zimFileHandler->find(ns, titleStr);
|
||||
return findItr != zimFileHandler->end() && findItr->getUrl() == titleStr;
|
||||
}
|
||||
|
||||
/* Does the ZIM file has a fulltext index */
|
||||
bool Reader::hasFulltextIndex() const
|
||||
{
|
||||
return ( this->urlExists("/Z/fulltextIndex/xapian")
|
||||
&& !zimFileHandler->is_multiPart() );
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
this->suggestions.clear();
|
||||
this->suggestionsOffset = this->suggestions.begin();
|
||||
} else {
|
||||
if (this->suggestions.size() > suggestionsCount) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Return if no prefix */
|
||||
if (prefix.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (articleItr = zimFileHandler->findByTitle('A', prefix);
|
||||
articleItr != zimFileHandler->end()
|
||||
&& articleItr->getTitle().compare(0, prefix.size(), prefix) == 0
|
||||
&& this->suggestions.size() < suggestionsCount;
|
||||
++articleItr) {
|
||||
/* Extract the interesting part of article title & url */
|
||||
std::string normalizedArticleTitle
|
||||
= kiwix::normalize(articleItr->getTitle());
|
||||
std::string articleFinalUrl = "/A/" + articleItr->getUrl();
|
||||
if (articleItr->isRedirect()) {
|
||||
zim::Article article = *articleItr;
|
||||
unsigned int loopCounter = 0;
|
||||
while (article.isRedirect() && loopCounter++ < 42) {
|
||||
article = article.getRedirectArticle();
|
||||
}
|
||||
articleFinalUrl = "/A/" + article.getUrl();
|
||||
}
|
||||
|
||||
/* Go through all already found suggestions and skip if this
|
||||
article is already in the suggestions list (with an other
|
||||
title) */
|
||||
bool insert = true;
|
||||
std::vector<std::vector<std::string>>::iterator suggestionItr;
|
||||
for (suggestionItr = this->suggestions.begin();
|
||||
suggestionItr != this->suggestions.end();
|
||||
suggestionItr++) {
|
||||
int result = normalizedArticleTitle.compare((*suggestionItr)[2]);
|
||||
if (result == 0 && articleFinalUrl.compare((*suggestionItr)[1]) == 0) {
|
||||
insert = false;
|
||||
break;
|
||||
} else if (result < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Insert if possible */
|
||||
if (insert) {
|
||||
std::vector<std::string> suggestion;
|
||||
suggestion.push_back(articleItr->getTitle());
|
||||
suggestion.push_back(articleFinalUrl);
|
||||
suggestion.push_back(normalizedArticleTitle);
|
||||
this->suggestions.insert(suggestionItr, suggestion);
|
||||
}
|
||||
|
||||
/* Suggestions where found */
|
||||
retVal = true;
|
||||
}
|
||||
|
||||
/* Set the cursor to the begining */
|
||||
this->suggestionsOffset = this->suggestions.begin();
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
std::vector<std::string> Reader::getTitleVariants(
|
||||
const std::string& title) const
|
||||
{
|
||||
std::vector<std::string> variants;
|
||||
variants.push_back(title);
|
||||
variants.push_back(kiwix::ucFirst(title));
|
||||
variants.push_back(kiwix::lcFirst(title));
|
||||
variants.push_back(kiwix::toTitle(title));
|
||||
return variants;
|
||||
}
|
||||
|
||||
/* Try also a few variations of the prefix to have better results */
|
||||
bool Reader::searchSuggestionsSmart(const string& prefix,
|
||||
unsigned int suggestionsCount)
|
||||
{
|
||||
std::vector<std::string> variants = this->getTitleVariants(prefix);
|
||||
bool retVal;
|
||||
|
||||
this->suggestions.clear();
|
||||
this->suggestionsOffset = this->suggestions.begin();
|
||||
/* Try to search in the title using fulltext search database */
|
||||
const zim::Search* suggestionSearch
|
||||
= this->getZimFileHandler()->suggestions(prefix, 0, suggestionsCount);
|
||||
|
||||
if (suggestionSearch->get_matches_estimated()) {
|
||||
for (auto current = suggestionSearch->begin();
|
||||
current != suggestionSearch->end();
|
||||
current++) {
|
||||
std::vector<std::string> suggestion;
|
||||
suggestion.push_back(current->getTitle());
|
||||
suggestion.push_back("/A/" + current->getUrl());
|
||||
suggestion.push_back(kiwix::normalize(current->getTitle()));
|
||||
this->suggestions.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;
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/* Get next suggestion */
|
||||
bool Reader::getNextSuggestion(string& title)
|
||||
{
|
||||
if (this->suggestionsOffset != this->suggestions.end()) {
|
||||
/* title */
|
||||
title = (*(this->suggestionsOffset))[0];
|
||||
|
||||
/* increment the cursor for the next call */
|
||||
this->suggestionsOffset++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Reader::getNextSuggestion(string& title, string& url)
|
||||
{
|
||||
if (this->suggestionsOffset != this->suggestions.end()) {
|
||||
/* title */
|
||||
title = (*(this->suggestionsOffset))[0];
|
||||
url = (*(this->suggestionsOffset))[1];
|
||||
|
||||
/* increment the cursor for the next call */
|
||||
this->suggestionsOffset++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check if the file has as checksum */
|
||||
bool Reader::canCheckIntegrity() const
|
||||
{
|
||||
return this->zimFileHandler->getChecksum() != "";
|
||||
}
|
||||
|
||||
/* Return true if corrupted, false otherwise */
|
||||
bool Reader::isCorrupted() const
|
||||
{
|
||||
try {
|
||||
if (this->zimFileHandler->verify() == true) {
|
||||
return false;
|
||||
}
|
||||
} catch (exception& e) {
|
||||
cerr << e.what() << endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Return the file size, works also for splitted files */
|
||||
unsigned int Reader::getFileSize() const
|
||||
{
|
||||
zim::File* file = this->getZimFileHandler();
|
||||
zim::offset_type size = 0;
|
||||
|
||||
if (file != NULL) {
|
||||
size = file->getFilesize();
|
||||
}
|
||||
|
||||
return (size / 1024);
|
||||
}
|
||||
}
|
||||
@@ -1,275 +0,0 @@
|
||||
/*
|
||||
* Copyright 2011 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "search_renderer.h"
|
||||
#include "library.h"
|
||||
#include "name_mapper.h"
|
||||
|
||||
#include "tools/archiveTools.h"
|
||||
|
||||
#include <zim/search.h>
|
||||
|
||||
#include <mustache.hpp>
|
||||
#include "libkiwix-resources.h"
|
||||
#include "tools/stringTools.h"
|
||||
|
||||
#include "server/i18n_utils.h"
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
ParameterizedMessage searchResultsPageTitleMsg(const std::string& searchPattern)
|
||||
{
|
||||
return ParameterizedMessage("search-results-page-title",
|
||||
{{"SEARCH_PATTERN", searchPattern}}
|
||||
);
|
||||
}
|
||||
|
||||
ParameterizedMessage searchResultsPageHeaderMsg(const std::string& searchPattern,
|
||||
const kainjow::mustache::data& r)
|
||||
{
|
||||
if ( r.get("count")->string_value() == "0" ) {
|
||||
return ParameterizedMessage("empty-search-results-page-header",
|
||||
{{"SEARCH_PATTERN", searchPattern}}
|
||||
);
|
||||
} else {
|
||||
return ParameterizedMessage("search-results-page-header",
|
||||
{
|
||||
{"SEARCH_PATTERN", searchPattern},
|
||||
{"START", r.get("startLabel")->string_value()},
|
||||
{"END", r.get("end") ->string_value()},
|
||||
{"COUNT", r.get("count")->string_value()},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
/* Constructor */
|
||||
SearchRenderer::SearchRenderer(zim::SearchResultSet srs,
|
||||
unsigned int start, unsigned int estimatedResultCount)
|
||||
: m_srs(srs),
|
||||
protocolPrefix("zim://"),
|
||||
searchProtocolPrefix("search://"),
|
||||
estimatedResultCount(estimatedResultCount),
|
||||
resultStart(start)
|
||||
{}
|
||||
|
||||
/* Destructor */
|
||||
SearchRenderer::~SearchRenderer() = default;
|
||||
|
||||
void SearchRenderer::setSearchPattern(const std::string& pattern)
|
||||
{
|
||||
searchPattern = pattern;
|
||||
}
|
||||
|
||||
void SearchRenderer::setSearchBookQuery(const std::string& bookQuery)
|
||||
{
|
||||
searchBookQuery = bookQuery;
|
||||
}
|
||||
|
||||
void SearchRenderer::setProtocolPrefix(const std::string& prefix)
|
||||
{
|
||||
this->protocolPrefix = prefix;
|
||||
}
|
||||
|
||||
void SearchRenderer::setSearchProtocolPrefix(const std::string& prefix)
|
||||
{
|
||||
this->searchProtocolPrefix = prefix;
|
||||
}
|
||||
|
||||
std::string extractValueFromQuery(const std::string& query, const std::string& key) {
|
||||
const std::string p = key + "=";
|
||||
const size_t i = query.find(p);
|
||||
if (i == std::string::npos) {
|
||||
return "";
|
||||
}
|
||||
std::string r = query.substr(i + p.size());
|
||||
return r.substr(0, r.find("&"));
|
||||
}
|
||||
|
||||
kainjow::mustache::data buildQueryData
|
||||
(
|
||||
const std::string& searchProtocolPrefix,
|
||||
const std::string& pattern,
|
||||
const std::string& bookQuery
|
||||
) {
|
||||
kainjow::mustache::data query;
|
||||
query.set("pattern", kiwix::encodeDiples(pattern));
|
||||
std::ostringstream ss;
|
||||
ss << searchProtocolPrefix << "?pattern=" << urlEncode(pattern);
|
||||
ss << "&" << bookQuery;
|
||||
query.set("unpaginatedQuery", ss.str());
|
||||
auto lang = extractValueFromQuery(bookQuery, "books.filter.lang");
|
||||
if(!lang.empty()) {
|
||||
query.set("lang", lang);
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
kainjow::mustache::data buildPagination(
|
||||
unsigned int pageLength,
|
||||
unsigned int resultsCount,
|
||||
unsigned int resultsStart
|
||||
)
|
||||
{
|
||||
assert(pageLength!=0);
|
||||
kainjow::mustache::data pagination;
|
||||
kainjow::mustache::data pages{kainjow::mustache::data::type::list};
|
||||
|
||||
if (resultsCount == 0) {
|
||||
// Easy case
|
||||
pagination.set("itemsPerPage", to_string(pageLength));
|
||||
pagination.set("hasPages", false);
|
||||
pagination.set("pages", pages);
|
||||
return pagination;
|
||||
}
|
||||
|
||||
// First we want to display pages starting at a multiple of `pageLength`
|
||||
// so, let's calculate the start index of the current page.
|
||||
auto currentPage = resultsStart/pageLength;
|
||||
auto lastPage = ((resultsCount-1)/pageLength);
|
||||
auto lastPageStart = lastPage*pageLength;
|
||||
auto nbPages = lastPage + 1;
|
||||
|
||||
auto firstPageGenerated = currentPage > 4 ? currentPage-4 : 0;
|
||||
auto lastPageGenerated = std::min(currentPage+4, lastPage);
|
||||
|
||||
if (nbPages != 1) {
|
||||
if (firstPageGenerated!=0) {
|
||||
kainjow::mustache::data page;
|
||||
page.set("label", "◀");
|
||||
page.set("start", to_string(0));
|
||||
page.set("current", false);
|
||||
pages.push_back(page);
|
||||
}
|
||||
|
||||
for (auto i=firstPageGenerated; i<=lastPageGenerated; i++) {
|
||||
kainjow::mustache::data page;
|
||||
page.set("label", to_string(i+1));
|
||||
page.set("start", to_string(i*pageLength));
|
||||
page.set("current", bool(i == currentPage));
|
||||
pages.push_back(page);
|
||||
}
|
||||
|
||||
if (lastPageGenerated!=lastPage) {
|
||||
kainjow::mustache::data page;
|
||||
page.set("label", "▶");
|
||||
page.set("start", to_string(lastPageStart));
|
||||
page.set("current", false);
|
||||
pages.push_back(page);
|
||||
}
|
||||
}
|
||||
|
||||
pagination.set("itemsPerPage", to_string(pageLength));
|
||||
pagination.set("hasPages", firstPageGenerated < lastPageGenerated);
|
||||
pagination.set("pages", pages);
|
||||
return pagination;
|
||||
}
|
||||
|
||||
std::string SearchRenderer::renderTemplate(const std::string& tmpl_str, const NameMapper& nameMapper, const Library* library)
|
||||
{
|
||||
const std::string absPathPrefix = protocolPrefix;
|
||||
// Build the results list
|
||||
kainjow::mustache::data items{kainjow::mustache::data::type::list};
|
||||
for (auto it = m_srs.begin(); it != m_srs.end(); it++) {
|
||||
kainjow::mustache::data result;
|
||||
const std::string zim_id(it.getZimId());
|
||||
const auto path = nameMapper.getNameForId(zim_id) + "/" + it.getPath();
|
||||
result.set("title", it.getTitle());
|
||||
result.set("absolutePath", absPathPrefix + urlEncode(path));
|
||||
result.set("snippet", it.getSnippet());
|
||||
if (library) {
|
||||
const std::string bookTitle = library->getBookById(zim_id).getTitle();
|
||||
const ParameterizedMessage bookInfoMsg("search-result-book-info",
|
||||
{{"BOOK_TITLE", bookTitle}}
|
||||
);
|
||||
result.set("bookInfo", bookInfoMsg.getText(userlang)); // for HTML
|
||||
result.set("bookTitle", bookTitle); // for XML
|
||||
}
|
||||
if (it.getWordCount() >= 0) {
|
||||
const auto wordCountStr = kiwix::beautifyInteger(it.getWordCount());
|
||||
const ParameterizedMessage wordCountMsg("word-count",
|
||||
{{"COUNT", wordCountStr}}
|
||||
);
|
||||
result.set("wordCountInfo", wordCountMsg.getText(userlang)); // for HTML
|
||||
result.set("wordCount", wordCountStr); // for XML
|
||||
}
|
||||
|
||||
items.push_back(result);
|
||||
}
|
||||
kainjow::mustache::data results;
|
||||
results.set("items", items);
|
||||
results.set("count", kiwix::beautifyInteger(estimatedResultCount));
|
||||
results.set("start", kiwix::beautifyInteger(resultStart));
|
||||
results.set("startLabel", kiwix::beautifyInteger(resultStart+1));
|
||||
results.set("end", kiwix::beautifyInteger(std::min(resultStart+pageLength, estimatedResultCount)));
|
||||
|
||||
// pagination
|
||||
auto pagination = buildPagination(
|
||||
pageLength,
|
||||
estimatedResultCount,
|
||||
resultStart
|
||||
);
|
||||
|
||||
kainjow::mustache::data query = buildQueryData(
|
||||
searchProtocolPrefix,
|
||||
searchPattern,
|
||||
searchBookQuery
|
||||
);
|
||||
|
||||
const auto pageHeaderMsg = searchResultsPageHeaderMsg(searchPattern, results);
|
||||
const kainjow::mustache::object allData{
|
||||
{"PAGE_TITLE", searchResultsPageTitleMsg(searchPattern).getText(userlang)},
|
||||
{"PAGE_HEADER", pageHeaderMsg.getText(userlang)},
|
||||
{"searchProtocolPrefix", searchProtocolPrefix},
|
||||
{"results", results},
|
||||
{"pagination", pagination},
|
||||
{"query", query},
|
||||
};
|
||||
|
||||
kainjow::mustache::mustache tmpl(tmpl_str);
|
||||
|
||||
std::stringstream ss;
|
||||
tmpl.render(allData, [&ss](const std::string& str) { ss << str; });
|
||||
if (!tmpl.is_valid()) {
|
||||
throw std::runtime_error("Error while rendering search results: " + tmpl.error_message());
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string SearchRenderer::getHtml(const NameMapper& mapper, const Library* library)
|
||||
{
|
||||
return renderTemplate(RESOURCE::templates::search_result_html, mapper, library);
|
||||
}
|
||||
|
||||
std::string SearchRenderer::getXml(const NameMapper& mapper, const Library* library)
|
||||
{
|
||||
return renderTemplate(RESOURCE::templates::search_result_xml, mapper, library);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user