mirror of
https://github.com/kiwix/libkiwix.git
synced 2025-12-27 16:38:01 -05:00
Compare commits
516 Commits
10.0.1
...
robust_uri
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd9785fe85 | ||
|
|
b7a019469c | ||
|
|
76dfc03751 | ||
|
|
ca079a72cc | ||
|
|
471c5b89f4 | ||
|
|
3bf8211b70 | ||
|
|
ec81d5904d | ||
|
|
82dcba542a | ||
|
|
63e0d5c7c2 | ||
|
|
772243e832 | ||
|
|
bad13d76b4 | ||
|
|
0bde4d9412 | ||
|
|
239b108fa7 | ||
|
|
c5ccbd37e2 | ||
|
|
822fb3748a | ||
|
|
aa2e443eb8 | ||
|
|
82d477009d | ||
|
|
e49081da80 | ||
|
|
07c7d3931d | ||
|
|
cf59a93cf1 | ||
|
|
e35e7585e0 | ||
|
|
fcb97c3c06 | ||
|
|
0edee4d066 | ||
|
|
b9937e6859 | ||
|
|
59012c50b4 | ||
|
|
7a98878273 | ||
|
|
8eb527389e | ||
|
|
78b2c1a273 | ||
|
|
497c0700b5 | ||
|
|
bac12010aa | ||
|
|
dad33a850c | ||
|
|
0968fc98ee | ||
|
|
ff44d88f21 | ||
|
|
1e7baee9d7 | ||
|
|
d9342acf5b | ||
|
|
b3f1ab6579 | ||
|
|
f5c9b2404a | ||
|
|
8b1fe21e4e | ||
|
|
815c59ff6d | ||
|
|
90318dfb6b | ||
|
|
f3d2f474a7 | ||
|
|
12140098e6 | ||
|
|
c7d8081e9a | ||
|
|
a10067e6b6 | ||
|
|
28e9fb48b6 | ||
|
|
634f3fcf14 | ||
|
|
88597e1834 | ||
|
|
69b3e1f8a7 | ||
|
|
669d8898ac | ||
|
|
14f0f79061 | ||
|
|
600ff07986 | ||
|
|
1d74b5e311 | ||
|
|
c0fe6f4aee | ||
|
|
aa7053bbe8 | ||
|
|
99f24eb598 | ||
|
|
6790a144a1 | ||
|
|
cd3d2110d9 | ||
|
|
b404241d0b | ||
|
|
2d42d6dc60 | ||
|
|
e65c9c41d8 | ||
|
|
0ae31bd181 | ||
|
|
0d8971ef88 | ||
|
|
2812b5ca5c | ||
|
|
4dc8973cdc | ||
|
|
160c95e317 | ||
|
|
956289d9f8 | ||
|
|
3568ccd511 | ||
|
|
d66cc6286c | ||
|
|
7743e73ede | ||
|
|
4966f4155d | ||
|
|
c727de6591 | ||
|
|
0f0ae1cfed | ||
|
|
da78aae62b | ||
|
|
abcd4ade99 | ||
|
|
7a9780eb90 | ||
|
|
51bd881211 | ||
|
|
f36f1661d5 | ||
|
|
18f4a58237 | ||
|
|
6285599b7c | ||
|
|
764f68f7d8 | ||
|
|
777c5e1f7a | ||
|
|
8031ffa447 | ||
|
|
0c8ceac117 | ||
|
|
ec31882e94 | ||
|
|
8cec014691 | ||
|
|
bf9aeffbfa | ||
|
|
7765769e6f | ||
|
|
7d69ece27d | ||
|
|
c0d027e8a4 | ||
|
|
c87add1419 | ||
|
|
a52138e5ba | ||
|
|
d1b85192c0 | ||
|
|
cb02dbd92a | ||
|
|
9409e8bd91 | ||
|
|
cd62b5dd91 | ||
|
|
414d7ae4fe | ||
|
|
9d2cc35447 | ||
|
|
7167ca1e6a | ||
|
|
8cc1c47133 | ||
|
|
e5b94fa1bb | ||
|
|
b0d719431d | ||
|
|
0e20f50443 | ||
|
|
18a18c17a9 | ||
|
|
602c20f160 | ||
|
|
415ec41099 | ||
|
|
b9f60ecfe9 | ||
|
|
12a638750e | ||
|
|
b62486c2f9 | ||
|
|
6bc7e0178d | ||
|
|
ce8b2bf9d9 | ||
|
|
9fd1423100 | ||
|
|
6b8d6232f0 | ||
|
|
c91df1cb26 | ||
|
|
b249edee60 | ||
|
|
a31ccb6588 | ||
|
|
43c8da9b04 | ||
|
|
190156e095 | ||
|
|
5471819021 | ||
|
|
7feef320d9 | ||
|
|
73191fb8f8 | ||
|
|
a844bc4000 | ||
|
|
f13ca55ef6 | ||
|
|
dc194683bb | ||
|
|
0841472004 | ||
|
|
ebb713cb85 | ||
|
|
cd6cbe3655 | ||
|
|
582c8d868a | ||
|
|
f6ae75e41d | ||
|
|
ffbda34b75 | ||
|
|
f61fc07121 | ||
|
|
de7fa771fc | ||
|
|
24c1ca5a4a | ||
|
|
15f5abad3c | ||
|
|
0a866fa914 | ||
|
|
ff192cba49 | ||
|
|
0dd638f261 | ||
|
|
229c0ceaf9 | ||
|
|
70f7be4202 | ||
|
|
60148717e1 | ||
|
|
266e29dff2 | ||
|
|
11051b4eed | ||
|
|
86eacea74e | ||
|
|
3a75facfdc | ||
|
|
0a0f52f1e2 | ||
|
|
0994a8f1b0 | ||
|
|
fa67b45f50 | ||
|
|
defa38719d | ||
|
|
cac2d212c6 | ||
|
|
4e06bb6a08 | ||
|
|
796e729f52 | ||
|
|
ae01790375 | ||
|
|
da23e4eca4 | ||
|
|
2be9ac342f | ||
|
|
369406fb5d | ||
|
|
b81cb3a8e9 | ||
|
|
6cc677b8ad | ||
|
|
a674561110 | ||
|
|
685e7f8ad4 | ||
|
|
0ce36e6246 | ||
|
|
eb0a45b13e | ||
|
|
c988511561 | ||
|
|
c73e6f9a81 | ||
|
|
0cf4850a9b | ||
|
|
40c496d401 | ||
|
|
9a193735fb | ||
|
|
2083c390b5 | ||
|
|
29efb88d48 | ||
|
|
948435794f | ||
|
|
7ed01e7678 | ||
|
|
eadc0ac72b | ||
|
|
77d9777208 | ||
|
|
4a55b136f6 | ||
|
|
a9446714ea | ||
|
|
17ff2a094d | ||
|
|
0c4d9e8730 | ||
|
|
7be7a8ed5f | ||
|
|
f41e71b2d7 | ||
|
|
58e45711ff | ||
|
|
5b545d81bd | ||
|
|
7c6c315ead | ||
|
|
228e31cddd | ||
|
|
4105be9bd2 | ||
|
|
e5f97d95b1 | ||
|
|
4db443eca6 | ||
|
|
dea674ef38 | ||
|
|
4b6c6452c0 | ||
|
|
5130bf9774 | ||
|
|
ee3514d2d6 | ||
|
|
e1847cb058 | ||
|
|
dd2b82a6be | ||
|
|
1062bd73a3 | ||
|
|
cd56277123 | ||
|
|
5e8b977bec | ||
|
|
9f545718c2 | ||
|
|
e323dcf6c9 | ||
|
|
3b98987cb3 | ||
|
|
fd36d11ccf | ||
|
|
dc56f82c29 | ||
|
|
1b1c1e352e | ||
|
|
a4b18893aa | ||
|
|
d737db666a | ||
|
|
cff143b4ec | ||
|
|
8e6d893f7f | ||
|
|
111aab0c23 | ||
|
|
dd90ca1018 | ||
|
|
3facd594f6 | ||
|
|
4cd52b0809 | ||
|
|
baf22c2516 | ||
|
|
f8a530100f | ||
|
|
a0db199388 | ||
|
|
f0f473b829 | ||
|
|
1e247d75bb | ||
|
|
4f3ec817db | ||
|
|
98bcf8acd6 | ||
|
|
b69bf4d062 | ||
|
|
6891ce3b57 | ||
|
|
16197afc95 | ||
|
|
abccd9d706 | ||
|
|
d0adb4e722 | ||
|
|
88c25b3a6c | ||
|
|
5aa74c62d6 | ||
|
|
2b6da38c46 | ||
|
|
dfc6cad9c2 | ||
|
|
28f8dbcf20 | ||
|
|
81865c0f0e | ||
|
|
538a46f262 | ||
|
|
e1d1d202bd | ||
|
|
71e2df7406 | ||
|
|
69931fb347 | ||
|
|
12e0fb6934 | ||
|
|
43ab6dfb6a | ||
|
|
93f2686a94 | ||
|
|
19a9c84e13 | ||
|
|
f034018b5c | ||
|
|
596b223a9d | ||
|
|
0c549af307 | ||
|
|
37b39430d1 | ||
|
|
947744caea | ||
|
|
b9e03d2772 | ||
|
|
e9b7eeb3c9 | ||
|
|
15cb9025bb | ||
|
|
1139f2cb4c | ||
|
|
0086049d4f | ||
|
|
e3e4bfa533 | ||
|
|
6938253e59 | ||
|
|
c5d3ffe0d7 | ||
|
|
3938d791e7 | ||
|
|
1572956da0 | ||
|
|
84b1321545 | ||
|
|
0f3bca442a | ||
|
|
83a9e54399 | ||
|
|
baa97cadf0 | ||
|
|
06d7a2320f | ||
|
|
f279769435 | ||
|
|
7d4867194a | ||
|
|
0340a49780 | ||
|
|
ed6aa5a89a | ||
|
|
75796ed6a5 | ||
|
|
ddd639eaa1 | ||
|
|
1c98b00128 | ||
|
|
600acb76c7 | ||
|
|
3bbbd1b15d | ||
|
|
ae47e5ee4e | ||
|
|
b442e2371e | ||
|
|
69c5c88c30 | ||
|
|
70382d15e2 | ||
|
|
62306373be | ||
|
|
01c384bb64 | ||
|
|
56167dc23e | ||
|
|
cc8ad9ebf2 | ||
|
|
bfcf317f09 | ||
|
|
ee01859984 | ||
|
|
9ec8593f8c | ||
|
|
7cb98f7f4e | ||
|
|
8100977cda | ||
|
|
a0cf91157a | ||
|
|
cadd2a5cbb | ||
|
|
e51a5b9ebc | ||
|
|
5d6b0ea96a | ||
|
|
e5df5e936f | ||
|
|
c4f706863c | ||
|
|
fbc7656b3f | ||
|
|
d196496802 | ||
|
|
3704d8ab87 | ||
|
|
a7651d0e9b | ||
|
|
3bca43344f | ||
|
|
b857293cfd | ||
|
|
b483a8e4e4 | ||
|
|
e2ab7fd62e | ||
|
|
f45962c697 | ||
|
|
3b3d7ad9c4 | ||
|
|
1514661c26 | ||
|
|
e5ea210d2c | ||
|
|
2b38d2cf1b | ||
|
|
0081b4d8e7 | ||
|
|
b74910b2af | ||
|
|
cf30233358 | ||
|
|
f0065fdd6f | ||
|
|
c72132054d | ||
|
|
077ceac5a5 | ||
|
|
39d0a56be8 | ||
|
|
76d5fafb72 | ||
|
|
4438106c2f | ||
|
|
76ebfd7ea4 | ||
|
|
22996e4a6b | ||
|
|
98c54b2279 | ||
|
|
854623618c | ||
|
|
fd0edbba80 | ||
|
|
f5af0633ec | ||
|
|
740581c55c | ||
|
|
582e3ec46d | ||
|
|
28fb76bbc2 | ||
|
|
7c688a4acc | ||
|
|
d4da05e591 | ||
|
|
66b2449800 | ||
|
|
aad95e3413 | ||
|
|
f0dd34b6db | ||
|
|
bbdde93f49 | ||
|
|
cb62da65c3 | ||
|
|
288b4ae7df | ||
|
|
52c12b0c2f | ||
|
|
4695f47dd2 | ||
|
|
f42f6a60df | ||
|
|
717c39f2ef | ||
|
|
aa1f73472d | ||
|
|
090c2fd31a | ||
|
|
ff2c7b1fb2 | ||
|
|
963362e1ea | ||
|
|
1a8d874a2c | ||
|
|
8e7658bb10 | ||
|
|
8f2f93371b | ||
|
|
eeca88573b | ||
|
|
4521249452 | ||
|
|
21e183c2e4 | ||
|
|
d56ccbd019 | ||
|
|
825cf1c948 | ||
|
|
57c31a43a4 | ||
|
|
84c68d4d7b | ||
|
|
f2cf42427a | ||
|
|
612ecc975d | ||
|
|
ae56d399b7 | ||
|
|
eaa8c3c91c | ||
|
|
26c06d8c2a | ||
|
|
eee6803328 | ||
|
|
d19ae1b054 | ||
|
|
abe2fa0179 | ||
|
|
6e93bad565 | ||
|
|
5fb919e73e | ||
|
|
2771a95d40 | ||
|
|
8dbf015689 | ||
|
|
6cdc47eb62 | ||
|
|
cbd37073e8 | ||
|
|
d131b732d8 | ||
|
|
17c1b3b82f | ||
|
|
744dd87fb0 | ||
|
|
d469e2aed8 | ||
|
|
73d2d47ca7 | ||
|
|
55149407d2 | ||
|
|
2eff5b55a6 | ||
|
|
26eccb5a5f | ||
|
|
1b81ccc5e5 | ||
|
|
091786c7d8 | ||
|
|
c0b9e2a466 | ||
|
|
03ab2f67dd | ||
|
|
157f01e951 | ||
|
|
42fd6e8926 | ||
|
|
707df3d10b | ||
|
|
c016dfd2ce | ||
|
|
150851b33d | ||
|
|
3b9f28b2b5 | ||
|
|
fc85215ea0 | ||
|
|
acdc1dfb27 | ||
|
|
f90cc39a52 | ||
|
|
fba0f09f4f | ||
|
|
0d294c50a5 | ||
|
|
dc42f831c0 | ||
|
|
1757f7f168 | ||
|
|
c43c637bea | ||
|
|
927c12574a | ||
|
|
9987fbd488 | ||
|
|
a0d9a824e1 | ||
|
|
5052d4018c | ||
|
|
11be821c46 | ||
|
|
527a606281 | ||
|
|
3da81a3d0f | ||
|
|
ed7717c1e7 | ||
|
|
f73be3cde7 | ||
|
|
c2bfeb4030 | ||
|
|
901664b097 | ||
|
|
6f3db20078 | ||
|
|
fbd23a8329 | ||
|
|
d2c864b010 | ||
|
|
779382642b | ||
|
|
ca7e0fb4a0 | ||
|
|
52d4f73e89 | ||
|
|
1ace16229d | ||
|
|
cb5ae01fd8 | ||
|
|
b2526c7a98 | ||
|
|
387f977d6c | ||
|
|
202ec81d8b | ||
|
|
577b6e29f9 | ||
|
|
e4a0a029ff | ||
|
|
507e111f34 | ||
|
|
d029c2b8d5 | ||
|
|
c574735f51 | ||
|
|
a18dd82d82 | ||
|
|
e22e073d43 | ||
|
|
6dcf4ee034 | ||
|
|
61ccbc65fb | ||
|
|
85a9d35488 | ||
|
|
a17258fcc9 | ||
|
|
ae1bf39023 | ||
|
|
dbcbdff275 | ||
|
|
c1823b8ee4 | ||
|
|
3f41ce8337 | ||
|
|
2a20e87341 | ||
|
|
2028bf3a98 | ||
|
|
545d409150 | ||
|
|
89dc9afc28 | ||
|
|
647118dd5e | ||
|
|
d8a60db739 | ||
|
|
f4059f3faf | ||
|
|
800cc5b68a | ||
|
|
b1f03385e4 | ||
|
|
feb30d08aa | ||
|
|
95d4dd63ac | ||
|
|
311f783ea9 | ||
|
|
f2a1c0f106 | ||
|
|
2cc4befb12 | ||
|
|
3641dbf14d | ||
|
|
1962262f94 | ||
|
|
7407f30790 | ||
|
|
d740ffe465 | ||
|
|
e7293346be | ||
|
|
b1643e422e | ||
|
|
574c1ad690 | ||
|
|
59364a737a | ||
|
|
49f24d18df | ||
|
|
ec2e10b40e | ||
|
|
2da8ea1650 | ||
|
|
0eb8f09f79 | ||
|
|
0ecbdbcf63 | ||
|
|
9bc09a815c | ||
|
|
48d377ca44 | ||
|
|
d5ae92e4e2 | ||
|
|
1a5e2eda0f | ||
|
|
89785a259a | ||
|
|
668063205c | ||
|
|
df98c58d07 | ||
|
|
ff8da65c68 | ||
|
|
ae60ba806b | ||
|
|
8cfcf2ea86 | ||
|
|
26c16bb1b2 | ||
|
|
ca965d448f | ||
|
|
6d16d7386d | ||
|
|
40e9a19c48 | ||
|
|
d487c78ea4 | ||
|
|
96cbd2bf26 | ||
|
|
941c3b5df3 | ||
|
|
b9e40def88 | ||
|
|
116ecd1c78 | ||
|
|
8f2faf37dc | ||
|
|
ddc4c3ec2c | ||
|
|
511261cc81 | ||
|
|
aaf232bee4 | ||
|
|
a3460f6f48 | ||
|
|
e4a4b2f961 | ||
|
|
389d29c92e | ||
|
|
c64fce52e7 | ||
|
|
a5baafd09f | ||
|
|
ed46541b6f | ||
|
|
e93ccd18d4 | ||
|
|
f893777dc0 | ||
|
|
04d682486a | ||
|
|
8136138492 | ||
|
|
e48b550b68 | ||
|
|
6523d9f563 | ||
|
|
7cb4c1361f | ||
|
|
a51f8d66a7 | ||
|
|
833bbc89ba | ||
|
|
4bd02f07eb | ||
|
|
9488842416 | ||
|
|
34b50ba30e | ||
|
|
cfab560d74 | ||
|
|
422f4c7dd7 | ||
|
|
cc3545ac3b | ||
|
|
609bc24cbe | ||
|
|
d9124ed40b | ||
|
|
921671eb4d | ||
|
|
ec18eb40ea | ||
|
|
a11abcf480 | ||
|
|
ae2d7d20dc | ||
|
|
42ee14c8f5 | ||
|
|
afb556bf64 | ||
|
|
5c38300504 | ||
|
|
cb2226c11f | ||
|
|
4cce4dce0b | ||
|
|
34d069e61f | ||
|
|
7a6562395a | ||
|
|
92f9ee9280 | ||
|
|
ae2d9b234f | ||
|
|
0ba452aece | ||
|
|
5f4256b900 | ||
|
|
a34dc725f9 | ||
|
|
892db07a2d | ||
|
|
58be502f3f | ||
|
|
62ba2f4861 | ||
|
|
c782cc718a | ||
|
|
9a6aef4dba | ||
|
|
943cbbf6ce | ||
|
|
ec94d9bfd9 | ||
|
|
f2088d7fe0 | ||
|
|
19dd068e5a | ||
|
|
d56e56293b | ||
|
|
dc4f9a4939 | ||
|
|
261adf0ef9 |
27
.github/move.yml
vendored
27
.github/move.yml
vendored
@@ -1,27 +0,0 @@
|
||||
# Configuration for Move Issues - https://github.com/dessant/move-issues
|
||||
|
||||
# Delete the command comment when it contains no other content
|
||||
deleteCommand: true
|
||||
|
||||
# Close the source issue after moving
|
||||
closeSourceIssue: true
|
||||
|
||||
# Lock the source issue after moving
|
||||
lockSourceIssue: false
|
||||
|
||||
# Mention issue and comment authors
|
||||
mentionAuthors: true
|
||||
|
||||
# Preserve mentions in the issue content
|
||||
keepContentMentions: true
|
||||
|
||||
# Move labels that also exist on the target repository
|
||||
moveLabels: true
|
||||
|
||||
# Set custom aliases for targets
|
||||
# aliases:
|
||||
# r: repo
|
||||
# or: owner/repo
|
||||
|
||||
# Repository to extend settings from
|
||||
# _extends: repo
|
||||
45
.github/workflows/ci.yml
vendored
45
.github/workflows/ci.yml
vendored
@@ -1,21 +1,25 @@
|
||||
name: CI
|
||||
|
||||
on: [push]
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
Macos:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v1
|
||||
- name: Setup python 3.10
|
||||
uses: actions/setup-python@v2
|
||||
uses: actions/checkout@v2
|
||||
- name: Setup python 3.9
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: '3.10'
|
||||
python-version: '3.9'
|
||||
- name: Install packages
|
||||
run: |
|
||||
brew update
|
||||
brew install gcovr pkg-config ninja
|
||||
brew install gcovr pkg-config ninja || brew link --overwrite python
|
||||
- name: Install python modules
|
||||
run: pip3 install meson==0.49.2 pytest
|
||||
- name: Install deps
|
||||
@@ -47,7 +51,6 @@ jobs:
|
||||
name:
|
||||
- native_static
|
||||
- native_dyn
|
||||
- native_dyn_bionic
|
||||
- android_arm
|
||||
- android_arm64
|
||||
- win32_static
|
||||
@@ -55,31 +58,27 @@ jobs:
|
||||
include:
|
||||
- name: native_static
|
||||
target: native_static
|
||||
image_variant: xenial
|
||||
image_variant: bionic
|
||||
lib_postfix: '/x86_64-linux-gnu'
|
||||
- name: native_dyn
|
||||
target: native_dyn
|
||||
image_variant: xenial
|
||||
lib_postfix: '/x86_64-linux-gnu'
|
||||
- name: native_dyn_bionic
|
||||
target: native_dyn
|
||||
image_variant: bionic
|
||||
lib_postfix: '/x86_64-linux-gnu'
|
||||
- name: android_arm
|
||||
target: android_arm
|
||||
image_variant: xenial
|
||||
lib_postfix: '/x86_64-linux-gnu'
|
||||
image_variant: bionic
|
||||
lib_postfix: '/arm-linux-androideabi'
|
||||
- name: android_arm64
|
||||
target: android_arm64
|
||||
image_variant: xenial
|
||||
lib_postfix: '/x86_64-linux-gnu'
|
||||
image_variant: bionic
|
||||
lib_postfix: '/aarch64-linux-android'
|
||||
- name: win32_static
|
||||
target: win32_static
|
||||
image_variant: f31
|
||||
image_variant: f35
|
||||
lib_postfix: '64'
|
||||
- name: win32_dyn
|
||||
target: win32_dyn
|
||||
image_variant: f31
|
||||
image_variant: f35
|
||||
lib_postfix: '64'
|
||||
env:
|
||||
HOME: /home/runner
|
||||
@@ -87,10 +86,6 @@ jobs:
|
||||
container:
|
||||
image: "kiwix/kiwix-build_ci:${{matrix.image_variant}}-31"
|
||||
steps:
|
||||
- name: Extract branch name
|
||||
shell: bash
|
||||
run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})"
|
||||
id: extract_branch
|
||||
- name: Checkout code
|
||||
shell: python
|
||||
run: |
|
||||
@@ -100,7 +95,7 @@ jobs:
|
||||
'git', 'clone',
|
||||
'https://github.com/${{github.repository}}',
|
||||
'--depth=1',
|
||||
'--branch', '${{steps.extract_branch.outputs.branch}}'
|
||||
'--branch', '${{ github.head_ref || github.ref_name }}'
|
||||
]
|
||||
check_call(command, cwd=environ['HOME'])
|
||||
- name: Install deps
|
||||
@@ -123,7 +118,7 @@ jobs:
|
||||
MESON_OPTION="$MESON_OPTION --cross-file $HOME/BUILD_${{matrix.target}}/meson_cross_file.txt"
|
||||
fi
|
||||
if [[ "${{matrix.target}}" =~ android_.* ]]; then
|
||||
MESON_OPTION="$MESON_OPTION -Dandroid=true"
|
||||
MESON_OPTION="$MESON_OPTION -Dstatic-linkage=true"
|
||||
fi
|
||||
cd $HOME/libkiwix
|
||||
meson . build ${MESON_OPTION}
|
||||
@@ -149,6 +144,6 @@ jobs:
|
||||
curl https://codecov.io/bash -o codecov.sh
|
||||
bash codecov.sh -n "${OS_NAME}_${{matrix.target}}" -Z
|
||||
rm codecov.sh
|
||||
if: startsWith(matrix.target, 'native_') && matrix.image_variant == 'xenial'
|
||||
if: startsWith(matrix.target, 'native_')
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
22
.github/workflows/package.yml
vendored
22
.github/workflows/package.yml
vendored
@@ -8,8 +8,8 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
distro:
|
||||
- ubuntu-kinetic
|
||||
- ubuntu-jammy
|
||||
- ubuntu-impish
|
||||
- ubuntu-focal
|
||||
- ubuntu-bionic
|
||||
steps:
|
||||
@@ -34,18 +34,18 @@ jobs:
|
||||
email: release+launchpad@kiwix.org
|
||||
distro: ${{ matrix.distro }}
|
||||
|
||||
- uses: legoktm/gh-action-build-deb@ubuntu-jammy
|
||||
if: matrix.distro == 'ubuntu-jammy'
|
||||
name: Build package for ubuntu-jammy
|
||||
id: build-ubuntu-jammy
|
||||
- uses: legoktm/gh-action-build-deb@ubuntu-kinetic
|
||||
if: matrix.distro == 'ubuntu-kinetic'
|
||||
name: Build package for ubuntu-kinetic
|
||||
id: build-ubuntu-kinetic
|
||||
with:
|
||||
args: --no-sign
|
||||
ppa: ${{ steps.ppa.outputs.ppa }}
|
||||
|
||||
- uses: legoktm/gh-action-build-deb@ubuntu-impish
|
||||
if: matrix.distro == 'ubuntu-impish'
|
||||
name: Build package for ubuntu-impish
|
||||
id: build-ubuntu-impish
|
||||
- uses: legoktm/gh-action-build-deb@ubuntu-jammy
|
||||
if: matrix.distro == 'ubuntu-jammy'
|
||||
name: Build package for ubuntu-jammy
|
||||
id: build-ubuntu-jammy
|
||||
with:
|
||||
args: --no-sign
|
||||
ppa: ${{ steps.ppa.outputs.ppa }}
|
||||
@@ -73,8 +73,8 @@ jobs:
|
||||
|
||||
- uses: legoktm/gh-action-dput@master
|
||||
name: Upload dev package
|
||||
# Only upload on pushes to master
|
||||
if: github.event_name == 'push' && github.event.ref == 'refs/heads/master' && startswith(matrix.distro, 'ubuntu-')
|
||||
# Only upload on pushes to git default branch
|
||||
if: github.event_name == 'push' && github.event.ref == 'refs/heads/main' && startswith(matrix.distro, 'ubuntu-')
|
||||
with:
|
||||
gpg_key: ${{ secrets.LAUNCHPAD_GPG }}
|
||||
repository: ppa:kiwixteam/dev
|
||||
|
||||
83
ChangeLog
83
ChangeLog
@@ -1,3 +1,86 @@
|
||||
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
|
||||
===============
|
||||
|
||||
|
||||
141
README.md
141
README.md
@@ -5,10 +5,12 @@ 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%3Amaster)
|
||||
[](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://codecov.io/gh/kiwix/libkiwix)
|
||||
[](https://www.gnu.org/licenses/gpl-3.0)
|
||||
|
||||
Disclaimer
|
||||
@@ -99,6 +101,33 @@ 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
|
||||
-------
|
||||
|
||||
@@ -122,7 +151,7 @@ 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
|
||||
@@ -132,6 +161,55 @@ 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 language 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 empeach 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
|
||||
---------------
|
||||
|
||||
@@ -154,63 +232,6 @@ cp ninja ../bin
|
||||
cd ..
|
||||
```
|
||||
|
||||
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
|
||||
type="text/javascript"
|
||||
src="{{root}}/skin/jquery-ui/external/jquery/jquery.js"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="{{root}}/skin/jquery-ui/jquery-ui.min.js"
|
||||
></script>
|
||||
<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 language 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="searchButton" value="Search"/>
|
||||
</form>
|
||||
```
|
||||
|
||||
|
||||
If you compile manually Libmicrohttpd, you might need to compile it
|
||||
without GNU TLS, a bug here will empeach 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.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ task writePom {
|
||||
project {
|
||||
groupId 'org.kiwix.kiwixlib'
|
||||
artifactId 'kiwixlib'
|
||||
version '10.0.1' + (System.env.KIWIXLIB_BUILDVERSION == null ? '' : '-'+System.env.KIWIXLIB_BUILDVERSION)
|
||||
version '10.1.1' + (System.env.KIWIXLIB_BUILDVERSION == null ? '' : '-'+System.env.KIWIXLIB_BUILDVERSION)
|
||||
packaging 'aar'
|
||||
name 'kiwixlib'
|
||||
url 'https://github.com/kiwix/libkiwix'
|
||||
|
||||
1
debian/libkiwix-dev.manpages
vendored
1
debian/libkiwix-dev.manpages
vendored
@@ -1 +1,2 @@
|
||||
usr/share/man/man1/kiwix-compile-resources.1*
|
||||
usr/share/man/man1/kiwix-compile-i18n.1*
|
||||
|
||||
@@ -19,7 +19,7 @@ import os
|
||||
|
||||
project = 'libkiwix'
|
||||
copyright = '2022, libkiwix-team'
|
||||
author = 'libzim-team'
|
||||
author = 'libkiwix-team'
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Welcome to libzim's documentation!
|
||||
Welcome to libkiwix's documentation!
|
||||
==================================
|
||||
|
||||
.. toctree::
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
breathe
|
||||
exhale
|
||||
sphinx<4
|
||||
|
||||
@@ -7,11 +7,9 @@ Introduction
|
||||
libkiwix is written in C++. To use the library, you need the include files of libkiwix have
|
||||
to link against libzim.
|
||||
|
||||
Errors are handled with exceptions. When something goes wrong, libzim throws an error,
|
||||
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.
|
||||
While libkiwix has some wrappers around libzim classes, they are deprecated and will be removed
|
||||
in the future.
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
#!/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/common/archiveTools.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/archiveTools.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
|
||||
@@ -38,7 +38,6 @@ namespace kiwix
|
||||
{
|
||||
|
||||
class OPDSDumper;
|
||||
class Reader;
|
||||
|
||||
/**
|
||||
* A class to store information about a book (a zim file)
|
||||
@@ -69,7 +68,6 @@ class Book
|
||||
~Book();
|
||||
|
||||
bool update(const Book& other);
|
||||
DEPRECATED void update(const Reader& reader);
|
||||
void update(const zim::Archive& archive);
|
||||
void updateFromXml(const pugi::xml_node& node, const std::string& baseDir);
|
||||
void updateFromOpds(const pugi::xml_node& node, const std::string& urlHost);
|
||||
|
||||
193
include/entry.h
193
include/entry.h
@@ -1,193 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2020 Matthieu Gautier <mgautier@kymeria.fr>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef KIWIX_ENTRY_H
|
||||
#define KIWIX_ENTRY_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <zim/entry.h>
|
||||
#include <zim/item.h>
|
||||
#include <exception>
|
||||
#include <string>
|
||||
|
||||
#include "common.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
|
||||
class NoEntry : public std::exception {};
|
||||
|
||||
/**
|
||||
* A entry represent an.. entry in a zim file.
|
||||
*/
|
||||
class Entry
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Construct an entry making reference to an zim article.
|
||||
*
|
||||
* @param article a zim::Article object
|
||||
*/
|
||||
DEPRECATED Entry(zim::Entry entry) : Entry(entry, true) {};
|
||||
virtual ~Entry() = default;
|
||||
|
||||
/**
|
||||
* Get the path of the entry.
|
||||
*
|
||||
* The path is the "key" of an entry.
|
||||
*
|
||||
* @return the path of the entry.
|
||||
*/
|
||||
std::string getPath() const { return entry.getPath(); }
|
||||
|
||||
/**
|
||||
* Get the title of the entry.
|
||||
*
|
||||
* @return the title of the entry.
|
||||
*/
|
||||
std::string getTitle() const { return entry.getTitle(); }
|
||||
|
||||
/**
|
||||
* Get the content of the entry.
|
||||
*
|
||||
* The string is a copy of the content.
|
||||
* If you don't want to do a copy, use get_blob.
|
||||
*
|
||||
* @return the content of the entry.
|
||||
*/
|
||||
std::string getContent() const { return entry.getItem().getData(); }
|
||||
|
||||
/**
|
||||
* Get the blob of the entry.
|
||||
*
|
||||
* A blob make reference to the content without copying it.
|
||||
*
|
||||
* @param offset The starting offset of the blob.
|
||||
* @return the blob of the entry.
|
||||
*/
|
||||
zim::Blob getBlob(offset_type offset = 0) const { return entry.getItem().getData(offset); }
|
||||
|
||||
/**
|
||||
* Get the blob of the entry.
|
||||
*
|
||||
* A blob make reference to the content without copying it.
|
||||
*
|
||||
* @param offset The starting offset of the blob.
|
||||
* @param size The size of the blob.
|
||||
* @return the blob of the entry.
|
||||
*/
|
||||
zim::Blob getBlob(offset_type offset, size_type size) const { return entry.getItem().getData(offset, size); }
|
||||
|
||||
/**
|
||||
* Get the info for direct access to the content of the entry.
|
||||
*
|
||||
* Some entry (ie binary ones) have their content plain stored
|
||||
* in the zim file. Knowing the offset where the content is stored
|
||||
* an user can directly read the content in the zim file bypassing the
|
||||
* libkiwix/libzim.
|
||||
*
|
||||
* @return A pair specifying where to read the content.
|
||||
* The string is the real file to read (may be different that .zim
|
||||
* file if zim is cut).
|
||||
* The offset is the offset to read in the file.
|
||||
* Return <"",0> if is not possible to read directly.
|
||||
*/
|
||||
zim::Item::DirectAccessInfo getDirectAccessInfo() const { return entry.getItem().getDirectAccessInformation(); }
|
||||
|
||||
/**
|
||||
* Get the size of the entry.
|
||||
*
|
||||
* @return the size of the entry.
|
||||
*/
|
||||
size_type getSize() const;
|
||||
|
||||
/**
|
||||
* Get the mime_type of the entry.
|
||||
*
|
||||
* @return the mime_type of the entry.
|
||||
*/
|
||||
std::string getMimetype() const;
|
||||
|
||||
|
||||
/**
|
||||
* Get if the entry is a redirect entry.
|
||||
*
|
||||
* @return True if the entry is a redirect.
|
||||
*/
|
||||
bool isRedirect() const;
|
||||
|
||||
/**
|
||||
* Get if the entry is a link target entry.
|
||||
*
|
||||
* @return True if the entry is a link target.
|
||||
*/
|
||||
bool isLinkTarget() const;
|
||||
|
||||
/**
|
||||
* Get if the entry is a deleted entry.
|
||||
*
|
||||
* @return True if the entry is a deleted entry.
|
||||
*/
|
||||
bool isDeleted() const;
|
||||
|
||||
/**
|
||||
* Get the entry pointed by this entry.
|
||||
*
|
||||
* @return the entry pointed.
|
||||
* @throw NoEntry if the entry is not a redirected entry.
|
||||
*/
|
||||
Entry getRedirectEntry() const;
|
||||
|
||||
/**
|
||||
* Get the final entry pointed by this entry.
|
||||
*
|
||||
* Follow the redirection until a "not redirecting" entry is found.
|
||||
* If the entry is not a redirected entry, return the entry itself.
|
||||
*
|
||||
* @return the final entry.
|
||||
*/
|
||||
Entry getFinalEntry() const;
|
||||
|
||||
/**
|
||||
* Get the zim entry wrapped by this (kiwix) entry
|
||||
*
|
||||
* @return the zim entry
|
||||
*/
|
||||
const zim::Entry& getZimEntry() const { return entry; }
|
||||
|
||||
private:
|
||||
zim::Entry entry;
|
||||
|
||||
private:
|
||||
// Entry is deprecated, so we've marked the constructor as deprecated.
|
||||
// But we still need to construct the entry (in our deprecated code)
|
||||
// To avoid warning because we use deprecated function, we create a second
|
||||
// constructor not deprecated. The `bool marker` is unused, it sole purpose
|
||||
// is to change the signature to have two different constructor.
|
||||
// This one is not deprecated and we must use it in our private code.
|
||||
Entry(zim::Entry entry, bool marker);
|
||||
friend class Reader;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // KIWIX_ENTRY_H
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <zim/archive.h>
|
||||
#include <zim/search.h>
|
||||
|
||||
#include "book.h"
|
||||
#include "bookmark.h"
|
||||
@@ -105,7 +106,15 @@ class Filter {
|
||||
Filter& rejectTags(const Tags& tags);
|
||||
|
||||
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);
|
||||
@@ -140,53 +149,35 @@ private: // functions
|
||||
bool accept(const Book& book) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* This class is not part of the libkiwix API. Its only purpose is
|
||||
* to simplify the implementation of the Library's move operations
|
||||
* and avoid bugs should new data members be added to Library.
|
||||
*/
|
||||
class LibraryBase
|
||||
|
||||
class ZimSearcher : public zim::Searcher
|
||||
{
|
||||
protected: // types
|
||||
typedef uint64_t LibraryRevision;
|
||||
public:
|
||||
explicit ZimSearcher(zim::Searcher&& searcher)
|
||||
: zim::Searcher(searcher)
|
||||
{}
|
||||
|
||||
struct Entry : Book
|
||||
{
|
||||
LibraryRevision lastUpdatedRevision = 0;
|
||||
|
||||
// May also keep the Archive and Reader pointers here and get
|
||||
// rid of the m_readers and m_archives data members in Library
|
||||
};
|
||||
|
||||
protected: // data
|
||||
LibraryRevision m_revision;
|
||||
std::map<std::string, Entry> m_books;
|
||||
std::map<std::string, std::shared_ptr<Reader>> m_readers;
|
||||
std::map<std::string, std::shared_ptr<zim::Archive>> m_archives;
|
||||
std::vector<kiwix::Bookmark> m_bookmarks;
|
||||
class BookDB;
|
||||
std::unique_ptr<BookDB> m_bookDB;
|
||||
|
||||
protected: // functions
|
||||
LibraryBase();
|
||||
~LibraryBase();
|
||||
|
||||
LibraryBase(LibraryBase&& );
|
||||
LibraryBase& operator=(LibraryBase&& );
|
||||
std::unique_lock<std::mutex> getLock() {
|
||||
return std::unique_lock<std::mutex>(m_mutex);
|
||||
}
|
||||
virtual ~ZimSearcher() = default;
|
||||
private:
|
||||
std::mutex m_mutex;
|
||||
};
|
||||
|
||||
/**
|
||||
* A Library store several books.
|
||||
*/
|
||||
class Library : private LibraryBase
|
||||
class Library
|
||||
{
|
||||
// all data fields must be added in LibraryBase
|
||||
mutable std::mutex m_mutex;
|
||||
|
||||
public:
|
||||
typedef LibraryRevision Revision;
|
||||
typedef uint64_t Revision;
|
||||
typedef std::vector<std::string> BookIdCollection;
|
||||
typedef std::map<std::string, int> AttributeCounts;
|
||||
typedef std::set<std::string> BookIdSet;
|
||||
|
||||
public:
|
||||
Library();
|
||||
@@ -240,8 +231,11 @@ class Library : private LibraryBase
|
||||
|
||||
Book getBookByIdThreadSafe(const std::string& id) const;
|
||||
|
||||
DEPRECATED std::shared_ptr<Reader> getReaderById(const std::string& id);
|
||||
std::shared_ptr<zim::Archive> getArchiveById(const std::string& id);
|
||||
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.
|
||||
@@ -346,12 +340,12 @@ class Library : private LibraryBase
|
||||
/**
|
||||
* Return the current revision of the library.
|
||||
*
|
||||
* The revision of the library is updated (incremented by one) only by
|
||||
* the addBook() operation.
|
||||
* The revision of the library is updated (incremented by one) by
|
||||
* the addBook() and removeBookById() operations.
|
||||
*
|
||||
* @return Current revision of the library.
|
||||
*/
|
||||
LibraryRevision getRevision() const;
|
||||
Revision getRevision() const;
|
||||
|
||||
/**
|
||||
* Remove books that have not been updated since the specified revision.
|
||||
@@ -359,20 +353,24 @@ class Library : private LibraryBase
|
||||
* @param rev the library revision to use
|
||||
* @return Count of books that were removed by this operation.
|
||||
*/
|
||||
uint32_t removeBooksNotUpdatedSince(LibraryRevision rev);
|
||||
uint32_t removeBooksNotUpdatedSince(Revision rev);
|
||||
|
||||
friend class OPDSDumper;
|
||||
friend class libXMLDumper;
|
||||
|
||||
private: // types
|
||||
typedef const std::string& (Book::*BookStrPropMemFn)() const;
|
||||
struct Impl;
|
||||
|
||||
private: // functions
|
||||
AttributeCounts getBookAttributeCounts(BookStrPropMemFn p) const;
|
||||
std::vector<std::string> getBookPropValueSet(BookStrPropMemFn p) const;
|
||||
BookIdCollection filterViaBookDB(const Filter& filter) const;
|
||||
void updateBookDB(const Book& book);
|
||||
void dropReader(const std::string& bookId);
|
||||
void dropCache(const std::string& bookId);
|
||||
|
||||
private: //data
|
||||
std::unique_ptr<Impl> mp_impl;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
|
||||
#include "book.h"
|
||||
#include "library.h"
|
||||
#include "reader.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -7,9 +7,6 @@ headers = [
|
||||
'libxml_dumper.h',
|
||||
'opds_dumper.h',
|
||||
'downloader.h',
|
||||
'reader.h',
|
||||
'entry.h',
|
||||
'searcher.h',
|
||||
'search_renderer.h',
|
||||
'server.h',
|
||||
'kiwixserve.h',
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
#include <pugixml.hpp>
|
||||
|
||||
#include "library.h"
|
||||
#include "reader.h"
|
||||
#include "name_mapper.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
@@ -42,7 +42,7 @@ class OPDSDumper
|
||||
{
|
||||
public:
|
||||
OPDSDumper() = default;
|
||||
OPDSDumper(Library* library);
|
||||
OPDSDumper(Library* library, NameMapper* NameMapper);
|
||||
~OPDSDumper();
|
||||
|
||||
/**
|
||||
@@ -111,6 +111,7 @@ class OPDSDumper
|
||||
|
||||
protected:
|
||||
kiwix::Library* library;
|
||||
kiwix::NameMapper* nameMapper;
|
||||
std::string libraryId;
|
||||
std::string rootLocation;
|
||||
int m_totalResults;
|
||||
|
||||
506
include/reader.h
506
include/reader.h
@@ -1,506 +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_READER_H
|
||||
#define KIWIX_READER_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <zim/zim.h>
|
||||
#include <zim/archive.h>
|
||||
#include <exception>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include "common.h"
|
||||
#include "entry.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
/**
|
||||
* The SuggestionItem is a helper class that contains the info about a single
|
||||
* suggestion item.
|
||||
*/
|
||||
class SuggestionItem
|
||||
{
|
||||
// Functions
|
||||
public:
|
||||
// Create a sugggestion item.
|
||||
explicit SuggestionItem(const std::string& title, const std::string& normalizedTitle,
|
||||
const std::string& path, const std::string& snippet = "") :
|
||||
title(title),
|
||||
normalizedTitle(normalizedTitle),
|
||||
path(path),
|
||||
snippet(snippet) {}
|
||||
|
||||
public:
|
||||
const std::string& getTitle() const { return title;}
|
||||
const std::string& getNormalizedTitle() const { return normalizedTitle;}
|
||||
const std::string& getPath() const { return path;}
|
||||
const std::string& getSnippet() const { return snippet;}
|
||||
|
||||
bool hasSnippet() const { return !snippet.empty();}
|
||||
|
||||
// Data
|
||||
private:
|
||||
std::string title;
|
||||
std::string normalizedTitle;
|
||||
std::string path;
|
||||
std::string snippet;
|
||||
};
|
||||
|
||||
/**
|
||||
* The Reader class is the class who allow to get an entry content from a zim
|
||||
* file.
|
||||
*
|
||||
* Reader is now deprecated. Directly use `zim::Archive`.
|
||||
*/
|
||||
|
||||
using SuggestionsList_t = std::vector<SuggestionItem>;
|
||||
class Reader
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Create a Reader to read a zim file specified by zimFilePath.
|
||||
*
|
||||
* @param zimFilePath The path to the zim file to read.
|
||||
* The zim file can be splitted (.zimaa, .zimab, ...).
|
||||
* In this case, the file path must still point to the
|
||||
* unsplitted path as if the file were not splitted
|
||||
* (.zim extesion).
|
||||
*/
|
||||
explicit DEPRECATED Reader(const string zimFilePath);
|
||||
|
||||
/**
|
||||
* Create a Reader to read a zim file given by the Archive.
|
||||
*
|
||||
* @param archive The shared pointer to the Archive object.
|
||||
*/
|
||||
explicit DEPRECATED Reader(const std::shared_ptr<zim::Archive> archive)
|
||||
: Reader(archive, true) {};
|
||||
#ifndef _WIN32
|
||||
explicit DEPRECATED Reader(int fd);
|
||||
DEPRECATED Reader(int fd, zim::offset_type offset, zim::size_type size);
|
||||
#endif
|
||||
~Reader() = default;
|
||||
|
||||
/**
|
||||
* Get the number of "displayable" entries in the zim file.
|
||||
*
|
||||
* @return If the zim file has a /M/Counter metadata, return the number of
|
||||
* entries with the 'text/html' MIMEtype specified in the metadata.
|
||||
* Else return the number of entries in the 'A' namespace.
|
||||
*/
|
||||
unsigned int getArticleCount() const;
|
||||
|
||||
/**
|
||||
* Get the number of media in the zim file.
|
||||
*
|
||||
* @return If the zim file has a /M/Counter metadata, return the number of
|
||||
* entries with the 'image/jpeg', 'image/gif' and 'image/png' in
|
||||
* the metadata.
|
||||
* Else return the number of entries in the 'I' namespace.
|
||||
*/
|
||||
unsigned int getMediaCount() const;
|
||||
|
||||
/**
|
||||
* Get the number of all entries in the zim file.
|
||||
*
|
||||
* @return Return the number of all the entries, whatever their MIMEtype or
|
||||
* their namespace.
|
||||
*/
|
||||
unsigned int getGlobalCount() const;
|
||||
|
||||
/**
|
||||
* Get the path of the zim file.
|
||||
*
|
||||
* @return the path of the zim file as given in the constructor.
|
||||
*/
|
||||
string getZimFilePath() const;
|
||||
|
||||
/**
|
||||
* Get the Id of the zim file.
|
||||
*
|
||||
* @return The uuid stored in the zim file.
|
||||
*/
|
||||
string getId() const;
|
||||
|
||||
/**
|
||||
* Get a random page.
|
||||
*
|
||||
* @return A random Entry. The entry is picked from all entries in
|
||||
* the 'A' namespace.
|
||||
* The main entry is excluded from the potential results.
|
||||
*/
|
||||
Entry getRandomPage() const;
|
||||
|
||||
/**
|
||||
* Get the entry of the main page.
|
||||
*
|
||||
* @return Entry of the main page as specified in the zim file.
|
||||
*/
|
||||
Entry getMainPage() const;
|
||||
|
||||
/**
|
||||
* Get the content of a metadata.
|
||||
*
|
||||
* @param[in] name The name of the metadata.
|
||||
* @param[out] value The value will be set to the content of the metadata.
|
||||
* @return True if it was possible to get the content of the metadata.
|
||||
*/
|
||||
bool getMetadata(const string& name, string& value) const;
|
||||
|
||||
/**
|
||||
* Get the name of the zim file.
|
||||
*
|
||||
* @return The name of the zim file as specified in the zim metadata.
|
||||
*/
|
||||
string getName() const;
|
||||
|
||||
/**
|
||||
* Get the title of the zim file.
|
||||
*
|
||||
* @return The title of zim file as specified in the zim metadata.
|
||||
* If no title has been set, return a title computed from the
|
||||
* file path.
|
||||
*/
|
||||
string getTitle() const;
|
||||
|
||||
/**
|
||||
* Get the creator of the zim file.
|
||||
*
|
||||
* @return The creator of the zim file as specified in the zim metadata.
|
||||
*/
|
||||
string getCreator() const;
|
||||
|
||||
/**
|
||||
* Get the publisher of the zim file.
|
||||
*
|
||||
* @return The publisher of the zim file as specified in the zim metadata.
|
||||
*/
|
||||
string getPublisher() const;
|
||||
|
||||
/**
|
||||
* Get the date of the zim file.
|
||||
*
|
||||
* @return The date of the zim file as specified in the zim metadata.
|
||||
*/
|
||||
string getDate() const;
|
||||
|
||||
/**
|
||||
* Get the description of the zim file.
|
||||
*
|
||||
* @return The description of the zim file as specified in the zim metadata.
|
||||
* If no description has been set, return the subtitle.
|
||||
*/
|
||||
string getDescription() const;
|
||||
|
||||
/**
|
||||
* Get the long description of the zim file.
|
||||
*
|
||||
* @return The long description of the zim file as specifed in the zim metadata.
|
||||
*/
|
||||
string getLongDescription() const;
|
||||
|
||||
/**
|
||||
* Get the language of the zim file.
|
||||
*
|
||||
* @return The language of the zim file as specified in the zim metadata.
|
||||
*/
|
||||
string getLanguage() const;
|
||||
|
||||
/**
|
||||
* Get the license of the zim file.
|
||||
*
|
||||
* @return The license of the zim file as specified in the zim metadata.
|
||||
*/
|
||||
string getLicense() const;
|
||||
|
||||
/**
|
||||
* Get the tags of the zim file.
|
||||
*
|
||||
* @param original If true, return the original tags as specified in the zim metadata.
|
||||
* Else, try to convert it to the new 'normalized' format.
|
||||
* @return The tags of the zim file.
|
||||
*/
|
||||
string getTags(bool original=false) const;
|
||||
|
||||
/**
|
||||
* Get the value (as a string) of a specific tag.
|
||||
*
|
||||
* According to https://wiki.openzim.org/wiki/Tags
|
||||
*
|
||||
* @return The value of the specified tag.
|
||||
* @throw std::out_of_range if the specified tag is not found.
|
||||
*/
|
||||
string getTagStr(const std::string& tagName) const;
|
||||
|
||||
/**
|
||||
* Get the boolean value of a specific tag.
|
||||
*
|
||||
* According to https://wiki.openzim.org/wiki/Tags
|
||||
*
|
||||
* @return The boolean value of the specified tag.
|
||||
* @throw std::out_of_range if the specified tag is not found.
|
||||
* std::domain_error if the value of the tag cannot be convert to bool.
|
||||
*/
|
||||
bool getTagBool(const std::string& tagName) const;
|
||||
|
||||
/**
|
||||
* Get the relations of the zim file.
|
||||
*
|
||||
* @return The relation of the zim file as specified in the zim metadata.
|
||||
*/
|
||||
string getRelation() const;
|
||||
|
||||
/**
|
||||
* Get the flavour of the zim file.
|
||||
*
|
||||
* @return The flavour of the zim file as specified in the zim metadata.
|
||||
*/
|
||||
string getFlavour() const;
|
||||
|
||||
/**
|
||||
* Get the source of the zim file.
|
||||
*
|
||||
* @return The source of the zim file as specified in the zim metadata.
|
||||
*/
|
||||
string getSource() const;
|
||||
|
||||
/**
|
||||
* Get the scraper of the zim file.
|
||||
*
|
||||
* @return The scraper of the zim file as specified in the zim metadata.
|
||||
*/
|
||||
string getScraper() const;
|
||||
|
||||
/**
|
||||
* Get the favicon of the zim file.
|
||||
*
|
||||
* @param[out] content The content of the favicon.
|
||||
* @param[out] mimeType The mimeType of the favicon.
|
||||
* @return True if a favicon has been found.
|
||||
*/
|
||||
bool getFavicon(string& content, string& mimeType) const;
|
||||
|
||||
/**
|
||||
* Get an entry associated to an path.
|
||||
*
|
||||
* @param path The path of the entry.
|
||||
* @return The entry.
|
||||
* @throw NoEntry If no entry correspond to the path.
|
||||
*/
|
||||
Entry getEntryFromPath(const std::string& path) const;
|
||||
|
||||
/**
|
||||
* Get an entry associated to an url encoded path.
|
||||
*
|
||||
* Equivalent to `getEntryFromPath(urlDecode(path));`
|
||||
*
|
||||
* @param path The url encoded path.
|
||||
* @return The entry.
|
||||
* @throw NoEntry If no entry correspond to the path.
|
||||
*/
|
||||
Entry getEntryFromEncodedPath(const std::string& path) const;
|
||||
|
||||
/**
|
||||
* Get un entry associated to a title.
|
||||
*
|
||||
* @param title The title.
|
||||
* @return The entry
|
||||
* throw NoEntry If no entry correspond to the url.
|
||||
*/
|
||||
Entry getEntryFromTitle(const std::string& title) const;
|
||||
|
||||
/**
|
||||
* Search for entries with title starting with prefix (case sensitive).
|
||||
*
|
||||
* Suggestions are stored in an internal vector and can be retrieved using
|
||||
* `getNextSuggestion` method.
|
||||
* This method is not thread safe and is deprecated. Use :
|
||||
* bool searchSuggestions(const string& prefix,
|
||||
* unsigned int suggestionsCount,
|
||||
* SuggestionsList_t& results);
|
||||
*
|
||||
* @param prefix The prefix to search.
|
||||
* @param suggestionsCount How many suggestions to search for.
|
||||
* @param reset If true, remove previous suggestions in the internal vector.
|
||||
* If false, add suggestions to the internal vector
|
||||
* (until internal vector size is suggestionCount (or no more
|
||||
* suggestion))
|
||||
* @return True if some suggestions have been added to the internal vector.
|
||||
*/
|
||||
DEPRECATED bool searchSuggestions(const string& prefix,
|
||||
unsigned int suggestionsCount,
|
||||
const bool reset = true);
|
||||
|
||||
/**
|
||||
* Search for entries with title starting with prefix (case sensitive).
|
||||
*
|
||||
* Suggestions are added to the `result` vector.
|
||||
*
|
||||
* @param prefix The prefix to search.
|
||||
* @param suggestionsCount How many suggestions to search for.
|
||||
* @param result The vector where to store the suggestions.
|
||||
* @return True if some suggestions have been added to the vector.
|
||||
*/
|
||||
|
||||
bool searchSuggestions(const string& prefix,
|
||||
unsigned int suggestionsCount,
|
||||
SuggestionsList_t& resuls);
|
||||
|
||||
/**
|
||||
* Search for entries for the given prefix.
|
||||
*
|
||||
* If the zim file has a internal fulltext index, the suggestions will be
|
||||
* searched using it.
|
||||
* Else the suggestions will be search using `searchSuggestions` while trying
|
||||
* to be smart about case sensitivity (using `getTitleVariants`).
|
||||
*
|
||||
* In any case, suggestions are stored in an internal vector and can be
|
||||
* retrieved using `getNextSuggestion` method.
|
||||
* The internal vector will be reset.
|
||||
* This method is not thread safe and is deprecated. Use :
|
||||
* bool searchSuggestionsSmart(const string& prefix,
|
||||
* unsigned int suggestionsCount,
|
||||
* SuggestionsList_t& results);
|
||||
*
|
||||
* @param prefix The prefix to search for.
|
||||
* @param suggestionsCount How many suggestions to search for.
|
||||
*/
|
||||
DEPRECATED bool searchSuggestionsSmart(const string& prefix,
|
||||
unsigned int suggestionsCount);
|
||||
|
||||
/**
|
||||
* Search for entries for the given prefix.
|
||||
*
|
||||
* If the zim file has a internal fulltext index, the suggestions will be
|
||||
* searched using it.
|
||||
* Else the suggestions will be search using `searchSuggestions` while trying
|
||||
* to be smart about case sensitivity (using `getTitleVariants`).
|
||||
*
|
||||
* In any case, suggestions are stored in an internal vector and can be
|
||||
* retrieved using `getNextSuggestion` method.
|
||||
* The internal vector will be reset.
|
||||
*
|
||||
* @param prefix The prefix to search for.
|
||||
* @param suggestionsCount How many suggestions to search for.
|
||||
* @param results The vector where to store the suggestions
|
||||
* @return True if some suggestions have been added to the results.
|
||||
*/
|
||||
bool searchSuggestionsSmart(const string& prefix,
|
||||
unsigned int suggestionsCount,
|
||||
SuggestionsList_t& results);
|
||||
|
||||
|
||||
/**
|
||||
* Check if the path exists in the zim file.
|
||||
*
|
||||
* @param path the path to check.
|
||||
* @return True if the path exists in the zim file.
|
||||
*/
|
||||
bool pathExists(const string& path) const;
|
||||
|
||||
/**
|
||||
* Check if the zim file has a embedded fulltext index.
|
||||
*
|
||||
* @return True if the zim file has a embedded fulltext index
|
||||
* and is not split (else the fulltext is not accessible).
|
||||
*/
|
||||
bool hasFulltextIndex() const;
|
||||
|
||||
/**
|
||||
* Get potential case title variations for a title.
|
||||
*
|
||||
* @param title a title.
|
||||
* @return the list of variantions.
|
||||
*/
|
||||
std::vector<std::string> getTitleVariants(const std::string& title) const;
|
||||
|
||||
/**
|
||||
* Get the next suggestion title.
|
||||
*
|
||||
* @param[out] title the title of the suggestion.
|
||||
* @return True if title has been set.
|
||||
*/
|
||||
DEPRECATED bool getNextSuggestion(string& title);
|
||||
|
||||
/**
|
||||
* Get the next suggestion title and url.
|
||||
*
|
||||
* @param[out] title the title of the suggestion.
|
||||
* @param[out] url the url of the suggestion.
|
||||
* @return True if title and url have been set.
|
||||
*/
|
||||
DEPRECATED bool getNextSuggestion(string& title, string& url);
|
||||
|
||||
/**
|
||||
* Get if we can check zim file integrity (has a checksum).
|
||||
*
|
||||
* @return True if zim file have a checksum.
|
||||
*/
|
||||
bool canCheckIntegrity() const;
|
||||
|
||||
/**
|
||||
* Check is zim file is corrupted.
|
||||
*
|
||||
* @return True if zim file is corrupted.
|
||||
*/
|
||||
bool isCorrupted() const;
|
||||
|
||||
/**
|
||||
* Return the total size of the zim file.
|
||||
*
|
||||
* If zim file is split, return the sum of all parts' size.
|
||||
*
|
||||
* @return Size of the size file is KiB.
|
||||
*/
|
||||
unsigned int getFileSize() const;
|
||||
|
||||
/**
|
||||
* Get the zim file handler.
|
||||
*
|
||||
* @return The libzim file handler.
|
||||
*/
|
||||
zim::Archive* getZimArchive() const;
|
||||
|
||||
protected:
|
||||
std::shared_ptr<zim::Archive> zimArchive;
|
||||
std::string zimFilePath;
|
||||
|
||||
SuggestionsList_t suggestions;
|
||||
SuggestionsList_t::iterator suggestionsOffset;
|
||||
|
||||
private:
|
||||
std::map<const std::string, unsigned int> parseCounterMetadata() const;
|
||||
|
||||
// Reader is deprecated, so we've marked the constructor as deprecated.
|
||||
// But we still need to construct the reader (in our deprecated code)
|
||||
// To avoid warning because we use deprecated function, we create a
|
||||
// constructor not deprecated. The `bool marker` is unused, it sole purpose
|
||||
// is to change the signature to have a different constructor.
|
||||
// This one is not deprecated and we must use it in our private code.
|
||||
Reader(const std::shared_ptr<zim::Archive> archive, bool marker);
|
||||
friend class Library;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -22,11 +22,11 @@
|
||||
|
||||
#include <string>
|
||||
#include <zim/search.h>
|
||||
#include "library.h"
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
class Searcher;
|
||||
class NameMapper;
|
||||
/**
|
||||
* The SearcherRenderer class is used to render a search result to a html page.
|
||||
@@ -34,20 +34,13 @@ class NameMapper;
|
||||
class SearchRenderer
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Construct a SearchRenderer from a Searcher.
|
||||
*
|
||||
* This method is now deprecated. Construct the renderer from a
|
||||
* `zim::SearchResultSet`
|
||||
*
|
||||
* @param searcher The `Searcher` to render.
|
||||
* @param mapper The `NameMapper` to use to do the rendering.
|
||||
*/
|
||||
DEPRECATED SearchRenderer(Searcher* searcher, NameMapper* mapper);
|
||||
|
||||
/**
|
||||
* Construct a SearchRenderer from a SearchResultSet.
|
||||
*
|
||||
* The constructed version of the SearchRenderer will not introduce
|
||||
* the book name for each result. It is better to use the other constructor
|
||||
* with a Library pointer to have a better html page.
|
||||
*
|
||||
* @param srs The `SearchResultSet` to render.
|
||||
* @param mapper The `NameMapper` to use to do the rendering.
|
||||
* @param start The start offset used for the srs.
|
||||
@@ -56,14 +49,29 @@ class SearchRenderer
|
||||
SearchRenderer(zim::SearchResultSet srs, NameMapper* mapper,
|
||||
unsigned int start, unsigned int estimatedResultCount);
|
||||
|
||||
/**
|
||||
* Construct a SearchRenderer from a SearchResultSet.
|
||||
*
|
||||
* @param srs The `SearchResultSet` to render.
|
||||
* @param mapper The `NameMapper` to use to do the rendering.
|
||||
* @param library The `Library` to use to look up book details for search results.
|
||||
* @param start The start offset used for the srs.
|
||||
* @param estimatedResultCount The estimatedResultCount of the whole search
|
||||
*/
|
||||
SearchRenderer(zim::SearchResultSet srs, NameMapper* mapper, Library* library,
|
||||
unsigned int start, unsigned int estimatedResultCount);
|
||||
|
||||
~SearchRenderer();
|
||||
|
||||
/**
|
||||
* Set the search pattern used to do the search
|
||||
*/
|
||||
void setSearchPattern(const std::string& pattern);
|
||||
|
||||
/**
|
||||
* Set the search content id.
|
||||
* Set the querystring used to select books
|
||||
*/
|
||||
void setSearchContent(const std::string& name);
|
||||
void setSearchBookQuery(const std::string& bookQuery);
|
||||
|
||||
/**
|
||||
* Set protocol prefix.
|
||||
@@ -82,16 +90,25 @@ class SearchRenderer
|
||||
this->pageLength = pageLength;
|
||||
}
|
||||
|
||||
std::string renderTemplate(const std::string& tmpl_str);
|
||||
|
||||
/**
|
||||
* Generate the html page with the resutls of the search.
|
||||
*/
|
||||
std::string getHtml();
|
||||
|
||||
/**
|
||||
* Generate the xml page with the resutls of the search.
|
||||
*/
|
||||
std::string getXml();
|
||||
|
||||
|
||||
protected:
|
||||
std::string beautifyInteger(const unsigned int number);
|
||||
zim::SearchResultSet m_srs;
|
||||
NameMapper* mp_nameMapper;
|
||||
std::string searchContent;
|
||||
Library* mp_library;
|
||||
std::string searchBookQuery;
|
||||
std::string searchPattern;
|
||||
std::string protocolPrefix;
|
||||
std::string searchProtocolPrefix;
|
||||
|
||||
@@ -1,180 +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_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 <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <zim/search.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 std::string get_zimId() = 0;
|
||||
};
|
||||
|
||||
struct SearcherInternal;
|
||||
struct SuggestionInternal;
|
||||
/**
|
||||
* The Searcher class is reponsible to do different kind of search using the
|
||||
* fulltext index.
|
||||
*
|
||||
* The Searcher is now deprecated. Use libzim search feature.
|
||||
*/
|
||||
class Searcher
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* The default constructor.
|
||||
*/
|
||||
DEPRECATED Searcher();
|
||||
|
||||
~Searcher();
|
||||
|
||||
/**
|
||||
* Add a reader (containing embedded fulltext index) to the search.
|
||||
*
|
||||
* @param reader The Reader for the zim containing the fulltext index.
|
||||
* @return true if the reader has been added.
|
||||
* false if the reader cannot be added (no embedded fulltext index present)
|
||||
*/
|
||||
bool add_reader(Reader* reader);
|
||||
|
||||
|
||||
Reader* get_reader(int index);
|
||||
|
||||
/**
|
||||
* Start a search on the zim associated to the Searcher.
|
||||
*
|
||||
* Search results should be retrived using the getNextResult method.
|
||||
*
|
||||
* @param search The search query.
|
||||
* @param resultStart the start offset of the search results (used for pagination).
|
||||
* @param maxResultCount Maximum results to get from start (used for pagination).
|
||||
* @param verbose print some info on stdout if true.
|
||||
*/
|
||||
void search(const std::string& search,
|
||||
unsigned int resultStart,
|
||||
unsigned int maxResultCount,
|
||||
const bool verbose = false);
|
||||
|
||||
/**
|
||||
* Start a geographique search.
|
||||
* The search return result for entry in a disc of center latitude/longitude
|
||||
* and radius distance.
|
||||
*
|
||||
* Search results should be retrived using the getNextResult method.
|
||||
*
|
||||
* @param latitude The latitude of the center point.
|
||||
* @param longitude The longitude of the center point.
|
||||
* @param distance The radius of the disc.
|
||||
* @param resultStart the start offset of the search results (used for pagination).
|
||||
* @param maxResultCount Maximum number of results to get from start (used for pagination).
|
||||
* @param verbose print some info on stdout if true.
|
||||
*/
|
||||
void geo_search(float latitude, float longitude, float distance,
|
||||
unsigned int resultStart,
|
||||
unsigned int maxResultCount,
|
||||
const bool verbose = false);
|
||||
|
||||
/**
|
||||
* Start a suggestion search.
|
||||
* The search made depend of the "version" of the embedded index.
|
||||
* - If the index is newer enough and have a title namespace, the search is
|
||||
* made in the titles only.
|
||||
* - Else the search is made on the whole article content.
|
||||
* In any case, the search is made "partial" (as adding '*' at the end of the query)
|
||||
*
|
||||
* @param search The search query.
|
||||
* @param verbose print some info on stdout if true.
|
||||
*/
|
||||
void suggestions(std::string& search, const bool verbose = false);
|
||||
|
||||
/**
|
||||
* Get the next result of a started search.
|
||||
* This is the method to use to loop hover the search results.
|
||||
*/
|
||||
Result* getNextResult();
|
||||
|
||||
/**
|
||||
* Restart the previous search.
|
||||
* Next call to getNextResult will return the first result.
|
||||
*/
|
||||
void restart_search();
|
||||
|
||||
/**
|
||||
* Get a estimation of the result count.
|
||||
*/
|
||||
unsigned int getEstimatedResultCount();
|
||||
|
||||
/**
|
||||
* Get a SearchResultSet object for current search
|
||||
*/
|
||||
zim::SearchResultSet getSearchResultSet();
|
||||
|
||||
unsigned int getResultStart() { return resultStart; }
|
||||
unsigned int getMaxResultCount() { return maxResultCount; }
|
||||
|
||||
protected:
|
||||
std::string beautifyInteger(const unsigned int number);
|
||||
void closeIndex();
|
||||
void searchInIndex(string& search,
|
||||
const unsigned int resultStart,
|
||||
const unsigned int maxResultCount,
|
||||
const bool verbose = false);
|
||||
|
||||
std::vector<Reader*> readers;
|
||||
std::unique_ptr<SearcherInternal> internal;
|
||||
std::unique_ptr<SuggestionInternal> suggestionInternal;
|
||||
std::string searchPattern;
|
||||
unsigned int estimatedResultCount;
|
||||
unsigned int resultStart;
|
||||
unsigned int maxResultCount;
|
||||
|
||||
private:
|
||||
void reset();
|
||||
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -54,6 +54,8 @@ namespace kiwix
|
||||
void setAddress(const std::string& addr) { m_addr = addr; }
|
||||
void setPort(int port) { m_port = port; }
|
||||
void setNbThreads(int threads) { m_nbThreads = threads; }
|
||||
void setMultiZimSearchLimit(unsigned int limit) { m_multizimSearchLimit = limit; }
|
||||
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)
|
||||
@@ -62,7 +64,7 @@ namespace kiwix
|
||||
{ m_blockExternalLinks = blockExternalLinks; }
|
||||
int getPort();
|
||||
std::string getAddress();
|
||||
|
||||
|
||||
protected:
|
||||
Library* mp_library;
|
||||
NameMapper* mp_nameMapper;
|
||||
@@ -71,10 +73,12 @@ namespace kiwix
|
||||
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;
|
||||
int m_ipConnectionLimit = 0;
|
||||
std::unique_ptr<InternalServer> mp_server;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
27
meson.build
27
meson.build
@@ -1,29 +1,20 @@
|
||||
project('libkiwix', 'cpp',
|
||||
version : '10.0.1', # Also change this in android-kiwix-lib-publisher/kiwixLibAndroid/build.gradle
|
||||
version : '12.0.0',
|
||||
license : 'GPLv3+',
|
||||
default_options : ['c_std=c11', 'cpp_std=c++11', 'werror=true'])
|
||||
|
||||
compiler = meson.get_compiler('cpp')
|
||||
|
||||
wrapper = get_option('wrapper')
|
||||
static_deps = get_option('static-linkage') or get_option('default_library') == 'static'
|
||||
|
||||
static_deps = wrapper.contains('android') or wrapper.contains('java') or get_option('default_library') == 'static'
|
||||
if wrapper.contains('android')
|
||||
extra_libs = ['-llog']
|
||||
# See https://github.com/kiwix/libkiwix/issues/371
|
||||
if ['arm', 'mips', 'm68k', 'ppc', 'sh4'].contains(host_machine.cpu_family())
|
||||
extra_libs = ['-latomic']
|
||||
else
|
||||
extra_libs = []
|
||||
endif
|
||||
|
||||
if wrapper.contains('java')
|
||||
add_languages('java')
|
||||
endif
|
||||
|
||||
# See https://github.com/kiwix/libkiwix/issues/371
|
||||
if ['arm', 'mips', 'm68k', 'ppc', 'sh4'].contains(target_machine.cpu_family())
|
||||
extra_libs += '-latomic'
|
||||
endif
|
||||
|
||||
if (compiler.get_id() == 'gcc' and build_machine.system() == 'linux') or target_machine.system() == 'freebsd'
|
||||
if (compiler.get_id() == 'gcc' and build_machine.system() == 'linux') or host_machine.system() == 'freebsd'
|
||||
# C++ std::thread is implemented using pthread on linux by gcc
|
||||
thread_dep = dependency('threads')
|
||||
else
|
||||
@@ -44,19 +35,19 @@ else
|
||||
error('Cannot found header mustache.hpp')
|
||||
endif
|
||||
|
||||
libzim_dep = dependency('libzim', version : '>=7.2.0', static:static_deps)
|
||||
libzim_dep = dependency('libzim', version : '>=8.1.0', static:static_deps)
|
||||
if not compiler.has_header_symbol('zim/zim.h', 'LIBZIM_WITH_XAPIAN')
|
||||
error('Libzim seems to be compiled without xapian. Xapian support is mandatory.')
|
||||
endif
|
||||
|
||||
|
||||
extra_cflags = ''
|
||||
if target_machine.system() == 'windows' and static_deps
|
||||
if host_machine.system() == 'windows' and static_deps
|
||||
add_project_arguments('-DCURL_STATICLIB', language : 'cpp')
|
||||
extra_cflags += '-DCURL_STATICLIB'
|
||||
endif
|
||||
|
||||
if target_machine.system() == 'windows'
|
||||
if host_machine.system() == 'windows'
|
||||
add_project_arguments('-DNOMINMAX', language: 'cpp')
|
||||
endif
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
option('wrapper', type:'array', choices:['java', 'android'], value:[],
|
||||
description: 'The wrapper to generate.')
|
||||
option('static-linkage', type : 'boolean', value : false,
|
||||
description : 'Link statically with the dependencies.')
|
||||
option('doc', type : 'boolean', value : false,
|
||||
description : 'Build the documentations.')
|
||||
|
||||
14
scripts/format_code.sh
Executable file
14
scripts/format_code.sh
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/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
|
||||
148
scripts/kiwix-compile-i18n
Executable file
148
scripts/kiwix-compile-i18n
Executable file
@@ -0,0 +1,148 @@
|
||||
#!/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.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))
|
||||
|
||||
18
scripts/kiwix-compile-i18n.1
Normal file
18
scripts/kiwix-compile-i18n.1
Normal file
@@ -0,0 +1,18 @@
|
||||
.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,15 +52,21 @@ 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}"""
|
||||
|
||||
class Resource:
|
||||
def __init__(self, base_dirs, filename):
|
||||
filename = filename.strip()
|
||||
def __init__(self, base_dirs, filename, cacheid=None):
|
||||
filename = filename
|
||||
self.filename = filename
|
||||
self.identifier = full_identifier(filename)
|
||||
self.cacheid = cacheid
|
||||
found = False
|
||||
for base_dir in base_dirs:
|
||||
try:
|
||||
@@ -71,7 +77,7 @@ class Resource:
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
if not found:
|
||||
raise Exception("Impossible to found {}".format(filename))
|
||||
raise Exception("Resource not found: {}".format(filename))
|
||||
|
||||
def dump_impl(self):
|
||||
nb_row = len(self.data)//16 + (1 if len(self.data) % 16 else 0)
|
||||
@@ -93,6 +99,12 @@ class Resource:
|
||||
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]),
|
||||
@@ -102,7 +114,7 @@ class Resource:
|
||||
|
||||
|
||||
|
||||
master_c_template = """//This file is automaically generated. Do not modify it.
|
||||
master_c_template = """//This file is automatically generated. Do not modify it.
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <fstream>
|
||||
@@ -123,7 +135,12 @@ 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.");
|
||||
throw ResourceNotFound("Resource not found: " + name);
|
||||
}}
|
||||
|
||||
const char* getResourceCacheId_{basename}(const std::string& name) {{
|
||||
{RESOURCE_CACHEID_GETTER}
|
||||
return nullptr;
|
||||
}}
|
||||
|
||||
{RESOURCES}
|
||||
@@ -134,6 +151,7 @@ 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)
|
||||
)
|
||||
@@ -159,8 +177,10 @@ 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}
|
||||
|
||||
@@ -189,8 +209,8 @@ if __name__ == "__main__":
|
||||
base_dir = os.path.dirname(os.path.realpath(args.resource_file))
|
||||
source_dir = args.source_dir or []
|
||||
with open(args.resource_file, 'r') as f:
|
||||
resources = [Resource([base_dir]+source_dir, filename)
|
||||
for filename in f.readlines()]
|
||||
resources = [Resource([base_dir]+source_dir, *line.strip().split())
|
||||
for line in f.readlines()]
|
||||
|
||||
h_identifier = to_identifier(os.path.basename(args.hfile))
|
||||
with open(args.hfile, 'w') as f:
|
||||
|
||||
135
scripts/kiwix-resources
Executable file
135
scripts/kiwix-resources
Executable file
@@ -0,0 +1,135 @@
|
||||
#!/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,6 +1,13 @@
|
||||
|
||||
res_manager = find_program('kiwix-resources')
|
||||
res_compiler = find_program('kiwix-compile-resources')
|
||||
|
||||
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')
|
||||
|
||||
16
src/book.cpp
16
src/book.cpp
@@ -18,7 +18,6 @@
|
||||
*/
|
||||
|
||||
#include "book.h"
|
||||
#include "reader.h"
|
||||
|
||||
#include "tools.h"
|
||||
#include "tools/base64.h"
|
||||
@@ -30,7 +29,7 @@
|
||||
#include "tools/archiveTools.h"
|
||||
|
||||
#include <zim/archive.h>
|
||||
|
||||
#include <zim/item.h>
|
||||
#include <pugixml.hpp>
|
||||
|
||||
namespace kiwix
|
||||
@@ -64,15 +63,10 @@ bool Book::update(const kiwix::Book& other)
|
||||
return true;
|
||||
}
|
||||
|
||||
void Book::update(const kiwix::Reader& reader)
|
||||
{
|
||||
update(*reader.getZimArchive());
|
||||
}
|
||||
|
||||
void Book::update(const zim::Archive& archive) {
|
||||
m_path = archive.getFilename();
|
||||
m_pathValid = true;
|
||||
m_id = getArchiveId(archive);
|
||||
m_id = std::string(archive.getUuid());
|
||||
m_title = getArchiveTitle(archive);
|
||||
m_description = getMetaDescription(archive);
|
||||
m_language = getMetaLanguage(archive);
|
||||
@@ -84,7 +78,7 @@ void Book::update(const zim::Archive& archive) {
|
||||
m_tags = getMetaTags(archive);
|
||||
m_category = getCategoryFromTags();
|
||||
m_articleCount = archive.getArticleCount();
|
||||
m_mediaCount = getArchiveMediaCount(archive);
|
||||
m_mediaCount = archive.getMediaCount();
|
||||
m_size = static_cast<uint64_t>(getArchiveFileSize(archive)) << 10;
|
||||
|
||||
m_illustrations.clear();
|
||||
@@ -161,7 +155,9 @@ void Book::updateFromOpds(const pugi::xml_node& node, const std::string& urlHost
|
||||
m_language = VALUE("language");
|
||||
m_creator = node.child("author").child("name").child_value();
|
||||
m_publisher = node.child("publisher").child("name").child_value();
|
||||
m_date = fromOpdsDate(VALUE("updated"));
|
||||
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");
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2020 Matthieu Gautier <mgautier@kymeria.fr>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "reader.h"
|
||||
#include <time.h>
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
Entry::Entry(zim::Entry entry, bool _marker)
|
||||
: entry(entry)
|
||||
{
|
||||
}
|
||||
|
||||
size_type Entry::getSize() const
|
||||
{
|
||||
if (entry.isRedirect()) {
|
||||
return 0;
|
||||
} else {
|
||||
return entry.getItem().getSize();
|
||||
}
|
||||
}
|
||||
|
||||
std::string Entry::getMimetype() const
|
||||
{
|
||||
return entry.getItem(true).getMimetype();
|
||||
}
|
||||
|
||||
bool Entry::isRedirect() const
|
||||
{
|
||||
return entry.isRedirect();
|
||||
}
|
||||
|
||||
Entry Entry::getRedirectEntry() const
|
||||
{
|
||||
if ( !entry.isRedirect() ) {
|
||||
throw NoEntry();
|
||||
}
|
||||
|
||||
return Entry(entry.getRedirectEntry(), true);
|
||||
}
|
||||
|
||||
Entry Entry::getFinalEntry() const
|
||||
{
|
||||
int loopCounter = 42;
|
||||
auto final_entry = entry;
|
||||
while (final_entry.isRedirect() && loopCounter--) {
|
||||
final_entry = final_entry.getRedirectEntry();
|
||||
}
|
||||
// Prevent infinite loops.
|
||||
if (final_entry.isRedirect()) {
|
||||
throw NoEntry();
|
||||
}
|
||||
return Entry(final_entry, true);
|
||||
}
|
||||
|
||||
}
|
||||
253
src/library.cpp
253
src/library.cpp
@@ -19,7 +19,6 @@
|
||||
|
||||
#include "library.h"
|
||||
#include "book.h"
|
||||
#include "reader.h"
|
||||
#include "libxml_dumper.h"
|
||||
|
||||
#include "tools.h"
|
||||
@@ -27,10 +26,13 @@
|
||||
#include "tools/regexTools.h"
|
||||
#include "tools/pathTools.h"
|
||||
#include "tools/stringTools.h"
|
||||
#include "tools/otherTools.h"
|
||||
#include "tools/concurrent_cache.h"
|
||||
|
||||
#include <pugixml.hpp>
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
#include <cmath>
|
||||
#include <unicode/locid.h>
|
||||
#include <xapian.h>
|
||||
|
||||
@@ -56,68 +58,129 @@ bool booksReferToTheSameArchive(const Book& book1, const Book& book2)
|
||||
&& book1.getPath() == book2.getPath();
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
class LibraryBase::BookDB : public Xapian::WritableDatabase
|
||||
template<typename Key, typename Value>
|
||||
class MultiKeyCache: public ConcurrentCache<std::set<Key>, Value>
|
||||
{
|
||||
public:
|
||||
BookDB() : Xapian::WritableDatabase("", Xapian::DB_BACKEND_INMEMORY) {}
|
||||
public:
|
||||
explicit MultiKeyCache(size_t maxEntries)
|
||||
: ConcurrentCache<std::set<Key>, Value>(maxEntries)
|
||||
{}
|
||||
|
||||
bool drop(const Key& key)
|
||||
{
|
||||
std::unique_lock<std::mutex> l(this->lock_);
|
||||
bool removed = false;
|
||||
for(auto& cache_key: this->impl_.keys()) {
|
||||
if(cache_key.find(key)!=cache_key.end()) {
|
||||
removed |= this->impl_.drop(cache_key);
|
||||
}
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
};
|
||||
|
||||
LibraryBase::LibraryBase()
|
||||
: m_bookDB(new BookDB)
|
||||
} // unnamed namespace
|
||||
|
||||
struct Library::Impl
|
||||
{
|
||||
struct Entry : Book
|
||||
{
|
||||
Library::Revision lastUpdatedRevision = 0;
|
||||
};
|
||||
|
||||
Library::Revision m_revision;
|
||||
std::map<std::string, Entry> m_books;
|
||||
using ArchiveCache = ConcurrentCache<std::string, std::shared_ptr<zim::Archive>>;
|
||||
std::unique_ptr<ArchiveCache> mp_archiveCache;
|
||||
using SearcherCache = MultiKeyCache<std::string, std::shared_ptr<ZimSearcher>>;
|
||||
std::unique_ptr<SearcherCache> mp_searcherCache;
|
||||
std::vector<kiwix::Bookmark> m_bookmarks;
|
||||
Xapian::WritableDatabase m_bookDB;
|
||||
|
||||
unsigned int getBookCount(const bool localBooks, const bool remoteBooks) const;
|
||||
|
||||
Impl();
|
||||
~Impl();
|
||||
|
||||
Impl(Impl&& );
|
||||
Impl& operator=(Impl&& );
|
||||
};
|
||||
|
||||
Library::Impl::Impl()
|
||||
: mp_archiveCache(new ArchiveCache(std::max(getEnvVar<int>("KIWIX_ARCHIVE_CACHE_SIZE", 1), 1))),
|
||||
mp_searcherCache(new SearcherCache(std::max(getEnvVar<int>("KIWIX_SEARCHER_CACHE_SIZE", 1), 1))),
|
||||
m_bookDB("", Xapian::DB_BACKEND_INMEMORY)
|
||||
{
|
||||
}
|
||||
|
||||
LibraryBase::~LibraryBase()
|
||||
Library::Impl::~Impl()
|
||||
{
|
||||
}
|
||||
|
||||
LibraryBase::LibraryBase(LibraryBase&& ) = default;
|
||||
LibraryBase& LibraryBase::operator=(LibraryBase&& ) = default;
|
||||
Library::Impl::Impl(Library::Impl&& ) = default;
|
||||
Library::Impl& Library::Impl::operator=(Library::Impl&& ) = default;
|
||||
|
||||
unsigned int
|
||||
Library::Impl::getBookCount(const bool localBooks, const bool remoteBooks) const
|
||||
{
|
||||
unsigned int result = 0;
|
||||
for (auto& pair: m_books) {
|
||||
auto& book = pair.second;
|
||||
if ((!book.getPath().empty() && localBooks)
|
||||
|| (!book.getUrl().empty() && remoteBooks)) {
|
||||
result++;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Constructor */
|
||||
Library::Library()
|
||||
: mp_impl(new Library::Impl)
|
||||
{
|
||||
}
|
||||
|
||||
Library::Library(Library&& other)
|
||||
: LibraryBase(std::move(other))
|
||||
: mp_impl(std::move(other.mp_impl))
|
||||
{
|
||||
}
|
||||
|
||||
Library& Library::operator=(Library&& other)
|
||||
{
|
||||
LibraryBase::operator=(std::move(other));
|
||||
mp_impl = std::move(other.mp_impl);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/* Destructor */
|
||||
Library::~Library()
|
||||
{
|
||||
}
|
||||
|
||||
Library::~Library() = default;
|
||||
|
||||
bool Library::addBook(const Book& book)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
++m_revision;
|
||||
++mp_impl->m_revision;
|
||||
/* Try to find it */
|
||||
updateBookDB(book);
|
||||
try {
|
||||
auto& oldbook = m_books.at(book.getId());
|
||||
auto& oldbook = mp_impl->m_books.at(book.getId());
|
||||
if ( ! booksReferToTheSameArchive(oldbook, book) ) {
|
||||
dropReader(book.getId());
|
||||
dropCache(book.getId());
|
||||
}
|
||||
oldbook.update(book); // XXX: This may have no effect if oldbook is readonly
|
||||
// XXX: Then m_bookDB will become out-of-sync with
|
||||
// XXX: the real contents of the library.
|
||||
oldbook.lastUpdatedRevision = m_revision;
|
||||
oldbook.lastUpdatedRevision = mp_impl->m_revision;
|
||||
return false;
|
||||
} catch (std::out_of_range&) {
|
||||
Entry& newEntry = m_books[book.getId()];
|
||||
auto& newEntry = mp_impl->m_books[book.getId()];
|
||||
static_cast<Book&>(newEntry) = book;
|
||||
newEntry.lastUpdatedRevision = m_revision;
|
||||
newEntry.lastUpdatedRevision = mp_impl->m_revision;
|
||||
size_t new_cache_size = static_cast<size_t>(std::ceil(mp_impl->getBookCount(true, true)*0.1));
|
||||
if (getEnvVar<int>("KIWIX_ARCHIVE_CACHE_SIZE", -1) <= 0) {
|
||||
mp_impl->mp_archiveCache->setMaxSize(new_cache_size);
|
||||
}
|
||||
if (getEnvVar<int>("KIWIX_SEARCHER_CACHE_SIZE", -1) <= 0) {
|
||||
mp_impl->mp_searcherCache->setMaxSize(new_cache_size);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -125,15 +188,15 @@ bool Library::addBook(const Book& book)
|
||||
void Library::addBookmark(const Bookmark& bookmark)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_bookmarks.push_back(bookmark);
|
||||
mp_impl->m_bookmarks.push_back(bookmark);
|
||||
}
|
||||
|
||||
bool Library::removeBookmark(const std::string& zimId, const std::string& url)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
for(auto it=m_bookmarks.begin(); it!=m_bookmarks.end(); it++) {
|
||||
for(auto it=mp_impl->m_bookmarks.begin(); it!=mp_impl->m_bookmarks.end(); it++) {
|
||||
if (it->getBookId() == zimId && it->getUrl() == url) {
|
||||
m_bookmarks.erase(it);
|
||||
mp_impl->m_bookmarks.erase(it);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -141,32 +204,42 @@ bool Library::removeBookmark(const std::string& zimId, const std::string& url)
|
||||
}
|
||||
|
||||
|
||||
void Library::dropReader(const std::string& id)
|
||||
void Library::dropCache(const std::string& id)
|
||||
{
|
||||
m_readers.erase(id);
|
||||
m_archives.erase(id);
|
||||
mp_impl->mp_archiveCache->drop(id);
|
||||
mp_impl->mp_searcherCache->drop(id);
|
||||
}
|
||||
|
||||
bool Library::removeBookById(const std::string& id)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_bookDB->delete_document("Q" + id);
|
||||
dropReader(id);
|
||||
return m_books.erase(id) == 1;
|
||||
mp_impl->m_bookDB.delete_document("Q" + id);
|
||||
dropCache(id);
|
||||
// We do not change the cache size here
|
||||
// Most of the time, the book is remove in case of library refresh, it is
|
||||
// often associated with addBook calls (which will properly set the cache size)
|
||||
// Having a too big cache is not a problem here (or it would have been before)
|
||||
// (And setMaxSize doesn't actually reduce the cache size, extra cached items
|
||||
// will be removed in put or getOrPut).
|
||||
const bool bookWasRemoved = mp_impl->m_books.erase(id) == 1;
|
||||
if ( bookWasRemoved ) {
|
||||
++mp_impl->m_revision;
|
||||
}
|
||||
return bookWasRemoved;
|
||||
}
|
||||
|
||||
Library::Revision Library::getRevision() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return m_revision;
|
||||
return mp_impl->m_revision;
|
||||
}
|
||||
|
||||
uint32_t Library::removeBooksNotUpdatedSince(LibraryRevision libraryRevision)
|
||||
uint32_t Library::removeBooksNotUpdatedSince(Revision libraryRevision)
|
||||
{
|
||||
BookIdCollection booksToRemove;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
for ( const auto& entry : m_books) {
|
||||
for ( const auto& entry : mp_impl->m_books) {
|
||||
if ( entry.second.lastUpdatedRevision <= libraryRevision ) {
|
||||
booksToRemove.push_back(entry.first);
|
||||
}
|
||||
@@ -185,7 +258,7 @@ const Book& Library::getBookById(const std::string& id) const
|
||||
{
|
||||
// XXX: Doesn't make sense to lock this operation since it cannot
|
||||
// XXX: guarantee thread-safety because of its return type
|
||||
return m_books.at(id);
|
||||
return mp_impl->m_books.at(id);
|
||||
}
|
||||
|
||||
Book Library::getBookByIdThreadSafe(const std::string& id) const
|
||||
@@ -198,7 +271,7 @@ const Book& Library::getBookByPath(const std::string& path) const
|
||||
{
|
||||
// XXX: Doesn't make sense to lock this operation since it cannot
|
||||
// XXX: guarantee thread-safety because of its return type
|
||||
for(auto& it: m_books) {
|
||||
for(auto& it: mp_impl->m_books) {
|
||||
auto& book = it.second;
|
||||
if (book.getPath() == path)
|
||||
return book;
|
||||
@@ -208,52 +281,48 @@ const Book& Library::getBookByPath(const std::string& path) const
|
||||
throw std::out_of_range(ss.str());
|
||||
}
|
||||
|
||||
std::shared_ptr<Reader> Library::getReaderById(const std::string& id)
|
||||
{
|
||||
try {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return m_readers.at(id);
|
||||
} catch (std::out_of_range& e) {}
|
||||
|
||||
const auto archive = getArchiveById(id);
|
||||
if ( !archive )
|
||||
return nullptr;
|
||||
|
||||
const shared_ptr<Reader> reader(new Reader(archive, true));
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_readers[id] = reader;
|
||||
return reader;
|
||||
}
|
||||
|
||||
std::shared_ptr<zim::Archive> Library::getArchiveById(const std::string& id)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
try {
|
||||
return m_archives.at(id);
|
||||
} catch (std::out_of_range& e) {}
|
||||
|
||||
auto book = getBookById(id);
|
||||
if (!book.isPathValid())
|
||||
return mp_impl->mp_archiveCache->getOrPut(id,
|
||||
[&](){
|
||||
auto book = getBookById(id);
|
||||
if (!book.isPathValid()) {
|
||||
throw std::invalid_argument("");
|
||||
}
|
||||
return std::make_shared<zim::Archive>(book.getPath());
|
||||
});
|
||||
} catch (std::invalid_argument&) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
auto sptr = make_shared<zim::Archive>(book.getPath());
|
||||
m_archives[id] = sptr;
|
||||
return sptr;
|
||||
std::shared_ptr<ZimSearcher> Library::getSearcherByIds(const BookIdSet& ids)
|
||||
{
|
||||
assert(!ids.empty());
|
||||
try {
|
||||
return mp_impl->mp_searcherCache->getOrPut(ids,
|
||||
[&](){
|
||||
std::vector<zim::Archive> archives;
|
||||
for(auto& id:ids) {
|
||||
auto archive = getArchiveById(id);
|
||||
if(!archive) {
|
||||
throw std::invalid_argument("");
|
||||
}
|
||||
archives.push_back(*archive);
|
||||
}
|
||||
return std::make_shared<ZimSearcher>(zim::Searcher(archives));
|
||||
});
|
||||
} catch (std::invalid_argument&) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int Library::getBookCount(const bool localBooks,
|
||||
const bool remoteBooks) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
unsigned int result = 0;
|
||||
for (auto& pair: m_books) {
|
||||
auto& book = pair.second;
|
||||
if ((!book.getPath().empty() && localBooks)
|
||||
|| (book.getPath().empty() && remoteBooks)) {
|
||||
result++;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return mp_impl->getBookCount(localBooks, remoteBooks);
|
||||
}
|
||||
|
||||
bool Library::writeToFile(const std::string& path) const
|
||||
@@ -284,7 +353,7 @@ Library::AttributeCounts Library::getBookAttributeCounts(BookStrPropMemFn p) con
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
AttributeCounts propValueCounts;
|
||||
|
||||
for (const auto& pair: m_books) {
|
||||
for (const auto& pair: mp_impl->m_books) {
|
||||
const auto& book = pair.second;
|
||||
if (book.getOrigId().empty()) {
|
||||
propValueCounts[(book.*p)()] += 1;
|
||||
@@ -317,7 +386,7 @@ std::vector<std::string> Library::getBooksCategories() const
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
std::set<std::string> categories;
|
||||
|
||||
for (const auto& pair: m_books) {
|
||||
for (const auto& pair: mp_impl->m_books) {
|
||||
const auto& book = pair.second;
|
||||
const auto& c = book.getCategory();
|
||||
if ( !c.empty() ) {
|
||||
@@ -341,12 +410,12 @@ std::vector<std::string> Library::getBooksPublishers() const
|
||||
const std::vector<kiwix::Bookmark> Library::getBookmarks(bool onlyValidBookmarks) const
|
||||
{
|
||||
if (!onlyValidBookmarks) {
|
||||
return m_bookmarks;
|
||||
return mp_impl->m_bookmarks;
|
||||
}
|
||||
std::vector<kiwix::Bookmark> validBookmarks;
|
||||
auto booksId = getBooksIds();
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
for(auto& bookmark:m_bookmarks) {
|
||||
for(auto& bookmark:mp_impl->m_bookmarks) {
|
||||
if (std::find(booksId.begin(), booksId.end(), bookmark.getBookId()) != booksId.end()) {
|
||||
validBookmarks.push_back(bookmark);
|
||||
}
|
||||
@@ -359,7 +428,7 @@ Library::BookIdCollection Library::getBooksIds() const
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
BookIdCollection bookIds;
|
||||
|
||||
for (auto& pair: m_books) {
|
||||
for (auto& pair: mp_impl->m_books) {
|
||||
bookIds.push_back(pair.first);
|
||||
}
|
||||
|
||||
@@ -397,15 +466,20 @@ void Library::updateBookDB(const Book& book)
|
||||
indexer.index_text(normalizeText(book.getName()), 1, "XN");
|
||||
indexer.index_text(normalizeText(book.getCategory()), 1, "XC");
|
||||
|
||||
for ( const auto& tag : split(normalizeText(book.getTags()), ";") )
|
||||
for ( const auto& tag : split(normalizeText(book.getTags()), ";") ) {
|
||||
doc.add_boolean_term("XT" + tag);
|
||||
if ( tag[0] != '_' ) {
|
||||
indexer.increase_termpos();
|
||||
indexer.index_text(tag);
|
||||
}
|
||||
}
|
||||
|
||||
const std::string idterm = "Q" + book.getId();
|
||||
doc.add_boolean_term(idterm);
|
||||
|
||||
doc.set_data(book.getId());
|
||||
|
||||
m_bookDB->replace_document(idterm, doc);
|
||||
mp_impl->m_bookDB.replace_document(idterm, doc);
|
||||
}
|
||||
|
||||
namespace
|
||||
@@ -461,9 +535,20 @@ Xapian::Query categoryQuery(const std::string& category)
|
||||
return Xapian::Query("XC" + normalizeText(category));
|
||||
}
|
||||
|
||||
Xapian::Query langQuery(const std::string& lang)
|
||||
Xapian::Query langQuery(const std::string& commaSeparatedLanguageList)
|
||||
{
|
||||
return Xapian::Query("L" + normalizeText(lang));
|
||||
Xapian::Query q;
|
||||
bool firstIteration = true;
|
||||
for ( const auto& lang : kiwix::split(commaSeparatedLanguageList, ",") ) {
|
||||
const Xapian::Query singleLangQuery("L" + normalizeText(lang));
|
||||
if ( firstIteration ) {
|
||||
q = singleLangQuery;
|
||||
firstIteration = false;
|
||||
} else {
|
||||
q = Xapian::Query(Xapian::Query::OP_OR, q, singleLangQuery);
|
||||
}
|
||||
}
|
||||
return q;
|
||||
}
|
||||
|
||||
Xapian::Query publisherQuery(const std::string& publisher)
|
||||
@@ -538,9 +623,9 @@ Library::BookIdCollection Library::filterViaBookDB(const Filter& filter) const
|
||||
BookIdCollection bookIds;
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
Xapian::Enquire enquire(*m_bookDB);
|
||||
Xapian::Enquire enquire(mp_impl->m_bookDB);
|
||||
enquire.set_query(query);
|
||||
const auto results = enquire.get_mset(0, m_books.size());
|
||||
const auto results = enquire.get_mset(0, mp_impl->m_books.size());
|
||||
for ( auto it = results.begin(); it != results.end(); ++it ) {
|
||||
bookIds.push_back(it.get_document().get_data());
|
||||
}
|
||||
@@ -554,7 +639,7 @@ Library::BookIdCollection Library::filter(const Filter& filter) const
|
||||
const auto preliminaryResult = filterViaBookDB(filter);
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
for(auto id : preliminaryResult) {
|
||||
if(filter.accept(m_books.at(id))) {
|
||||
if(filter.accept(mp_impl->m_books.at(id))) {
|
||||
result.push_back(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,7 @@ kiwix_sources = [
|
||||
'libxml_dumper.cpp',
|
||||
'opds_dumper.cpp',
|
||||
'downloader.cpp',
|
||||
'reader.cpp',
|
||||
'entry.cpp',
|
||||
'server.cpp',
|
||||
'searcher.cpp',
|
||||
'search_renderer.cpp',
|
||||
'subprocess.cpp',
|
||||
'aria2.cpp',
|
||||
@@ -28,10 +25,12 @@ kiwix_sources = [
|
||||
'server/response.cpp',
|
||||
'server/internalServer.cpp',
|
||||
'server/internalServer_catalog_v2.cpp',
|
||||
'server/i18n.cpp',
|
||||
'opds_catalog.cpp',
|
||||
'version.cpp'
|
||||
]
|
||||
kiwix_sources += lib_resources
|
||||
kiwix_sources += i18n_resources
|
||||
|
||||
if host_machine.system() == 'windows'
|
||||
kiwix_sources += 'subprocess_windows.cpp'
|
||||
@@ -39,22 +38,14 @@ else
|
||||
kiwix_sources += 'subprocess_unix.cpp'
|
||||
endif
|
||||
|
||||
if wrapper.contains('android')
|
||||
install_dir = 'kiwix-lib/jniLibs/' + meson.get_cross_property('android_abi')
|
||||
else
|
||||
install_dir = get_option('libdir')
|
||||
endif
|
||||
|
||||
if wrapper.contains('android') or wrapper.contains('java')
|
||||
subdir('wrapper/java')
|
||||
endif
|
||||
install_dir = get_option('libdir')
|
||||
|
||||
config_h = configure_file(output : 'kiwix_config.h',
|
||||
configuration : conf,
|
||||
input : 'config.h.in')
|
||||
install_headers(config_h, subdir:'kiwix')
|
||||
|
||||
kiwixlib = library('kiwix',
|
||||
libkiwix = library('kiwix',
|
||||
kiwix_sources,
|
||||
include_directories : inc,
|
||||
dependencies : all_deps,
|
||||
|
||||
@@ -20,9 +20,8 @@
|
||||
#include "opds_dumper.h"
|
||||
#include "book.h"
|
||||
|
||||
#include "kiwixlib-resources.h"
|
||||
#include "libkiwix-resources.h"
|
||||
#include <mustache.hpp>
|
||||
#include <unicode/locid.h>
|
||||
|
||||
#include "tools/stringTools.h"
|
||||
#include "tools/otherTools.h"
|
||||
@@ -31,8 +30,9 @@ namespace kiwix
|
||||
{
|
||||
|
||||
/* Constructor */
|
||||
OPDSDumper::OPDSDumper(Library* library)
|
||||
: library(library)
|
||||
OPDSDumper::OPDSDumper(Library* library, NameMapper* nameMapper)
|
||||
: library(library),
|
||||
nameMapper(nameMapper)
|
||||
{
|
||||
}
|
||||
/* Destructor */
|
||||
@@ -50,6 +50,8 @@ void OPDSDumper::setOpenSearchInfo(int totalResults, int startIndex, int count)
|
||||
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;
|
||||
@@ -70,19 +72,19 @@ IllustrationInfo getBookIllustrationInfo(const Book& book)
|
||||
return illustrations;
|
||||
}
|
||||
|
||||
kainjow::mustache::object getSingleBookData(const Book& book)
|
||||
std::string fullEntryXML(const Book& book, const std::string& rootLocation, const std::string& contentId)
|
||||
{
|
||||
const MustacheData bookUrl = book.getUrl().empty()
|
||||
? MustacheData(false)
|
||||
: MustacheData(book.getUrl());
|
||||
return kainjow::mustache::object{
|
||||
const auto bookDate = book.getDate() + "T00:00:00Z";
|
||||
const kainjow::mustache::object data{
|
||||
{"root", rootLocation},
|
||||
{"id", book.getId()},
|
||||
{"name", book.getName()},
|
||||
{"title", book.getTitle()},
|
||||
{"description", book.getDescription()},
|
||||
{"language", book.getLanguage()},
|
||||
{"content_id", urlEncode(book.getHumanReadableIdFromPath(), true)},
|
||||
{"updated", book.getDate() + "T00:00:00Z"},
|
||||
{"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()},
|
||||
@@ -90,31 +92,38 @@ kainjow::mustache::object getSingleBookData(const Book& book)
|
||||
{"media_count", to_string(book.getMediaCount())},
|
||||
{"author_name", book.getCreator()},
|
||||
{"publisher_name", book.getPublisher()},
|
||||
{"url", bookUrl},
|
||||
{"url", onlyAsNonEmptyMustacheValue(book.getUrl())},
|
||||
{"size", to_string(book.getSize())},
|
||||
{"icons", getBookIllustrationInfo(book)},
|
||||
};
|
||||
return render_template(RESOURCE::templates::catalog_v2_entry_xml, data);
|
||||
}
|
||||
|
||||
std::string getSingleBookEntryXML(const Book& book, bool withXMLHeader, const std::string& rootLocation, const std::string& endpointRoot, bool partial)
|
||||
std::string partialEntryXML(const Book& book, const std::string& rootLocation)
|
||||
{
|
||||
auto data = getSingleBookData(book);
|
||||
data["with_xml_header"] = MustacheData(withXMLHeader);
|
||||
data["dump_partial_entries"] = MustacheData(partial);
|
||||
data["endpoint_root"] = endpointRoot;
|
||||
data["root"] = rootLocation;
|
||||
return render_template(RESOURCE::templates::catalog_v2_entry_xml, data);
|
||||
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 std::vector<std::string>& bookIds, const std::string& rootLocation, const std::string& endpointRoot, bool partial)
|
||||
BooksData getBooksData(const Library* library, const NameMapper* nameMapper, const std::vector<std::string>& bookIds, const std::string& rootLocation, bool partial)
|
||||
{
|
||||
BooksData booksData;
|
||||
for ( const auto& bookId : bookIds ) {
|
||||
try {
|
||||
const Book book = library->getBookByIdThreadSafe(bookId);
|
||||
booksData.push_back(kainjow::mustache::object{
|
||||
{"entry", getSingleBookEntryXML(book, false, rootLocation, endpointRoot, partial)}
|
||||
});
|
||||
const std::string contentId = nameMapper->getNameForId(bookId);
|
||||
const auto entryXML = partial
|
||||
? partialEntryXML(book, rootLocation)
|
||||
: fullEntryXML(book, rootLocation, 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
|
||||
@@ -124,25 +133,69 @@ BooksData getBooksData(const Library* library, const std::vector<std::string>& b
|
||||
return booksData;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> iso639_3 = {
|
||||
{"atj", "atikamekw"},
|
||||
{"azb", "آذربایجان دیلی"},
|
||||
{"bcl", "central bikol"},
|
||||
{"bgs", "tagabawa"},
|
||||
{"bxr", "буряад хэлэн"},
|
||||
{"cbk", "chavacano"},
|
||||
{"cdo", "閩東語"},
|
||||
{"dag", "Dagbani"},
|
||||
{"diq", "dimli"},
|
||||
{"dty", "डोटेली"},
|
||||
{"eml", "emiliân-rumagnōl"},
|
||||
{"fbs", "српскохрватски"},
|
||||
{"ido", "ido"},
|
||||
{"kbp", "kabɩyɛ"},
|
||||
{"kld", "Gamilaraay"},
|
||||
{"lbe", "лакку маз"},
|
||||
{"lbj", "ལ་དྭགས་སྐད་"},
|
||||
{"map", "Austronesian"},
|
||||
{"mhr", "марий йылме"},
|
||||
{"mnw", "ဘာသာမန်"},
|
||||
{"myn", "mayan"},
|
||||
{"nah", "nahuatl"},
|
||||
{"nai", "north American Indian"},
|
||||
{"nds", "plattdütsch"},
|
||||
{"nrm", "bhasa narom"},
|
||||
{"olo", "livvi"},
|
||||
{"pih", "Pitcairn-Norfolk"},
|
||||
{"pnb", "Western Panjabi"},
|
||||
{"rmr", "Caló"},
|
||||
{"rmy", "romani shib"},
|
||||
{"roa", "romance languages"},
|
||||
{"twi", "twi"}
|
||||
};
|
||||
|
||||
std::once_flag fillLanguagesFlag;
|
||||
|
||||
void fillLanguagesMap()
|
||||
{
|
||||
for (auto icuLangPtr = icu::Locale::getISOLanguages(); *icuLangPtr != NULL; ++icuLangPtr) {
|
||||
const ICULanguageInfo lang(*icuLangPtr);
|
||||
iso639_3.insert({lang.iso3Code(), lang.selfName()});
|
||||
}
|
||||
}
|
||||
|
||||
std::string getLanguageSelfName(const std::string& lang) {
|
||||
const icu::Locale locale(lang.c_str());
|
||||
icu::UnicodeString ustring;
|
||||
locale.getDisplayLanguage(locale, ustring);
|
||||
std::string result;
|
||||
ustring.toUTF8String(result);
|
||||
return result;
|
||||
const auto itr = iso639_3.find(lang);
|
||||
if (itr != iso639_3.end()) {
|
||||
return itr->second;
|
||||
}
|
||||
return lang;
|
||||
};
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
string OPDSDumper::dumpOPDSFeed(const std::vector<std::string>& bookIds, const std::string& query) const
|
||||
{
|
||||
const auto booksData = getBooksData(library, bookIds, rootLocation, "", false);
|
||||
const auto booksData = getBooksData(library, nameMapper, bookIds, rootLocation, false);
|
||||
const kainjow::mustache::object template_data{
|
||||
{"date", gen_date_str()},
|
||||
{"root", rootLocation},
|
||||
{"feed_id", gen_uuid(libraryId + "/catalog/search?"+query)},
|
||||
{"filter", query.empty() ? MustacheData(false) : MustacheData(query)},
|
||||
{"filter", onlyAsNonEmptyMustacheValue(query)},
|
||||
{"totalResults", to_string(m_totalResults)},
|
||||
{"startIndex", to_string(m_startIndex)},
|
||||
{"itemsPerPage", to_string(m_count)},
|
||||
@@ -155,15 +208,15 @@ string OPDSDumper::dumpOPDSFeed(const std::vector<std::string>& bookIds, const s
|
||||
string OPDSDumper::dumpOPDSFeedV2(const std::vector<std::string>& bookIds, const std::string& query, bool partial) const
|
||||
{
|
||||
const auto endpointRoot = rootLocation + "/catalog/v2";
|
||||
const auto booksData = getBooksData(library, bookIds, rootLocation, endpointRoot, partial);
|
||||
const auto booksData = getBooksData(library, nameMapper, bookIds, rootLocation, partial);
|
||||
|
||||
const char* const endpoint = partial ? "/partial_entries" : "/entries";
|
||||
const kainjow::mustache::object template_data{
|
||||
{"date", gen_date_str()},
|
||||
{"endpoint_root", endpointRoot},
|
||||
{"feed_id", gen_uuid(libraryId + endpoint + "?" + query)},
|
||||
{"filter", query.empty() ? MustacheData(false) : MustacheData(query)},
|
||||
{"query", query.empty() ? "" : "?" + urlEncode(query)},
|
||||
{"filter", onlyAsNonEmptyMustacheValue(query)},
|
||||
{"query", query.empty() ? "" : "?" + query},
|
||||
{"totalResults", to_string(m_totalResults)},
|
||||
{"startIndex", to_string(m_startIndex)},
|
||||
{"itemsPerPage", to_string(m_count)},
|
||||
@@ -176,7 +229,11 @@ string OPDSDumper::dumpOPDSFeedV2(const std::vector<std::string>& bookIds, const
|
||||
|
||||
std::string OPDSDumper::dumpOPDSCompleteEntry(const std::string& bookId) const
|
||||
{
|
||||
return getSingleBookEntryXML(library->getBookById(bookId), true, rootLocation, "", false);
|
||||
const auto book = library->getBookById(bookId);
|
||||
const std::string contentId = nameMapper->getNameForId(bookId);
|
||||
return XML_HEADER
|
||||
+ "\n"
|
||||
+ fullEntryXML(book, rootLocation, contentId);
|
||||
}
|
||||
|
||||
std::string OPDSDumper::categoriesOPDSFeed() const
|
||||
@@ -208,6 +265,7 @@ std::string OPDSDumper::languagesOPDSFeed() const
|
||||
{
|
||||
const auto now = gen_date_str();
|
||||
kainjow::mustache::list languageData;
|
||||
std::call_once(fillLanguagesFlag, fillLanguagesMap);
|
||||
for ( const auto& langAndBookCount : library->getBooksLanguagesWithCounts() ) {
|
||||
const std::string languageCode = langAndBookCount.first;
|
||||
const int bookCount = langAndBookCount.second;
|
||||
|
||||
472
src/reader.cpp
472
src/reader.cpp
@@ -1,472 +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 "reader.h"
|
||||
#include <time.h>
|
||||
|
||||
#include <zim/search.h>
|
||||
#include <zim/suggestion.h>
|
||||
#include <zim/item.h>
|
||||
#include <zim/error.h>
|
||||
|
||||
#include "tools.h"
|
||||
#include "tools/stringTools.h"
|
||||
#include "tools/otherTools.h"
|
||||
#include "tools/archiveTools.h"
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
/* Constructor */
|
||||
Reader::Reader(const string zimFilePath)
|
||||
: zimArchive(nullptr),
|
||||
zimFilePath(zimFilePath)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
zimArchive.reset(new zim::Archive(tmpZimFilePath));
|
||||
|
||||
/* initialize random seed: */
|
||||
srand(time(nullptr));
|
||||
}
|
||||
|
||||
Reader::Reader(const std::shared_ptr<zim::Archive> archive, bool _marker)
|
||||
: zimArchive(archive),
|
||||
zimFilePath(archive->getFilename())
|
||||
{}
|
||||
|
||||
#ifndef _WIN32
|
||||
Reader::Reader(int fd)
|
||||
: zimArchive(new zim::Archive(fd)),
|
||||
zimFilePath("")
|
||||
{
|
||||
/* initialize random seed: */
|
||||
srand(time(nullptr));
|
||||
}
|
||||
|
||||
Reader::Reader(int fd, zim::offset_type offset, zim::size_type size)
|
||||
: zimArchive(new zim::Archive(fd, offset, size)),
|
||||
zimFilePath("")
|
||||
{
|
||||
/* initialize random seed: */
|
||||
srand(time(nullptr));
|
||||
}
|
||||
#endif // #ifndef _WIN32
|
||||
|
||||
zim::Archive* Reader::getZimArchive() const
|
||||
{
|
||||
return zimArchive.get();
|
||||
}
|
||||
|
||||
MimeCounterType Reader::parseCounterMetadata() const
|
||||
{
|
||||
return kiwix::parseArchiveCounter(*zimArchive);
|
||||
}
|
||||
|
||||
/* 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;
|
||||
|
||||
for(auto &pair:counterMap) {
|
||||
if (startsWith(pair.first, "text/html")) {
|
||||
counter += pair.second;
|
||||
}
|
||||
}
|
||||
|
||||
return counter;
|
||||
}
|
||||
|
||||
/* Get the count of medias content in the ZIM file */
|
||||
unsigned int Reader::getMediaCount() const
|
||||
{
|
||||
return kiwix::getArchiveMediaCount(*zimArchive);
|
||||
}
|
||||
|
||||
/* Get the total of all items of a ZIM file, redirects included */
|
||||
unsigned int Reader::getGlobalCount() const
|
||||
{
|
||||
return zimArchive->getEntryCount();
|
||||
}
|
||||
|
||||
/* Return the UID of the ZIM file */
|
||||
string Reader::getId() const
|
||||
{
|
||||
return kiwix::getArchiveId(*zimArchive);
|
||||
}
|
||||
|
||||
Entry Reader::getRandomPage() const
|
||||
{
|
||||
try {
|
||||
return Entry(zimArchive->getRandomEntry(), true);
|
||||
} catch(...) {
|
||||
throw NoEntry();
|
||||
}
|
||||
}
|
||||
|
||||
Entry Reader::getMainPage() const
|
||||
{
|
||||
return Entry(zimArchive->getMainEntry(), true);
|
||||
}
|
||||
|
||||
bool Reader::getFavicon(string& content, string& mimeType) const
|
||||
{
|
||||
return kiwix::getArchiveFavicon(*zimArchive, 48, content, mimeType);
|
||||
}
|
||||
|
||||
string Reader::getZimFilePath() const
|
||||
{
|
||||
return zimFilePath;
|
||||
}
|
||||
/* Return a metatag value */
|
||||
bool Reader::getMetadata(const string& name, string& value) const
|
||||
{
|
||||
try {
|
||||
value = zimArchive->getMetadata(name);
|
||||
return true;
|
||||
} catch(zim::EntryNotFound& e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#define METADATA(NAME) std::string v; getMetadata(NAME, v); return v;
|
||||
|
||||
string Reader::getName() const
|
||||
{
|
||||
return kiwix::getMetaName(*zimArchive);
|
||||
}
|
||||
|
||||
string Reader::getTitle() const
|
||||
{
|
||||
return kiwix::getArchiveTitle(*zimArchive);
|
||||
}
|
||||
|
||||
string Reader::getCreator() const
|
||||
{
|
||||
return kiwix::getMetaCreator(*zimArchive);
|
||||
}
|
||||
|
||||
string Reader::getPublisher() const
|
||||
{
|
||||
return kiwix::getMetaPublisher(*zimArchive);
|
||||
}
|
||||
|
||||
string Reader::getDate() const
|
||||
{
|
||||
return kiwix::getMetaDate(*zimArchive);
|
||||
}
|
||||
|
||||
string Reader::getDescription() const
|
||||
{
|
||||
return kiwix::getMetaDescription(*zimArchive);
|
||||
}
|
||||
|
||||
string Reader::getLongDescription() const
|
||||
{
|
||||
METADATA("LongDescription")
|
||||
}
|
||||
|
||||
string Reader::getLanguage() const
|
||||
{
|
||||
return kiwix::getMetaLanguage(*zimArchive);
|
||||
}
|
||||
|
||||
string Reader::getLicense() const
|
||||
{
|
||||
METADATA("License")
|
||||
}
|
||||
|
||||
string Reader::getTags(bool original) const
|
||||
{
|
||||
return kiwix::getMetaTags(*zimArchive, original);
|
||||
}
|
||||
|
||||
|
||||
string Reader::getTagStr(const std::string& tagName) const
|
||||
{
|
||||
string tags_str;
|
||||
getMetadata("Tags", tags_str);
|
||||
return getTagValueFromTagList(convertTags(tags_str), tagName);
|
||||
}
|
||||
|
||||
bool Reader::getTagBool(const std::string& tagName) const
|
||||
{
|
||||
return convertStrToBool(getTagStr(tagName));
|
||||
}
|
||||
|
||||
string Reader::getRelation() const
|
||||
{
|
||||
METADATA("Relation")
|
||||
}
|
||||
|
||||
string Reader::getFlavour() const
|
||||
{
|
||||
return kiwix::getMetaFlavour(*zimArchive);
|
||||
}
|
||||
|
||||
string Reader::getSource() const
|
||||
{
|
||||
METADATA("Source")
|
||||
}
|
||||
|
||||
string Reader::getScraper() const
|
||||
{
|
||||
METADATA("Scraper")
|
||||
}
|
||||
#undef METADATA
|
||||
|
||||
Entry Reader::getEntryFromPath(const std::string& path) const
|
||||
{
|
||||
try {
|
||||
return Entry(kiwix::getEntryFromPath(*zimArchive, path), true);
|
||||
} catch (zim::EntryNotFound& e) {
|
||||
throw NoEntry();
|
||||
}
|
||||
}
|
||||
|
||||
Entry Reader::getEntryFromEncodedPath(const std::string& path) const
|
||||
{
|
||||
return getEntryFromPath(urlDecode(path, true));
|
||||
}
|
||||
|
||||
Entry Reader::getEntryFromTitle(const std::string& title) const
|
||||
{
|
||||
try {
|
||||
return Entry(zimArchive->getEntryByTitle(title), true);
|
||||
} catch(zim::EntryNotFound& e) {
|
||||
throw NoEntry();
|
||||
}
|
||||
}
|
||||
|
||||
bool Reader::pathExists(const string& path) const
|
||||
{
|
||||
return zimArchive->hasEntryByPath(path);
|
||||
}
|
||||
|
||||
/* Does the ZIM file has a fulltext index */
|
||||
bool Reader::hasFulltextIndex() const
|
||||
{
|
||||
return zimArchive->hasFulltextIndex();
|
||||
}
|
||||
|
||||
/* Search titles by prefix */
|
||||
|
||||
bool Reader::searchSuggestions(const string& prefix,
|
||||
unsigned int suggestionsCount,
|
||||
const bool reset)
|
||||
{
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
|
||||
auto ret = searchSuggestions(prefix, suggestionsCount, this->suggestions);
|
||||
|
||||
/* Set the cursor to the begining */
|
||||
this->suggestionsOffset = this->suggestions.begin();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
bool Reader::searchSuggestions(const string& prefix,
|
||||
unsigned int suggestionsCount,
|
||||
SuggestionsList_t& results)
|
||||
{
|
||||
bool retVal = false;
|
||||
|
||||
/* Return if no prefix */
|
||||
if (prefix.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto& entry: zimArchive->findByTitle(prefix)) {
|
||||
if (results.size() >= suggestionsCount) {
|
||||
break;
|
||||
}
|
||||
/* Extract the interesting part of article title & url */
|
||||
std::string normalizedArticleTitle
|
||||
= kiwix::normalize(entry.getTitle());
|
||||
|
||||
// Get the final path.
|
||||
auto item = entry.getItem(true);
|
||||
std::string articleFinalUrl = item.getPath();
|
||||
|
||||
/* 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<SuggestionItem>::iterator suggestionItr;
|
||||
for (suggestionItr = results.begin();
|
||||
suggestionItr != results.end();
|
||||
suggestionItr++) {
|
||||
int result = normalizedArticleTitle.compare((*suggestionItr).getNormalizedTitle());
|
||||
if (result == 0 && articleFinalUrl.compare((*suggestionItr).getPath()) == 0) {
|
||||
insert = false;
|
||||
break;
|
||||
} else if (result < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Insert if possible */
|
||||
if (insert) {
|
||||
SuggestionItem suggestion(entry.getTitle(), normalizedArticleTitle, articleFinalUrl);
|
||||
results.insert(suggestionItr, suggestion);
|
||||
}
|
||||
|
||||
/* Suggestions where found */
|
||||
retVal = true;
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
std::vector<std::string> Reader::getTitleVariants(
|
||||
const std::string& title) const
|
||||
{
|
||||
return kiwix::getTitleVariants(title);
|
||||
}
|
||||
|
||||
|
||||
bool Reader::searchSuggestionsSmart(const string& prefix,
|
||||
unsigned int suggestionsCount)
|
||||
{
|
||||
this->suggestions.clear();
|
||||
this->suggestionsOffset = this->suggestions.begin();
|
||||
|
||||
auto ret = searchSuggestionsSmart(prefix, suggestionsCount, this->suggestions);
|
||||
|
||||
this->suggestionsOffset = this->suggestions.begin();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Try also a few variations of the prefix to have better results */
|
||||
bool Reader::searchSuggestionsSmart(const string& prefix,
|
||||
unsigned int suggestionsCount,
|
||||
SuggestionsList_t& results)
|
||||
{
|
||||
std::vector<std::string> variants = this->getTitleVariants(prefix);
|
||||
|
||||
auto suggestionSearcher = zim::SuggestionSearcher(*zimArchive);
|
||||
|
||||
if (zimArchive->hasTitleIndex()) {
|
||||
auto suggestionSearch = suggestionSearcher.suggest(prefix);
|
||||
const auto suggestions = suggestionSearch.getResults(0, suggestionsCount);
|
||||
for (auto current : suggestions) {
|
||||
SuggestionItem suggestion(current.getTitle(), kiwix::normalize(current.getTitle()),
|
||||
current.getPath(), current.getSnippet());
|
||||
results.push_back(suggestion);
|
||||
}
|
||||
} else {
|
||||
// Check some of the variants of the prefix
|
||||
for (std::vector<std::string>::iterator variantsItr = variants.begin();
|
||||
variantsItr != variants.end();
|
||||
variantsItr++) {
|
||||
auto suggestionSearch = suggestionSearcher.suggest(*variantsItr);
|
||||
for (auto current : suggestionSearch.getResults(0, suggestionsCount)) {
|
||||
if (results.size() >= suggestionsCount) {
|
||||
break;
|
||||
}
|
||||
|
||||
SuggestionItem suggestion(current.getTitle(), kiwix::normalize(current.getTitle()),
|
||||
current.getPath(), current.getSnippet());
|
||||
results.push_back(suggestion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results.size() > 0;
|
||||
}
|
||||
|
||||
/* Get next suggestion */
|
||||
bool Reader::getNextSuggestion(string& title)
|
||||
{
|
||||
if (this->suggestionsOffset != this->suggestions.end()) {
|
||||
/* title */
|
||||
title = (*(this->suggestionsOffset)).getTitle();
|
||||
|
||||
/* 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)).getTitle();
|
||||
url = (*(this->suggestionsOffset)).getPath();
|
||||
|
||||
/* 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 zimArchive->hasChecksum();
|
||||
}
|
||||
|
||||
/* Return true if corrupted, false otherwise */
|
||||
bool Reader::isCorrupted() const
|
||||
{
|
||||
try {
|
||||
if (zimArchive->check() == 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
|
||||
{
|
||||
return kiwix::getArchiveFileSize(*zimArchive);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,36 +21,33 @@
|
||||
#include <cmath>
|
||||
|
||||
#include "search_renderer.h"
|
||||
#include "searcher.h"
|
||||
#include "reader.h"
|
||||
#include "library.h"
|
||||
#include "name_mapper.h"
|
||||
|
||||
#include "tools/archiveTools.h"
|
||||
|
||||
#include <zim/search.h>
|
||||
|
||||
#include <mustache.hpp>
|
||||
#include "kiwixlib-resources.h"
|
||||
#include "libkiwix-resources.h"
|
||||
#include "tools/stringTools.h"
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
/* Constructor */
|
||||
SearchRenderer::SearchRenderer(Searcher* searcher, NameMapper* mapper)
|
||||
: m_srs(searcher->getSearchResultSet()),
|
||||
mp_nameMapper(mapper),
|
||||
protocolPrefix("zim://"),
|
||||
searchProtocolPrefix("search://?"),
|
||||
estimatedResultCount(searcher->getEstimatedResultCount()),
|
||||
resultStart(searcher->getResultStart())
|
||||
SearchRenderer::SearchRenderer(zim::SearchResultSet srs, NameMapper* mapper,
|
||||
unsigned int start, unsigned int estimatedResultCount)
|
||||
: SearchRenderer(srs, mapper, nullptr, start, estimatedResultCount)
|
||||
{}
|
||||
|
||||
SearchRenderer::SearchRenderer(zim::SearchResultSet srs, NameMapper* mapper,
|
||||
SearchRenderer::SearchRenderer(zim::SearchResultSet srs, NameMapper* mapper, Library* library,
|
||||
unsigned int start, unsigned int estimatedResultCount)
|
||||
: m_srs(srs),
|
||||
mp_nameMapper(mapper),
|
||||
mp_library(library),
|
||||
protocolPrefix("zim://"),
|
||||
searchProtocolPrefix("search://?"),
|
||||
searchProtocolPrefix("search://"),
|
||||
estimatedResultCount(estimatedResultCount),
|
||||
resultStart(start)
|
||||
{}
|
||||
@@ -60,12 +57,12 @@ SearchRenderer::~SearchRenderer() = default;
|
||||
|
||||
void SearchRenderer::setSearchPattern(const std::string& pattern)
|
||||
{
|
||||
this->searchPattern = pattern;
|
||||
searchPattern = pattern;
|
||||
}
|
||||
|
||||
void SearchRenderer::setSearchContent(const std::string& name)
|
||||
void SearchRenderer::setSearchBookQuery(const std::string& bookQuery)
|
||||
{
|
||||
this->searchContent = name;
|
||||
searchBookQuery = bookQuery;
|
||||
}
|
||||
|
||||
void SearchRenderer::setProtocolPrefix(const std::string& prefix)
|
||||
@@ -78,81 +75,162 @@ void SearchRenderer::setSearchProtocolPrefix(const std::string& prefix)
|
||||
this->searchProtocolPrefix = prefix;
|
||||
}
|
||||
|
||||
std::string SearchRenderer::getHtml()
|
||||
{
|
||||
kainjow::mustache::data results{kainjow::mustache::data::type::list};
|
||||
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 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 = mp_nameMapper->getNameForId(zim_id) + "/" + it.getPath();
|
||||
result.set("title", it.getTitle());
|
||||
result.set("url", it.getPath());
|
||||
result.set("absolutePath", absPathPrefix + urlEncode(path));
|
||||
result.set("snippet", it.getSnippet());
|
||||
std::ostringstream s;
|
||||
s << it.getZimId();
|
||||
result.set("resultContentId", mp_nameMapper->getNameForId(s.str()));
|
||||
|
||||
if (mp_library) {
|
||||
result.set("bookTitle", mp_library->getBookById(zim_id).getTitle());
|
||||
}
|
||||
if (it.getWordCount() >= 0) {
|
||||
result.set("wordCount", kiwix::beautifyInteger(it.getWordCount()));
|
||||
}
|
||||
|
||||
results.push_back(result);
|
||||
items.push_back(result);
|
||||
}
|
||||
kainjow::mustache::data results;
|
||||
results.set("items", items);
|
||||
results.set("count", kiwix::beautifyInteger(estimatedResultCount));
|
||||
results.set("hasResults", estimatedResultCount != 0);
|
||||
results.set("start", kiwix::beautifyInteger(resultStart));
|
||||
results.set("end", kiwix::beautifyInteger(std::min(resultStart+pageLength-1, estimatedResultCount)));
|
||||
|
||||
// pages
|
||||
kainjow::mustache::data pages{kainjow::mustache::data::type::list};
|
||||
// pagination
|
||||
auto pagination = buildPagination(
|
||||
pageLength,
|
||||
estimatedResultCount,
|
||||
resultStart
|
||||
);
|
||||
|
||||
auto resultEnd = 0U;
|
||||
auto currentPage = 0U;
|
||||
auto pageStart = 0U;
|
||||
auto pageEnd = 0U;
|
||||
auto lastPageStart = 0U;
|
||||
if (pageLength) {
|
||||
currentPage = resultStart/pageLength;
|
||||
pageStart = currentPage > 4 ? currentPage-4 : 0;
|
||||
pageEnd = currentPage + 5;
|
||||
if (pageEnd > estimatedResultCount / pageLength) {
|
||||
pageEnd = (estimatedResultCount + pageLength - 1) / pageLength;
|
||||
}
|
||||
if (estimatedResultCount > pageLength) {
|
||||
lastPageStart = ((estimatedResultCount-1)/pageLength)*pageLength;
|
||||
}
|
||||
}
|
||||
kainjow::mustache::data query = buildQueryData(
|
||||
searchProtocolPrefix,
|
||||
searchPattern,
|
||||
searchBookQuery
|
||||
);
|
||||
|
||||
resultEnd = resultStart+pageLength; //setting result end
|
||||
|
||||
for (unsigned int i = pageStart; i < pageEnd; i++) {
|
||||
kainjow::mustache::data page;
|
||||
page.set("label", to_string(i + 1));
|
||||
page.set("start", to_string(i * pageLength));
|
||||
|
||||
if (i == currentPage) {
|
||||
page.set("selected", true);
|
||||
}
|
||||
pages.push_back(page);
|
||||
}
|
||||
|
||||
std::string template_str = RESOURCE::templates::search_result_html;
|
||||
kainjow::mustache::mustache tmpl(template_str);
|
||||
|
||||
kainjow::mustache::data allData;
|
||||
allData.set("searchProtocolPrefix", searchProtocolPrefix);
|
||||
allData.set("results", results);
|
||||
allData.set("pages", pages);
|
||||
allData.set("hasResults", estimatedResultCount != 0);
|
||||
allData.set("hasPages", pageStart != pageEnd);
|
||||
allData.set("count", kiwix::beautifyInteger(estimatedResultCount));
|
||||
allData.set("searchPattern", kiwix::encodeDiples(this->searchPattern));
|
||||
allData.set("searchPatternEncoded", urlEncode(this->searchPattern));
|
||||
allData.set("resultStart", to_string(resultStart + 1));
|
||||
allData.set("resultEnd", to_string(min(resultEnd, estimatedResultCount)));
|
||||
allData.set("pageLength", to_string(pageLength));
|
||||
allData.set("resultLastPageStart", to_string(lastPageStart));
|
||||
allData.set("protocolPrefix", this->protocolPrefix);
|
||||
allData.set("searchProtocolPrefix", this->searchProtocolPrefix);
|
||||
allData.set("contentId", this->searchContent);
|
||||
allData.set("pagination", pagination);
|
||||
allData.set("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()
|
||||
{
|
||||
return renderTemplate(RESOURCE::templates::search_result_html);
|
||||
}
|
||||
|
||||
std::string SearchRenderer::getXml()
|
||||
{
|
||||
return renderTemplate(RESOURCE::templates::search_result_xml);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
330
src/searcher.cpp
330
src/searcher.cpp
@@ -1,330 +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 "searcher.h"
|
||||
#include "reader.h"
|
||||
|
||||
#include <zim/search.h>
|
||||
#include <zim/suggestion.h>
|
||||
|
||||
#include <mustache.hpp>
|
||||
#include <cmath>
|
||||
#include "tools/stringTools.h"
|
||||
#include "kiwixlib-resources.h"
|
||||
|
||||
#define MAX_SEARCH_LEN 140
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
class _Result : public Result
|
||||
{
|
||||
public:
|
||||
_Result(zim::SearchResultSet::iterator iterator);
|
||||
_Result(SuggestionItem suggestionItem);
|
||||
virtual ~_Result(){};
|
||||
|
||||
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 std::string get_zimId();
|
||||
|
||||
private:
|
||||
zim::SearchResultSet::iterator iterator;
|
||||
SuggestionItem suggestionItem;
|
||||
bool isSuggestion;
|
||||
};
|
||||
|
||||
struct SearcherInternal : zim::SearchResultSet {
|
||||
explicit SearcherInternal(const zim::SearchResultSet& srs)
|
||||
: zim::SearchResultSet(srs)
|
||||
, current_iterator(srs.begin())
|
||||
{
|
||||
}
|
||||
|
||||
zim::SearchResultSet::iterator current_iterator;
|
||||
};
|
||||
|
||||
struct SuggestionInternal : zim::SuggestionResultSet {
|
||||
explicit SuggestionInternal(const zim::SuggestionResultSet& srs)
|
||||
: zim::SuggestionResultSet(srs),
|
||||
currentIterator(srs.begin()) {}
|
||||
|
||||
zim::SuggestionResultSet::iterator currentIterator;
|
||||
};
|
||||
|
||||
/* Constructor */
|
||||
Searcher::Searcher()
|
||||
: searchPattern(""),
|
||||
estimatedResultCount(0),
|
||||
resultStart(0),
|
||||
maxResultCount(0)
|
||||
{
|
||||
loadICUExternalTables();
|
||||
}
|
||||
|
||||
/* Destructor */
|
||||
Searcher::~Searcher()
|
||||
{
|
||||
}
|
||||
|
||||
bool Searcher::add_reader(Reader* reader)
|
||||
{
|
||||
if (!reader->hasFulltextIndex()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for ( const Reader* const existing_reader : readers ) {
|
||||
if ( existing_reader->getZimArchive()->getUuid() == reader->getZimArchive()->getUuid() )
|
||||
return false;
|
||||
}
|
||||
|
||||
this->readers.push_back(reader);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reader* Searcher::get_reader(int readerIndex)
|
||||
{
|
||||
return readers.at(readerIndex);
|
||||
}
|
||||
|
||||
/* Search strings in the database */
|
||||
void Searcher::search(const std::string& search,
|
||||
unsigned int resultStart,
|
||||
unsigned int maxResultCount,
|
||||
const bool verbose)
|
||||
{
|
||||
this->reset();
|
||||
|
||||
if (verbose == true) {
|
||||
cout << "Performing query `" << search << "'" << endl;
|
||||
}
|
||||
|
||||
this->searchPattern = search;
|
||||
this->resultStart = resultStart;
|
||||
this->maxResultCount = maxResultCount;
|
||||
/* Try to find results */
|
||||
if (maxResultCount != 0) {
|
||||
/* Perform the search */
|
||||
string unaccentedSearch = removeAccents(search);
|
||||
std::vector<zim::Archive> archives;
|
||||
for (auto current = this->readers.begin(); current != this->readers.end();
|
||||
current++) {
|
||||
if ( (*current)->hasFulltextIndex() ) {
|
||||
archives.push_back(*(*current)->getZimArchive());
|
||||
}
|
||||
}
|
||||
zim::Searcher searcher(archives);
|
||||
searcher.setVerbose(verbose);
|
||||
zim::Query query;
|
||||
query.setQuery(unaccentedSearch);
|
||||
zim::Search search = searcher.search(query);
|
||||
internal.reset(new SearcherInternal(search.getResults(resultStart, maxResultCount)));
|
||||
this->estimatedResultCount = search.getEstimatedMatches();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
void Searcher::geo_search(float latitude, float longitude, float distance,
|
||||
unsigned int resultStart,
|
||||
unsigned int maxResultCount,
|
||||
const bool verbose)
|
||||
{
|
||||
this->reset();
|
||||
|
||||
if (verbose == true) {
|
||||
cout << "Performing geo query `" << distance << "&(" << latitude << ";" << longitude << ")'" << endl;
|
||||
}
|
||||
|
||||
/* Perform the search */
|
||||
std::ostringstream oss;
|
||||
oss << "Articles located less than " << distance << " meters of " << latitude << ";" << longitude;
|
||||
this->searchPattern = oss.str();
|
||||
this->resultStart = resultStart;
|
||||
this->maxResultCount = maxResultCount;
|
||||
|
||||
/* Try to find results */
|
||||
if (maxResultCount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<zim::Archive> archives;
|
||||
for (auto current = this->readers.begin(); current != this->readers.end();
|
||||
current++) {
|
||||
archives.push_back(*(*current)->getZimArchive());
|
||||
}
|
||||
zim::Searcher searcher(archives);
|
||||
searcher.setVerbose(verbose);
|
||||
zim::Query query;
|
||||
query.setQuery("");
|
||||
query.setGeorange(latitude, longitude, distance);
|
||||
zim::Search search = searcher.search(query);
|
||||
internal.reset(new SearcherInternal(search.getResults(resultStart, maxResultCount)));
|
||||
this->estimatedResultCount = search.getEstimatedMatches();
|
||||
}
|
||||
|
||||
|
||||
void Searcher::restart_search()
|
||||
{
|
||||
if (internal.get()) {
|
||||
internal->current_iterator = internal->begin();
|
||||
}
|
||||
}
|
||||
|
||||
Result* Searcher::getNextResult()
|
||||
{
|
||||
if (internal.get() && internal->current_iterator != internal->end()) {
|
||||
Result* result = new _Result(internal->current_iterator);
|
||||
internal->current_iterator++;
|
||||
return result;
|
||||
} else if (suggestionInternal.get() &&
|
||||
suggestionInternal->currentIterator != suggestionInternal->end()) {
|
||||
SuggestionItem item(
|
||||
suggestionInternal->currentIterator->getTitle(),
|
||||
normalize(suggestionInternal->currentIterator->getTitle()),
|
||||
suggestionInternal->currentIterator->getPath(),
|
||||
suggestionInternal->currentIterator->getSnippet()
|
||||
);
|
||||
Result* result = new _Result(item);
|
||||
suggestionInternal->currentIterator++;
|
||||
return result;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Reset the results */
|
||||
void Searcher::reset()
|
||||
{
|
||||
this->estimatedResultCount = 0;
|
||||
this->searchPattern = "";
|
||||
return;
|
||||
}
|
||||
|
||||
void Searcher::suggestions(std::string& searchPattern, const bool verbose)
|
||||
{
|
||||
this->reset();
|
||||
|
||||
if (verbose == true) {
|
||||
cout << "Performing suggestion query `" << searchPattern << "`" << endl;
|
||||
}
|
||||
|
||||
this->searchPattern = searchPattern;
|
||||
this->resultStart = 0;
|
||||
this->maxResultCount = 10;
|
||||
string unaccentedSearch = removeAccents(searchPattern);
|
||||
|
||||
// Multizim suggestion is not supported as of now! taking only one archive
|
||||
zim::Archive archive = *(*this->readers.begin())->getZimArchive();
|
||||
zim::SuggestionSearcher searcher(archive);
|
||||
searcher.setVerbose(verbose);
|
||||
zim::SuggestionSearch search = searcher.suggest(searchPattern);
|
||||
suggestionInternal.reset(new SuggestionInternal(search.getResults(resultStart, maxResultCount)));
|
||||
this->estimatedResultCount = search.getEstimatedMatches();
|
||||
}
|
||||
|
||||
/* Return the result count estimation */
|
||||
unsigned int Searcher::getEstimatedResultCount()
|
||||
{
|
||||
return this->estimatedResultCount;
|
||||
}
|
||||
|
||||
zim::SearchResultSet Searcher::getSearchResultSet()
|
||||
{
|
||||
return *(this->internal);
|
||||
}
|
||||
|
||||
_Result::_Result(zim::SearchResultSet::iterator iterator)
|
||||
: iterator(iterator),
|
||||
suggestionItem("", "", ""),
|
||||
isSuggestion(false)
|
||||
{}
|
||||
|
||||
_Result::_Result(SuggestionItem item)
|
||||
: iterator(),
|
||||
suggestionItem(item.getTitle(), item.getNormalizedTitle(), item.getPath(), item.getSnippet()),
|
||||
isSuggestion(true)
|
||||
{}
|
||||
|
||||
std::string _Result::get_url()
|
||||
{
|
||||
if (isSuggestion) {
|
||||
return suggestionItem.getPath();
|
||||
}
|
||||
return iterator.getPath();
|
||||
}
|
||||
std::string _Result::get_title()
|
||||
{
|
||||
if (isSuggestion) {
|
||||
return suggestionItem.getTitle();
|
||||
}
|
||||
return iterator.getTitle();
|
||||
}
|
||||
int _Result::get_score()
|
||||
{
|
||||
if (isSuggestion) {
|
||||
return 0;
|
||||
}
|
||||
return iterator.getScore();
|
||||
}
|
||||
std::string _Result::get_snippet()
|
||||
{
|
||||
if (isSuggestion) {
|
||||
return suggestionItem.getSnippet();
|
||||
}
|
||||
return iterator.getSnippet();
|
||||
}
|
||||
std::string _Result::get_content()
|
||||
{
|
||||
if (isSuggestion) return "";
|
||||
return iterator->getItem(true).getData();
|
||||
}
|
||||
int _Result::get_size()
|
||||
{
|
||||
if (isSuggestion) {
|
||||
return 0;
|
||||
}
|
||||
return iterator.getSize();
|
||||
}
|
||||
int _Result::get_wordCount()
|
||||
{
|
||||
if (isSuggestion) {
|
||||
return 0;
|
||||
}
|
||||
return iterator.getWordCount();
|
||||
}
|
||||
std::string _Result::get_zimId()
|
||||
{
|
||||
if (isSuggestion) {
|
||||
return "";
|
||||
}
|
||||
std::ostringstream s;
|
||||
s << iterator.getZimId();
|
||||
return s.str();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <zim/item.h>
|
||||
#include "server/internalServer.h"
|
||||
|
||||
namespace kiwix {
|
||||
@@ -45,11 +46,13 @@ bool Server::start() {
|
||||
m_port,
|
||||
m_root,
|
||||
m_nbThreads,
|
||||
m_multizimSearchLimit,
|
||||
m_verbose,
|
||||
m_withTaskbar,
|
||||
m_withLibraryButton,
|
||||
m_blockExternalLinks,
|
||||
m_indexTemplateString));
|
||||
m_indexTemplateString,
|
||||
m_ipConnectionLimit));
|
||||
return mp_server->start();
|
||||
}
|
||||
|
||||
|
||||
@@ -37,11 +37,11 @@ namespace {
|
||||
// into the ETag for ETag::Option opt.
|
||||
// IMPORTANT: The characters in all_options must come in sorted order (so that
|
||||
// IMPORTANT: isValidOptionsString() works correctly).
|
||||
const char all_options[] = "cz";
|
||||
const char all_options[] = "Zz";
|
||||
|
||||
static_assert(ETag::OPTION_COUNT == sizeof(all_options) - 1, "");
|
||||
|
||||
bool isValidServerId(const std::string& s)
|
||||
bool isValidETagBody(const std::string& s)
|
||||
{
|
||||
return !s.empty() && s.find_first_of("\"/") == std::string::npos;
|
||||
}
|
||||
@@ -83,17 +83,17 @@ bool ETag::get_option(Option opt) const
|
||||
|
||||
std::string ETag::get_etag() const
|
||||
{
|
||||
if ( m_serverId.empty() )
|
||||
if ( m_body.empty() )
|
||||
return std::string();
|
||||
|
||||
return "\"" + m_serverId + "/" + m_options + "\"";
|
||||
return "\"" + m_body + "/" + m_options + "\"";
|
||||
}
|
||||
|
||||
ETag::ETag(const std::string& serverId, const std::string& options)
|
||||
ETag::ETag(const std::string& body, const std::string& options)
|
||||
{
|
||||
if ( isValidServerId(serverId) && isValidOptionsString(options) )
|
||||
if ( isValidETagBody(body) && isValidOptionsString(options) )
|
||||
{
|
||||
m_serverId = serverId;
|
||||
m_body = body;
|
||||
m_options = options;
|
||||
}
|
||||
}
|
||||
@@ -115,7 +115,7 @@ ETag ETag::parse(std::string s)
|
||||
return ETag(s.substr(0, i), s.substr(i+1));
|
||||
}
|
||||
|
||||
ETag ETag::match(const std::string& etags, const std::string& server_id)
|
||||
ETag ETag::match(const std::string& etags, const std::string& body)
|
||||
{
|
||||
std::istringstream ss(etags);
|
||||
std::string etag_str;
|
||||
@@ -125,7 +125,7 @@ ETag ETag::match(const std::string& etags, const std::string& server_id)
|
||||
etag_str.pop_back();
|
||||
|
||||
const ETag etag = parse(etag_str);
|
||||
if ( etag && etag.m_serverId == server_id )
|
||||
if ( etag && etag.m_body == body )
|
||||
return etag;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,10 +28,11 @@ namespace kiwix {
|
||||
// The ETag string used by Kiwix server (more precisely, its value inside the
|
||||
// double quotes) consists of two parts:
|
||||
//
|
||||
// 1. ServerId - The string obtained on server start up
|
||||
// 1. Body - A string uniquely identifying the object or state from which
|
||||
// the resource has been obtained.
|
||||
//
|
||||
// 2. Options - Zero or more characters encoding the values of some of the
|
||||
// headers of the response
|
||||
// 2. Options - Zero or more characters encoding the type of the ETag and/or
|
||||
// the values of some of the headers of the response
|
||||
//
|
||||
// The two parts are separated with a slash (/) symbol (which is always present,
|
||||
// even when the the options part is empty). Neither portion of a Kiwix ETag
|
||||
@@ -40,7 +41,7 @@ namespace kiwix {
|
||||
//
|
||||
// "abcdefghijklmn/"
|
||||
// "1234567890/z"
|
||||
// "1234567890/cz"
|
||||
// "6f1d19d0-633f-087b-fb55-7ac324ff9baf/Zz"
|
||||
//
|
||||
// The options part of the Kiwix ETag allows to correctly set the required
|
||||
// headers when responding to a conditional If-None-Match request with a 304
|
||||
@@ -51,7 +52,7 @@ class ETag
|
||||
{
|
||||
public: // types
|
||||
enum Option {
|
||||
CACHEABLE_ENTITY,
|
||||
ZIM_CONTENT,
|
||||
COMPRESSED_CONTENT,
|
||||
OPTION_COUNT
|
||||
};
|
||||
@@ -59,10 +60,10 @@ class ETag
|
||||
public: // functions
|
||||
ETag() {}
|
||||
|
||||
void set_server_id(const std::string& id) { m_serverId = id; }
|
||||
void set_body(const std::string& s) { m_body = s; }
|
||||
void set_option(Option opt);
|
||||
|
||||
explicit operator bool() const { return !m_serverId.empty(); }
|
||||
explicit operator bool() const { return !m_body.empty(); }
|
||||
|
||||
bool get_option(Option opt) const;
|
||||
std::string get_etag() const;
|
||||
@@ -76,7 +77,7 @@ class ETag
|
||||
static ETag parse(std::string s);
|
||||
|
||||
private: // data
|
||||
std::string m_serverId;
|
||||
std::string m_body;
|
||||
std::string m_options;
|
||||
};
|
||||
|
||||
|
||||
192
src/server/i18n.cpp
Normal file
192
src/server/i18n.cpp
Normal file
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "i18n.h"
|
||||
|
||||
#include "tools/otherTools.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
const char* I18nStringTable::get(const std::string& key) const
|
||||
{
|
||||
const I18nString* const begin = entries;
|
||||
const I18nString* const end = begin + entryCount;
|
||||
const I18nString* found = std::lower_bound(begin, end, key,
|
||||
[](const I18nString& a, const std::string& k) {
|
||||
return a.key < k;
|
||||
});
|
||||
return (found == end || found->key != key) ? nullptr : found->value;
|
||||
}
|
||||
|
||||
namespace i18n
|
||||
{
|
||||
// this data is generated by the i18n resource compiler
|
||||
extern const I18nStringTable stringTables[];
|
||||
extern const size_t langCount;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
class I18nStringDB
|
||||
{
|
||||
public: // functions
|
||||
I18nStringDB() {
|
||||
for ( size_t i = 0; i < kiwix::i18n::langCount; ++i ) {
|
||||
const auto& t = kiwix::i18n::stringTables[i];
|
||||
lang2TableMap[t.lang] = &t;
|
||||
}
|
||||
enStrings = lang2TableMap.at("en");
|
||||
};
|
||||
|
||||
std::string get(const std::string& lang, const std::string& key) const {
|
||||
const char* s = getStringsFor(lang)->get(key);
|
||||
if ( s == nullptr ) {
|
||||
s = enStrings->get(key);
|
||||
if ( s == nullptr ) {
|
||||
throw std::runtime_error("Invalid message id");
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
size_t getStringCount(const std::string& lang) const {
|
||||
try {
|
||||
return lang2TableMap.at(lang)->entryCount;
|
||||
} catch(const std::out_of_range&) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private: // functions
|
||||
const I18nStringTable* getStringsFor(const std::string& lang) const {
|
||||
try {
|
||||
return lang2TableMap.at(lang);
|
||||
} catch(const std::out_of_range&) {
|
||||
return enStrings;
|
||||
}
|
||||
}
|
||||
|
||||
private: // data
|
||||
std::map<std::string, const I18nStringTable*> lang2TableMap;
|
||||
const I18nStringTable* enStrings;
|
||||
};
|
||||
|
||||
const I18nStringDB& getStringDb()
|
||||
{
|
||||
static const I18nStringDB stringDb;
|
||||
return stringDb;
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
std::string getTranslatedString(const std::string& lang, const std::string& key)
|
||||
{
|
||||
return getStringDb().get(lang, key);
|
||||
}
|
||||
|
||||
namespace i18n
|
||||
{
|
||||
|
||||
std::string expandParameterizedString(const std::string& lang,
|
||||
const std::string& key,
|
||||
const Parameters& params)
|
||||
{
|
||||
const std::string tmpl = getTranslatedString(lang, key);
|
||||
return render_template(tmpl, params);
|
||||
}
|
||||
|
||||
} // namespace i18n
|
||||
|
||||
std::string ParameterizedMessage::getText(const std::string& lang) const
|
||||
{
|
||||
return i18n::expandParameterizedString(lang, msgId, params);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
LangPreference parseSingleLanguagePreference(const std::string& s)
|
||||
{
|
||||
const size_t langStart = s.find_first_not_of(" \t\n");
|
||||
if ( langStart == std::string::npos ) {
|
||||
return {"", 0};
|
||||
}
|
||||
|
||||
const size_t langEnd = s.find(';', langStart);
|
||||
if ( langEnd == std::string::npos ) {
|
||||
return {s.substr(langStart), 1};
|
||||
}
|
||||
|
||||
const std::string lang = s.substr(langStart, langEnd - langStart);
|
||||
// We don't care about langEnd == langStart which will result in an empty
|
||||
// language name - it will be dismissed by parseUserLanguagePreferences()
|
||||
|
||||
float q = 1.0;
|
||||
int nCharsScanned;
|
||||
if ( 1 == sscanf(s.c_str() + langEnd + 1, "q=%f%n", &q, &nCharsScanned)
|
||||
&& langEnd + 1 + nCharsScanned == s.size() ) {
|
||||
return {lang, q};
|
||||
}
|
||||
|
||||
return {"", 0};
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
UserLangPreferences parseUserLanguagePreferences(const std::string& s)
|
||||
{
|
||||
UserLangPreferences result;
|
||||
std::istringstream iss(s);
|
||||
std::string singleLangPrefStr;
|
||||
while ( std::getline(iss, singleLangPrefStr, ',') )
|
||||
{
|
||||
const auto langPref = parseSingleLanguagePreference(singleLangPrefStr);
|
||||
if ( !langPref.lang.empty() && langPref.preference > 0 ) {
|
||||
result.push_back(langPref);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string selectMostSuitableLanguage(const UserLangPreferences& prefs)
|
||||
{
|
||||
if ( prefs.empty() ) {
|
||||
return "en";
|
||||
}
|
||||
|
||||
std::string bestLangSoFar("en");
|
||||
float bestScoreSoFar = 0;
|
||||
const auto& stringDb = getStringDb();
|
||||
for ( const auto& entry : prefs ) {
|
||||
const float score = entry.preference * stringDb.getStringCount(entry.lang);
|
||||
if ( score > bestScoreSoFar ) {
|
||||
bestScoreSoFar = score;
|
||||
bestLangSoFar = entry.lang;
|
||||
}
|
||||
}
|
||||
return bestLangSoFar;
|
||||
}
|
||||
|
||||
} // namespace kiwix
|
||||
106
src/server/i18n.h
Normal file
106
src/server/i18n.h
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2022 Veloman Yunkan <veloman.yunkan@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef KIWIX_SERVER_I18N
|
||||
#define KIWIX_SERVER_I18N
|
||||
|
||||
#include <string>
|
||||
#include <mustache.hpp>
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
struct I18nString {
|
||||
const char* const key;
|
||||
const char* const value;
|
||||
};
|
||||
|
||||
struct I18nStringTable {
|
||||
const char* const lang;
|
||||
const size_t entryCount;
|
||||
const I18nString* const entries;
|
||||
|
||||
const char* get(const std::string& key) const;
|
||||
};
|
||||
|
||||
std::string getTranslatedString(const std::string& lang, const std::string& key);
|
||||
|
||||
namespace i18n
|
||||
{
|
||||
|
||||
typedef kainjow::mustache::object 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
|
||||
|
||||
struct ParameterizedMessage
|
||||
{
|
||||
public: // types
|
||||
typedef kainjow::mustache::object Parameters;
|
||||
|
||||
public: // functions
|
||||
ParameterizedMessage(const std::string& msgId, const Parameters& params)
|
||||
: msgId(msgId)
|
||||
, params(params)
|
||||
{}
|
||||
|
||||
std::string getText(const std::string& lang) const;
|
||||
|
||||
private: // data
|
||||
const std::string msgId;
|
||||
const Parameters params;
|
||||
};
|
||||
|
||||
struct LangPreference
|
||||
{
|
||||
const std::string lang;
|
||||
const float preference;
|
||||
};
|
||||
|
||||
typedef std::vector<LangPreference> UserLangPreferences;
|
||||
|
||||
UserLangPreferences parseUserLanguagePreferences(const std::string& s);
|
||||
|
||||
std::string selectMostSuitableLanguage(const UserLangPreferences& prefs);
|
||||
|
||||
} // namespace kiwix
|
||||
|
||||
#endif // KIWIX_SERVER_I18N
|
||||
File diff suppressed because it is too large
Load Diff
@@ -28,6 +28,9 @@ extern "C" {
|
||||
#include "library.h"
|
||||
#include "name_mapper.h"
|
||||
|
||||
#include <zim/search.h>
|
||||
#include <zim/suggestion.h>
|
||||
|
||||
#include <mustache.hpp>
|
||||
|
||||
#include <atomic>
|
||||
@@ -36,11 +39,55 @@ extern "C" {
|
||||
#include "server/request_context.h"
|
||||
#include "server/response.h"
|
||||
|
||||
#include "tools/concurrent_cache.h"
|
||||
|
||||
namespace kiwix {
|
||||
|
||||
typedef kainjow::mustache::data MustacheData;
|
||||
struct GeoQuery {
|
||||
GeoQuery()
|
||||
: GeoQuery(0, 0, -1)
|
||||
{}
|
||||
|
||||
class Entry;
|
||||
GeoQuery(float latitude, float longitude, float distance)
|
||||
: latitude(latitude), longitude(longitude), distance(distance)
|
||||
{}
|
||||
float latitude;
|
||||
float longitude;
|
||||
float distance;
|
||||
|
||||
explicit operator bool() const {
|
||||
return distance >= 0;
|
||||
}
|
||||
|
||||
friend bool operator<(const GeoQuery& l, const GeoQuery& r)
|
||||
{
|
||||
return std::tie(l.latitude, l.longitude, l.distance)
|
||||
< std::tie(r.latitude, r.longitude, r.distance); // keep the same order
|
||||
}
|
||||
};
|
||||
|
||||
class SearchInfo {
|
||||
public:
|
||||
SearchInfo(const std::string& pattern, GeoQuery geoQuery, const Library::BookIdSet& bookIds, const std::string& bookFilterString);
|
||||
|
||||
zim::Query getZimQuery(bool verbose) const;
|
||||
const Library::BookIdSet& getBookIds() const { return bookIds; }
|
||||
|
||||
friend bool operator<(const SearchInfo& l, const SearchInfo& r)
|
||||
{
|
||||
return std::tie(l.bookIds, l.pattern, l.geoQuery)
|
||||
< std::tie(r.bookIds, r.pattern, r.geoQuery); // keep the same order
|
||||
}
|
||||
|
||||
public: //data
|
||||
std::string pattern;
|
||||
GeoQuery geoQuery;
|
||||
Library::BookIdSet bookIds;
|
||||
std::string bookFilterQuery;
|
||||
};
|
||||
|
||||
|
||||
typedef kainjow::mustache::data MustacheData;
|
||||
class OPDSDumper;
|
||||
|
||||
class InternalServer {
|
||||
@@ -51,12 +98,14 @@ class InternalServer {
|
||||
int port,
|
||||
std::string root,
|
||||
int nbThreads,
|
||||
unsigned int multizimSearchLimit,
|
||||
bool verbose,
|
||||
bool withTaskbar,
|
||||
bool withLibraryButton,
|
||||
bool blockExternalLinks,
|
||||
std::string indexTemplateString);
|
||||
virtual ~InternalServer() = default;
|
||||
std::string indexTemplateString,
|
||||
int ipConnectionLimit);
|
||||
virtual ~InternalServer();
|
||||
|
||||
MHD_Result handlerCallback(struct MHD_Connection* connection,
|
||||
const char* url,
|
||||
@@ -68,12 +117,13 @@ class InternalServer {
|
||||
bool start();
|
||||
void stop();
|
||||
std::string getAddress() { return m_addr; }
|
||||
int getPort() { return m_port; }
|
||||
int getPort() { return m_port; }
|
||||
|
||||
private: // functions
|
||||
std::unique_ptr<Response> handle_request(const RequestContext& request);
|
||||
std::unique_ptr<Response> build_redirect(const std::string& bookName, const zim::Item& item) const;
|
||||
std::unique_ptr<Response> build_homepage(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_viewer_settings(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_skin(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_catalog(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_catalog_v2(const RequestContext& request);
|
||||
@@ -84,43 +134,60 @@ class InternalServer {
|
||||
std::unique_ptr<Response> handle_catalog_v2_languages(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_catalog_v2_illustration(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_search(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_search_request(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_suggest(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_random(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_catch(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_captured_external(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_content(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_raw(const RequestContext& request);
|
||||
std::unique_ptr<Response> handle_locally_customized_resource(const RequestContext& request);
|
||||
|
||||
std::vector<std::string> search_catalog(const RequestContext& request,
|
||||
kiwix::OPDSDumper& opdsDumper);
|
||||
|
||||
MustacheData get_default_data() const;
|
||||
|
||||
bool etag_not_needed(const RequestContext& r) const;
|
||||
ETag get_matching_if_none_match_etag(const RequestContext& request) const;
|
||||
std::pair<std::string, Library::BookIdSet> selectBooks(const RequestContext& r) const;
|
||||
SearchInfo getSearchInfo(const RequestContext& r) const;
|
||||
|
||||
bool isLocallyCustomizedResource(const std::string& url) const;
|
||||
|
||||
std::string getLibraryId() const;
|
||||
|
||||
private: // types
|
||||
class LockableSuggestionSearcher;
|
||||
typedef ConcurrentCache<SearchInfo, std::shared_ptr<zim::Search>> SearchCache;
|
||||
typedef ConcurrentCache<std::string, std::shared_ptr<LockableSuggestionSearcher>> SuggestionSearcherCache;
|
||||
|
||||
private: // data
|
||||
std::string m_addr;
|
||||
int m_port;
|
||||
std::string m_root;
|
||||
int m_nbThreads;
|
||||
unsigned int m_multizimSearchLimit;
|
||||
std::atomic_bool m_verbose;
|
||||
bool m_withTaskbar;
|
||||
bool m_withLibraryButton;
|
||||
bool m_blockExternalLinks;
|
||||
std::string m_indexTemplateString;
|
||||
int m_ipConnectionLimit;
|
||||
struct MHD_Daemon* mp_daemon;
|
||||
|
||||
Library* mp_library;
|
||||
NameMapper* mp_nameMapper;
|
||||
|
||||
SearchCache searchCache;
|
||||
SuggestionSearcherCache suggestionSearcherCache;
|
||||
|
||||
std::string m_server_id;
|
||||
std::string m_library_id;
|
||||
|
||||
class CustomizedResources;
|
||||
std::unique_ptr<CustomizedResources> m_customizedResources;
|
||||
|
||||
friend std::unique_ptr<Response> Response::build(const InternalServer& server);
|
||||
friend std::unique_ptr<ContentResponse> ContentResponse::build(const InternalServer& server, const std::string& content, const std::string& mimetype, bool isHomePage, bool raw);
|
||||
friend std::unique_ptr<Response> ItemResponse::build(const InternalServer& server, const RequestContext& request, const zim::Item& item, bool raw);
|
||||
friend std::unique_ptr<Response> Response::build_500(const InternalServer& server, const std::string& msg);
|
||||
|
||||
friend std::unique_ptr<ContentResponse> ContentResponse::build(const InternalServer& server, const std::string& content, const std::string& mimetype);
|
||||
friend std::unique_ptr<Response> ItemResponse::build(const InternalServer& server, const RequestContext& request, const zim::Item& item);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#include "request_context.h"
|
||||
#include "response.h"
|
||||
#include "tools/otherTools.h"
|
||||
#include "kiwixlib-resources.h"
|
||||
#include "libkiwix-resources.h"
|
||||
|
||||
#include <mustache.hpp>
|
||||
|
||||
@@ -43,7 +43,8 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2(const RequestContext
|
||||
try {
|
||||
url = request.get_url_part(2);
|
||||
} catch (const std::out_of_range&) {
|
||||
return Response::build_404(*this, request.get_full_url(), "", "");
|
||||
return HTTP404Response(*this, request)
|
||||
+ urlNotFoundMsg;
|
||||
}
|
||||
|
||||
if (url == "root.xml") {
|
||||
@@ -69,23 +70,25 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2(const RequestContext
|
||||
} else if (url == "illustration") {
|
||||
return handle_catalog_v2_illustration(request);
|
||||
} else {
|
||||
return Response::build_404(*this, request.get_full_url(), "", "");
|
||||
return HTTP404Response(*this, request)
|
||||
+ urlNotFoundMsg;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Response> InternalServer::handle_catalog_v2_root(const RequestContext& request)
|
||||
{
|
||||
const std::string libraryId = getLibraryId();
|
||||
return ContentResponse::build(
|
||||
*this,
|
||||
RESOURCE::templates::catalog_v2_root_xml,
|
||||
kainjow::mustache::object{
|
||||
{"date", gen_date_str()},
|
||||
{"endpoint_root", m_root + "/catalog/v2"},
|
||||
{"feed_id", gen_uuid(m_library_id)},
|
||||
{"all_entries_feed_id", gen_uuid(m_library_id + "/entries")},
|
||||
{"partial_entries_feed_id", gen_uuid(m_library_id + "/partial_entries")},
|
||||
{"category_list_feed_id", gen_uuid(m_library_id + "/categories")},
|
||||
{"language_list_feed_id", gen_uuid(m_library_id + "/languages")}
|
||||
{"feed_id", gen_uuid(libraryId)},
|
||||
{"all_entries_feed_id", gen_uuid(libraryId + "/entries")},
|
||||
{"partial_entries_feed_id", gen_uuid(libraryId + "/partial_entries")},
|
||||
{"category_list_feed_id", gen_uuid(libraryId + "/categories")},
|
||||
{"language_list_feed_id", gen_uuid(libraryId + "/languages")}
|
||||
},
|
||||
"application/atom+xml;profile=opds-catalog;kind=navigation"
|
||||
);
|
||||
@@ -93,9 +96,9 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_root(const RequestCo
|
||||
|
||||
std::unique_ptr<Response> InternalServer::handle_catalog_v2_entries(const RequestContext& request, bool partial)
|
||||
{
|
||||
OPDSDumper opdsDumper(mp_library);
|
||||
OPDSDumper opdsDumper(mp_library, mp_nameMapper);
|
||||
opdsDumper.setRootLocation(m_root);
|
||||
opdsDumper.setLibraryId(m_library_id);
|
||||
opdsDumper.setLibraryId(getLibraryId());
|
||||
const auto bookIds = search_catalog(request, opdsDumper);
|
||||
const auto opdsFeed = opdsDumper.dumpOPDSFeedV2(bookIds, request.get_query(), partial);
|
||||
return ContentResponse::build(
|
||||
@@ -110,12 +113,13 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_complete_entry(const
|
||||
try {
|
||||
mp_library->getBookById(entryId);
|
||||
} catch (const std::out_of_range&) {
|
||||
return Response::build_404(*this, request.get_full_url(), "", "");
|
||||
return HTTP404Response(*this, request)
|
||||
+ urlNotFoundMsg;
|
||||
}
|
||||
|
||||
OPDSDumper opdsDumper(mp_library);
|
||||
OPDSDumper opdsDumper(mp_library, mp_nameMapper);
|
||||
opdsDumper.setRootLocation(m_root);
|
||||
opdsDumper.setLibraryId(m_library_id);
|
||||
opdsDumper.setLibraryId(getLibraryId());
|
||||
const auto opdsFeed = opdsDumper.dumpOPDSCompleteEntry(entryId);
|
||||
return ContentResponse::build(
|
||||
*this,
|
||||
@@ -126,9 +130,9 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_complete_entry(const
|
||||
|
||||
std::unique_ptr<Response> InternalServer::handle_catalog_v2_categories(const RequestContext& request)
|
||||
{
|
||||
OPDSDumper opdsDumper(mp_library);
|
||||
OPDSDumper opdsDumper(mp_library, mp_nameMapper);
|
||||
opdsDumper.setRootLocation(m_root);
|
||||
opdsDumper.setLibraryId(m_library_id);
|
||||
opdsDumper.setLibraryId(getLibraryId());
|
||||
return ContentResponse::build(
|
||||
*this,
|
||||
opdsDumper.categoriesOPDSFeed(),
|
||||
@@ -138,9 +142,9 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_categories(const Req
|
||||
|
||||
std::unique_ptr<Response> InternalServer::handle_catalog_v2_languages(const RequestContext& request)
|
||||
{
|
||||
OPDSDumper opdsDumper(mp_library);
|
||||
OPDSDumper opdsDumper(mp_library, mp_nameMapper);
|
||||
opdsDumper.setRootLocation(m_root);
|
||||
opdsDumper.setLibraryId(m_library_id);
|
||||
opdsDumper.setLibraryId(getLibraryId());
|
||||
return ContentResponse::build(
|
||||
*this,
|
||||
opdsDumper.languagesOPDSFeed(),
|
||||
@@ -151,14 +155,18 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_languages(const Requ
|
||||
std::unique_ptr<Response> InternalServer::handle_catalog_v2_illustration(const RequestContext& request)
|
||||
{
|
||||
try {
|
||||
const auto bookName = request.get_url_part(3);
|
||||
const auto bookId = mp_nameMapper->getIdForName(bookName);
|
||||
const auto bookId = request.get_url_part(3);
|
||||
auto book = mp_library->getBookByIdThreadSafe(bookId);
|
||||
auto size = request.get_argument<unsigned int>("size");
|
||||
auto illustration = book.getIllustration(size);
|
||||
return ContentResponse::build(*this, illustration->getData(), illustration->mimeType);
|
||||
return ContentResponse::build(
|
||||
*this,
|
||||
illustration->getData(),
|
||||
illustration->mimeType
|
||||
);
|
||||
} catch(...) {
|
||||
return Response::build_404(*this, request.get_full_url(), "", "");
|
||||
return HTTP404Response(*this, request)
|
||||
+ urlNotFoundMsg;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,8 +25,10 @@
|
||||
#include <sstream>
|
||||
#include <cstdio>
|
||||
#include <atomic>
|
||||
#include <cctype>
|
||||
|
||||
#include "tools/stringTools.h"
|
||||
#include "i18n.h"
|
||||
|
||||
namespace kiwix {
|
||||
|
||||
@@ -66,29 +68,33 @@ fullURL2LocalURL(const std::string& full_url, const std::string& rootLocation)
|
||||
} // unnamed namespace
|
||||
|
||||
RequestContext::RequestContext(struct MHD_Connection* connection,
|
||||
std::string rootLocation,
|
||||
std::string _rootLocation,
|
||||
const std::string& _url,
|
||||
const std::string& _method,
|
||||
const std::string& version) :
|
||||
rootLocation(_rootLocation),
|
||||
full_url(_url),
|
||||
url(fullURL2LocalURL(_url, rootLocation)),
|
||||
url(fullURL2LocalURL(_url, _rootLocation)),
|
||||
method(str2RequestMethod(_method)),
|
||||
version(version),
|
||||
requestIndex(s_requestIndex++),
|
||||
acceptEncodingDeflate(false),
|
||||
acceptEncodingGzip(false),
|
||||
byteRange_()
|
||||
{
|
||||
MHD_get_connection_values(connection, MHD_HEADER_KIND, &RequestContext::fill_header, this);
|
||||
MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, &RequestContext::fill_argument, this);
|
||||
MHD_get_connection_values(connection, MHD_COOKIE_KIND, &RequestContext::fill_cookie, this);
|
||||
|
||||
try {
|
||||
acceptEncodingDeflate =
|
||||
(get_header(MHD_HTTP_HEADER_ACCEPT_ENCODING).find("deflate") != std::string::npos);
|
||||
acceptEncodingGzip =
|
||||
(get_header(MHD_HTTP_HEADER_ACCEPT_ENCODING).find("gzip") != std::string::npos);
|
||||
} catch (const std::out_of_range&) {}
|
||||
|
||||
try {
|
||||
byteRange_ = ByteRange::parse(get_header(MHD_HTTP_HEADER_RANGE));
|
||||
} catch (const std::out_of_range&) {}
|
||||
|
||||
userlang = determine_user_language();
|
||||
}
|
||||
|
||||
RequestContext::~RequestContext()
|
||||
@@ -106,7 +112,23 @@ MHD_Result RequestContext::fill_argument(void *__this, enum MHD_ValueKind kind,
|
||||
const char *key, const char* value)
|
||||
{
|
||||
RequestContext *_this = static_cast<RequestContext*>(__this);
|
||||
_this->arguments[key] = value == nullptr ? "" : value;
|
||||
_this->arguments[key].push_back(value == nullptr ? "" : value);
|
||||
if ( ! _this->queryString.empty() ) {
|
||||
_this->queryString += "&";
|
||||
}
|
||||
_this->queryString += urlEncode(key);
|
||||
if ( value ) {
|
||||
_this->queryString += "=";
|
||||
_this->queryString += urlEncode(value);
|
||||
}
|
||||
return MHD_YES;
|
||||
}
|
||||
|
||||
MHD_Result RequestContext::fill_cookie(void *__this, enum MHD_ValueKind kind,
|
||||
const char *key, const char* value)
|
||||
{
|
||||
RequestContext *_this = static_cast<RequestContext*>(__this);
|
||||
_this->cookies[key] = value == nullptr ? "" : value;
|
||||
return MHD_YES;
|
||||
}
|
||||
|
||||
@@ -121,13 +143,19 @@ void RequestContext::print_debug_info() const {
|
||||
printf(" - %s : '%s'\n", it->first.c_str(), it->second.c_str());
|
||||
}
|
||||
printf("arguments :\n");
|
||||
for (auto it=arguments.begin(); it!=arguments.end(); it++) {
|
||||
printf(" - %s : '%s'\n", it->first.c_str(), it->second.c_str());
|
||||
for (auto& pair:arguments) {
|
||||
printf(" - %s :", pair.first.c_str());
|
||||
bool first = true;
|
||||
for (auto& v: pair.second) {
|
||||
printf("%s %s", first?"":",", v.c_str());
|
||||
first = false;
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
printf("Parsed : \n");
|
||||
printf("full_url: %s\n", full_url.c_str());
|
||||
printf("url : %s\n", url.c_str());
|
||||
printf("acceptEncodingDeflate : %d\n", acceptEncodingDeflate);
|
||||
printf("acceptEncodingGzip : %d\n", acceptEncodingGzip);
|
||||
printf("has_range : %d\n", byteRange_.kind() != ByteRange::NONE);
|
||||
printf("is_valid_url : %d\n", is_valid_url());
|
||||
printf(".............\n");
|
||||
@@ -166,6 +194,10 @@ std::string RequestContext::get_full_url() const {
|
||||
return full_url;
|
||||
}
|
||||
|
||||
std::string RequestContext::get_root_path() const {
|
||||
return rootLocation.empty() ? "/" : rootLocation;
|
||||
}
|
||||
|
||||
bool RequestContext::is_valid_url() const {
|
||||
return !url.empty();
|
||||
}
|
||||
@@ -176,21 +208,46 @@ ByteRange RequestContext::get_range() const {
|
||||
|
||||
template<>
|
||||
std::string RequestContext::get_argument(const std::string& name) const {
|
||||
return arguments.at(name);
|
||||
return arguments.at(name)[0];
|
||||
}
|
||||
|
||||
std::string RequestContext::get_header(const std::string& name) const {
|
||||
return headers.at(lcAll(name));
|
||||
}
|
||||
|
||||
std::string RequestContext::get_query() const {
|
||||
std::string q;
|
||||
const char* sep = "";
|
||||
for ( const auto& a : arguments ) {
|
||||
q += sep + a.first + '=' + a.second;
|
||||
sep = "&";
|
||||
}
|
||||
return q;
|
||||
std::string RequestContext::get_user_language() const
|
||||
{
|
||||
return userlang.lang;
|
||||
}
|
||||
|
||||
bool RequestContext::user_language_comes_from_cookie() const
|
||||
{
|
||||
return userlang.selectedBy == UserLanguage::SelectorKind::COOKIE;
|
||||
}
|
||||
|
||||
RequestContext::UserLanguage RequestContext::determine_user_language() const
|
||||
{
|
||||
try {
|
||||
return {UserLanguage::SelectorKind::QUERY_PARAM, get_argument("userlang")};
|
||||
} catch(const std::out_of_range&) {}
|
||||
|
||||
try {
|
||||
return {UserLanguage::SelectorKind::COOKIE, cookies.at("userlang")};
|
||||
} catch(const std::out_of_range&) {}
|
||||
|
||||
try {
|
||||
const std::string acceptLanguage = get_header("Accept-Language");
|
||||
const auto userLangPrefs = parseUserLanguagePreferences(acceptLanguage);
|
||||
const auto lang = selectMostSuitableLanguage(userLangPrefs);
|
||||
return {UserLanguage::SelectorKind::ACCEPT_LANGUAGE_HEADER, lang};
|
||||
} catch(const std::out_of_range&) {}
|
||||
|
||||
return {UserLanguage::SelectorKind::DEFAULT, "en"};
|
||||
}
|
||||
|
||||
std::string RequestContext::get_requested_format() const
|
||||
{
|
||||
return get_optional_param<std::string>("format", "html");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,9 +25,11 @@
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "byte_range.h"
|
||||
#include "tools/stringTools.h"
|
||||
|
||||
extern "C" {
|
||||
#include "microhttpd_wrapper.h"
|
||||
@@ -68,10 +70,11 @@ class RequestContext {
|
||||
std::string get_header(const std::string& name) const;
|
||||
template<typename T=std::string>
|
||||
T get_argument(const std::string& name) const {
|
||||
std::istringstream stream(arguments.at(name));
|
||||
T v;
|
||||
stream >> v;
|
||||
return v;
|
||||
return extractFromString<T>(get_argument(name));
|
||||
}
|
||||
|
||||
std::vector<std::string> get_arguments(const std::string& name) const {
|
||||
return arguments.at(name);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
@@ -88,27 +91,72 @@ class RequestContext {
|
||||
std::string get_url() const;
|
||||
std::string get_url_part(int part) const;
|
||||
std::string get_full_url() const;
|
||||
std::string get_query() const;
|
||||
std::string get_root_path() const;
|
||||
|
||||
std::string get_query() const { return queryString; }
|
||||
|
||||
template<class F>
|
||||
std::string get_query(F filter) const {
|
||||
std::string q;
|
||||
const char* sep = "";
|
||||
for ( const auto& a : arguments ) {
|
||||
if (!filter(a.first)) {
|
||||
continue;
|
||||
}
|
||||
for (const auto& v: a.second) {
|
||||
q += sep + urlEncode(a.first) + '=' + urlEncode(v);
|
||||
sep = "&";
|
||||
}
|
||||
}
|
||||
return q;
|
||||
}
|
||||
|
||||
ByteRange get_range() const;
|
||||
|
||||
bool can_compress() const { return acceptEncodingDeflate; }
|
||||
bool can_compress() const { return acceptEncodingGzip; }
|
||||
|
||||
std::string get_user_language() const;
|
||||
std::string get_requested_format() const;
|
||||
|
||||
bool user_language_comes_from_cookie() const;
|
||||
|
||||
private: // types
|
||||
struct UserLanguage
|
||||
{
|
||||
enum SelectorKind
|
||||
{
|
||||
QUERY_PARAM,
|
||||
COOKIE,
|
||||
ACCEPT_LANGUAGE_HEADER,
|
||||
DEFAULT
|
||||
};
|
||||
|
||||
SelectorKind selectedBy;
|
||||
std::string lang;
|
||||
};
|
||||
|
||||
private: // data
|
||||
std::string rootLocation;
|
||||
std::string full_url;
|
||||
std::string url;
|
||||
RequestMethod method;
|
||||
std::string version;
|
||||
unsigned long long requestIndex;
|
||||
|
||||
bool acceptEncodingDeflate;
|
||||
bool acceptEncodingGzip;
|
||||
|
||||
ByteRange byteRange_;
|
||||
std::map<std::string, std::string> headers;
|
||||
std::map<std::string, std::string> arguments;
|
||||
std::map<std::string, std::vector<std::string>> arguments;
|
||||
std::map<std::string, std::string> cookies;
|
||||
std::string queryString;
|
||||
UserLanguage userlang;
|
||||
|
||||
private: // functions
|
||||
UserLanguage determine_user_language() const;
|
||||
|
||||
static MHD_Result fill_header(void *, enum MHD_ValueKind, const char*, const char*);
|
||||
static MHD_Result fill_cookie(void *, enum MHD_ValueKind, const char*, const char*);
|
||||
static MHD_Result fill_argument(void *, enum MHD_ValueKind, const char*, const char*);
|
||||
};
|
||||
|
||||
|
||||
@@ -20,18 +20,28 @@
|
||||
#include "response.h"
|
||||
#include "request_context.h"
|
||||
#include "internalServer.h"
|
||||
#include "kiwixlib-resources.h"
|
||||
#include "libkiwix-resources.h"
|
||||
|
||||
#include "tools/regexTools.h"
|
||||
#include "tools/stringTools.h"
|
||||
#include "tools/otherTools.h"
|
||||
#include "tools/archiveTools.h"
|
||||
|
||||
#include "string.h"
|
||||
#include <mustache.hpp>
|
||||
#include <zlib.h>
|
||||
|
||||
#include <array>
|
||||
|
||||
#define KIWIX_MIN_CONTENT_SIZE_TO_DEFLATE 100
|
||||
// This is somehow a magic value.
|
||||
// If this value is too small, we will compress (and lost cpu time) too much
|
||||
// content.
|
||||
// If this value is too big, we will not compress enough content and send too
|
||||
// much data.
|
||||
// If we assume that MTU is 1500 Bytes it is useless to compress
|
||||
// content smaller as the content will be sent in one packet anyway.
|
||||
// 1400 Bytes seems to be a common accepted limit.
|
||||
#define KIWIX_MIN_CONTENT_SIZE_TO_COMPRESS 1400
|
||||
|
||||
namespace kiwix {
|
||||
|
||||
@@ -43,20 +53,69 @@ std::string get_mime_type(const zim::Item& item)
|
||||
{
|
||||
try {
|
||||
return item.getMimetype();
|
||||
} catch (exception& e) {
|
||||
} catch (std::exception& e) {
|
||||
return "application/octet-stream";
|
||||
}
|
||||
}
|
||||
|
||||
bool is_compressible_mime_type(const std::string& mimeType)
|
||||
{
|
||||
return mimeType.find("text/") != string::npos
|
||||
|| mimeType.find("application/javascript") != string::npos
|
||||
|| mimeType.find("application/atom") != string::npos
|
||||
|| mimeType.find("application/opensearchdescription") != string::npos
|
||||
|| mimeType.find("application/json") != string::npos;
|
||||
return mimeType.find("text/") != std::string::npos
|
||||
|| mimeType.find("application/javascript") != std::string::npos
|
||||
|| mimeType.find("application/atom") != std::string::npos
|
||||
|| mimeType.find("application/opensearchdescription") != std::string::npos
|
||||
|| mimeType.find("application/json") != std::string::npos
|
||||
|
||||
// Web fonts
|
||||
|| mimeType.find("application/font-") != std::string::npos
|
||||
|| mimeType.find("application/x-font-") != std::string::npos
|
||||
|| mimeType.find("application/vnd.ms-fontobject") != std::string::npos
|
||||
|| mimeType.find("font/") != std::string::npos;
|
||||
}
|
||||
|
||||
bool compress(std::string &content) {
|
||||
z_stream strm;
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
|
||||
auto ret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8,
|
||||
Z_DEFAULT_STRATEGY);
|
||||
if (ret != Z_OK) { return false; }
|
||||
|
||||
strm.avail_in = static_cast<decltype(strm.avail_in)>(content.size());
|
||||
strm.next_in =
|
||||
const_cast<Bytef *>(reinterpret_cast<const Bytef *>(content.data()));
|
||||
|
||||
std::string compressed;
|
||||
|
||||
std::array<char, 16384> buff{};
|
||||
do {
|
||||
strm.avail_out = buff.size();
|
||||
strm.next_out = reinterpret_cast<Bytef *>(buff.data());
|
||||
ret = deflate(&strm, Z_FINISH);
|
||||
assert(ret != Z_STREAM_ERROR);
|
||||
compressed.append(buff.data(), buff.size() - strm.avail_out);
|
||||
} while (strm.avail_out == 0);
|
||||
|
||||
assert(ret == Z_STREAM_END);
|
||||
assert(strm.avail_in == 0);
|
||||
|
||||
content.swap(compressed);
|
||||
|
||||
deflateEnd(&strm);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
const char* getCacheControlHeader(Response::Kind k)
|
||||
{
|
||||
switch(k) {
|
||||
case Response::STATIC_RESOURCE: return "max-age=31536000, immutable";
|
||||
case Response::ZIM_CONTENT: return "max-age=3600, must-revalidate";
|
||||
default: return "max-age=0, must-revalidate";
|
||||
}
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
@@ -67,6 +126,13 @@ Response::Response(bool verbose)
|
||||
add_header(MHD_HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, "*");
|
||||
}
|
||||
|
||||
void Response::set_kind(Kind k)
|
||||
{
|
||||
m_kind = k;
|
||||
if ( k == ZIM_CONTENT )
|
||||
m_etag.set_option(ETag::ZIM_CONTENT);
|
||||
}
|
||||
|
||||
std::unique_ptr<Response> Response::build(const InternalServer& server)
|
||||
{
|
||||
return std::unique_ptr<Response>(new Response(server.m_verbose.load()));
|
||||
@@ -77,25 +143,124 @@ std::unique_ptr<Response> Response::build_304(const InternalServer& server, cons
|
||||
auto response = Response::build(server);
|
||||
response->set_code(MHD_HTTP_NOT_MODIFIED);
|
||||
response->m_etag = etag;
|
||||
if ( etag.get_option(ETag::ZIM_CONTENT) ) {
|
||||
response->set_kind(Response::ZIM_CONTENT);
|
||||
}
|
||||
if ( etag.get_option(ETag::COMPRESSED_CONTENT) ) {
|
||||
response->add_header(MHD_HTTP_HEADER_VARY, "Accept-Encoding");
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
std::unique_ptr<Response> Response::build_404(const InternalServer& server, const std::string& url, const std::string& bookName, const std::string& bookTitle, const std::string& details)
|
||||
const UrlNotFoundMsg urlNotFoundMsg;
|
||||
const InvalidUrlMsg invalidUrlMsg;
|
||||
|
||||
std::string ContentResponseBlueprint::getMessage(const std::string& msgId) const
|
||||
{
|
||||
MustacheData results;
|
||||
if ( !url.empty() ) {
|
||||
results.set("url", url);
|
||||
return getTranslatedString(m_request.get_user_language(), msgId);
|
||||
}
|
||||
|
||||
std::unique_ptr<ContentResponse> ContentResponseBlueprint::generateResponseObject() const
|
||||
{
|
||||
auto r = ContentResponse::build(m_server, m_template, m_data, m_mimeType);
|
||||
r->set_code(m_httpStatusCode);
|
||||
return r;
|
||||
}
|
||||
|
||||
HTTPErrorResponse::HTTPErrorResponse(const InternalServer& server,
|
||||
const RequestContext& request,
|
||||
int httpStatusCode,
|
||||
const std::string& pageTitleMsgId,
|
||||
const std::string& headingMsgId,
|
||||
const std::string& cssUrl)
|
||||
: ContentResponseBlueprint(&server,
|
||||
&request,
|
||||
httpStatusCode,
|
||||
request.get_requested_format() == "html" ? "text/html; charset=utf-8" : "application/xml; charset=utf-8",
|
||||
request.get_requested_format() == "html" ? RESOURCE::templates::error_html : RESOURCE::templates::error_xml)
|
||||
{
|
||||
kainjow::mustache::list emptyList;
|
||||
this->m_data = kainjow::mustache::object{
|
||||
{"CSS_URL", onlyAsNonEmptyMustacheValue(cssUrl) },
|
||||
{"PAGE_TITLE", getMessage(pageTitleMsgId)},
|
||||
{"PAGE_HEADING", getMessage(headingMsgId)},
|
||||
{"details", emptyList}
|
||||
};
|
||||
}
|
||||
|
||||
HTTP404Response::HTTP404Response(const InternalServer& server,
|
||||
const RequestContext& request)
|
||||
: HTTPErrorResponse(server,
|
||||
request,
|
||||
MHD_HTTP_NOT_FOUND,
|
||||
"404-page-title",
|
||||
"404-page-heading")
|
||||
{
|
||||
}
|
||||
|
||||
HTTPErrorResponse& HTTP404Response::operator+(UrlNotFoundMsg /*unused*/)
|
||||
{
|
||||
const std::string requestUrl = m_request.get_full_url();
|
||||
return *this + ParameterizedMessage("url-not-found", {{"url", requestUrl}});
|
||||
}
|
||||
|
||||
HTTPErrorResponse& HTTPErrorResponse::operator+(const std::string& msg)
|
||||
{
|
||||
m_data["details"].push_back({"p", msg});
|
||||
return *this;
|
||||
}
|
||||
|
||||
HTTPErrorResponse& HTTPErrorResponse::operator+(const ParameterizedMessage& details)
|
||||
{
|
||||
return *this + details.getText(m_request.get_user_language());
|
||||
}
|
||||
|
||||
HTTPErrorResponse& HTTPErrorResponse::operator+=(const ParameterizedMessage& details)
|
||||
{
|
||||
// operator+() is already a state-modifying operator (akin to operator+=)
|
||||
return *this + details;
|
||||
}
|
||||
|
||||
|
||||
HTTP400Response::HTTP400Response(const InternalServer& server,
|
||||
const RequestContext& request)
|
||||
: HTTPErrorResponse(server,
|
||||
request,
|
||||
MHD_HTTP_BAD_REQUEST,
|
||||
"400-page-title",
|
||||
"400-page-heading")
|
||||
{
|
||||
}
|
||||
|
||||
HTTPErrorResponse& HTTP400Response::operator+(InvalidUrlMsg /*unused*/)
|
||||
{
|
||||
std::string requestUrl = m_request.get_full_url();
|
||||
const auto query = m_request.get_query();
|
||||
if (!query.empty()) {
|
||||
requestUrl += "?" + encodeDiples(query);
|
||||
}
|
||||
results.set("details", details);
|
||||
kainjow::mustache::mustache msgTmpl(R"(The requested URL "{{{url}}}" is not a valid request.)");
|
||||
return *this + msgTmpl.render({"url", requestUrl});
|
||||
}
|
||||
|
||||
auto response = ContentResponse::build(server, RESOURCE::templates::_404_html, results, "text/html");
|
||||
response->set_code(MHD_HTTP_NOT_FOUND);
|
||||
response->set_taskbar(bookName, bookTitle);
|
||||
HTTP500Response::HTTP500Response(const InternalServer& server,
|
||||
const RequestContext& request)
|
||||
: HTTPErrorResponse(server,
|
||||
request,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
"500-page-title",
|
||||
"500-page-heading")
|
||||
{
|
||||
// operator+() is a state-modifying operator (akin to operator+=)
|
||||
*this + "An internal server error occured. We are sorry about that :/";
|
||||
}
|
||||
|
||||
return std::move(response);
|
||||
std::unique_ptr<ContentResponse> HTTP500Response::generateResponseObject() const
|
||||
{
|
||||
const std::string mimeType = "text/html;charset=utf-8";
|
||||
auto r = ContentResponse::build(m_server, m_template, m_data, mimeType);
|
||||
r->set_code(m_httpStatusCode);
|
||||
return r;
|
||||
}
|
||||
|
||||
std::unique_ptr<Response> Response::build_416(const InternalServer& server, size_t resourceLength)
|
||||
@@ -111,26 +276,6 @@ std::unique_ptr<Response> Response::build_416(const InternalServer& server, size
|
||||
return response;
|
||||
}
|
||||
|
||||
std::unique_ptr<Response> Response::build_500(const InternalServer& server, const std::string& msg)
|
||||
{
|
||||
MustacheData data;
|
||||
data.set("error", msg);
|
||||
auto content = render_template(RESOURCE::templates::_500_html, data);
|
||||
std::unique_ptr<Response> response (
|
||||
new ContentResponse(
|
||||
server.m_root, //root
|
||||
true, //verbose
|
||||
true, //raw
|
||||
false, //withTaskbar
|
||||
false, //withLibraryButton
|
||||
false, //blockExternalLinks
|
||||
content, //content
|
||||
"text/html" //mimetype
|
||||
));
|
||||
response->set_code(MHD_HTTP_INTERNAL_SERVER_ERROR);
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
std::unique_ptr<Response> Response::build_redirect(const InternalServer& server, const std::string& redirectUrl)
|
||||
{
|
||||
@@ -166,7 +311,7 @@ static ssize_t callback_reader_from_item(void* cls,
|
||||
{
|
||||
RunningResponse* response = static_cast<RunningResponse*>(cls);
|
||||
|
||||
size_t max_size_to_set = min<size_t>(
|
||||
size_t max_size_to_set = std::min<size_t>(
|
||||
max,
|
||||
response->item.getSize() - pos - response->range_start);
|
||||
|
||||
@@ -196,62 +341,12 @@ void print_response_info(int retCode, MHD_Response* response)
|
||||
}
|
||||
|
||||
|
||||
void ContentResponse::introduce_taskbar()
|
||||
{
|
||||
kainjow::mustache::data data;
|
||||
data.set("root", m_root);
|
||||
data.set("content", m_bookName);
|
||||
data.set("hascontent", (!m_bookName.empty() && !m_bookTitle.empty()));
|
||||
data.set("title", m_bookTitle);
|
||||
data.set("withlibrarybutton", m_withLibraryButton);
|
||||
auto head_content = render_template(RESOURCE::templates::head_taskbar_html, data);
|
||||
m_content = prependToFirstOccurence(
|
||||
m_content,
|
||||
"</head[ \\t]*>",
|
||||
head_content);
|
||||
|
||||
auto taskbar_part = render_template(RESOURCE::templates::taskbar_part_html, data);
|
||||
m_content = appendToFirstOccurence(
|
||||
m_content,
|
||||
"<body[^>]*>",
|
||||
taskbar_part);
|
||||
}
|
||||
|
||||
|
||||
void ContentResponse::inject_externallinks_blocker()
|
||||
{
|
||||
kainjow::mustache::data data;
|
||||
data.set("root", m_root);
|
||||
auto script_tag = render_template(RESOURCE::templates::external_blocker_part_html, data);
|
||||
m_content = prependToFirstOccurence(
|
||||
m_content,
|
||||
"</head[ \\t]*>",
|
||||
script_tag);
|
||||
}
|
||||
|
||||
void ContentResponse::inject_root_link(){
|
||||
m_content = prependToFirstOccurence(
|
||||
m_content,
|
||||
"</head[ \\t]*>",
|
||||
"<link type=\"root\" href=\"" + m_root + "\">");
|
||||
}
|
||||
|
||||
bool
|
||||
ContentResponse::can_compress(const RequestContext& request) const
|
||||
{
|
||||
return request.can_compress()
|
||||
&& is_compressible_mime_type(m_mimeType)
|
||||
&& (m_content.size() > KIWIX_MIN_CONTENT_SIZE_TO_DEFLATE);
|
||||
}
|
||||
|
||||
bool
|
||||
ContentResponse::contentDecorationAllowed() const
|
||||
{
|
||||
if (m_raw) {
|
||||
return false;
|
||||
}
|
||||
return (startsWith(m_mimeType, "text/html")
|
||||
&& m_mimeType.find(";raw=true") == std::string::npos);
|
||||
&& (m_content.size() > KIWIX_MIN_CONTENT_SIZE_TO_COMPRESS);
|
||||
}
|
||||
|
||||
MHD_Response*
|
||||
@@ -264,46 +359,17 @@ Response::create_mhd_response(const RequestContext& request)
|
||||
MHD_Response*
|
||||
ContentResponse::create_mhd_response(const RequestContext& request)
|
||||
{
|
||||
if (contentDecorationAllowed()) {
|
||||
inject_root_link();
|
||||
|
||||
if (m_withTaskbar) {
|
||||
introduce_taskbar();
|
||||
}
|
||||
if (m_blockExternalLinks) {
|
||||
inject_externallinks_blocker();
|
||||
}
|
||||
}
|
||||
|
||||
bool shouldCompress = can_compress(request);
|
||||
if (shouldCompress) {
|
||||
std::vector<Bytef> compr_buffer(compressBound(m_content.size()));
|
||||
uLongf comprLen = compr_buffer.capacity();
|
||||
int err = compress(&compr_buffer[0],
|
||||
&comprLen,
|
||||
(const Bytef*)(m_content.data()),
|
||||
m_content.size());
|
||||
if (err == Z_OK && comprLen > 2 && comprLen < (m_content.size() + 2)) {
|
||||
/* /!\ Internet Explorer has a bug with deflate compression.
|
||||
It can not handle the first two bytes (compression headers)
|
||||
We need to chunk them off (move the content 2bytes)
|
||||
It has no incidence on other browsers
|
||||
See http://www.subbu.org/blog/2008/03/ie7-deflate-or-not and comments */
|
||||
m_content = string((char*)&compr_buffer[2], comprLen - 2);
|
||||
m_etag.set_option(ETag::COMPRESSED_CONTENT);
|
||||
} else {
|
||||
shouldCompress = false;
|
||||
}
|
||||
}
|
||||
const bool isCompressed = can_compress(request) && compress(m_content);
|
||||
|
||||
MHD_Response* response = MHD_create_response_from_buffer(
|
||||
m_content.size(), const_cast<char*>(m_content.data()), MHD_RESPMEM_MUST_COPY);
|
||||
|
||||
if (shouldCompress) {
|
||||
if (isCompressed) {
|
||||
m_etag.set_option(ETag::COMPRESSED_CONTENT);
|
||||
MHD_add_response_header(
|
||||
response, MHD_HTTP_HEADER_VARY, "Accept-Encoding");
|
||||
MHD_add_response_header(
|
||||
response, MHD_HTTP_HEADER_CONTENT_ENCODING, "deflate");
|
||||
response, MHD_HTTP_HEADER_CONTENT_ENCODING, "gzip");
|
||||
}
|
||||
return response;
|
||||
}
|
||||
@@ -313,7 +379,7 @@ MHD_Result Response::send(const RequestContext& request, MHD_Connection* connect
|
||||
MHD_Response* response = create_mhd_response(request);
|
||||
|
||||
MHD_add_response_header(response, MHD_HTTP_HEADER_CACHE_CONTROL,
|
||||
m_etag.get_option(ETag::CACHEABLE_ENTITY) ? "max-age=2723040, public" : "no-cache, no-store, must-revalidate");
|
||||
getCacheControlHeader(m_kind));
|
||||
const std::string etag = m_etag.get_etag();
|
||||
if ( ! etag.empty() )
|
||||
MHD_add_response_header(response, MHD_HTTP_HEADER_ETAG, etag.c_str());
|
||||
@@ -321,6 +387,13 @@ MHD_Result Response::send(const RequestContext& request, MHD_Connection* connect
|
||||
MHD_add_response_header(response, p.first.c_str(), p.second.c_str());
|
||||
}
|
||||
|
||||
if ( ! request.user_language_comes_from_cookie() ) {
|
||||
const std::string cookie = "userlang=" + request.get_user_language()
|
||||
+ ";Path=" + request.get_root_path()
|
||||
+ ";Max-Age=31536000";
|
||||
MHD_add_response_header(response, MHD_HTTP_HEADER_SET_COOKIE, cookie.c_str());
|
||||
}
|
||||
|
||||
if (m_returnCode == MHD_HTTP_OK && m_byteRange.kind() == ByteRange::RESOLVED_PARTIAL_CONTENT)
|
||||
m_returnCode = MHD_HTTP_PARTIAL_CONTENT;
|
||||
|
||||
@@ -332,24 +405,11 @@ MHD_Result Response::send(const RequestContext& request, MHD_Connection* connect
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ContentResponse::set_taskbar(const std::string& bookName, const std::string& bookTitle)
|
||||
{
|
||||
m_bookName = bookName;
|
||||
m_bookTitle = bookTitle;
|
||||
}
|
||||
|
||||
|
||||
ContentResponse::ContentResponse(const std::string& root, bool verbose, bool raw, bool withTaskbar, bool withLibraryButton, bool blockExternalLinks, const std::string& content, const std::string& mimetype) :
|
||||
ContentResponse::ContentResponse(const std::string& root, bool verbose, const std::string& content, const std::string& mimetype) :
|
||||
Response(verbose),
|
||||
m_root(root),
|
||||
m_content(content),
|
||||
m_mimeType(mimetype),
|
||||
m_raw(raw),
|
||||
m_withTaskbar(withTaskbar),
|
||||
m_withLibraryButton(withLibraryButton),
|
||||
m_blockExternalLinks(blockExternalLinks),
|
||||
m_bookName(""),
|
||||
m_bookTitle("")
|
||||
m_mimeType(mimetype)
|
||||
{
|
||||
add_header(MHD_HTTP_HEADER_CONTENT_TYPE, m_mimeType);
|
||||
}
|
||||
@@ -357,17 +417,11 @@ ContentResponse::ContentResponse(const std::string& root, bool verbose, bool raw
|
||||
std::unique_ptr<ContentResponse> ContentResponse::build(
|
||||
const InternalServer& server,
|
||||
const std::string& content,
|
||||
const std::string& mimetype,
|
||||
bool isHomePage,
|
||||
bool raw)
|
||||
const std::string& mimetype)
|
||||
{
|
||||
return std::unique_ptr<ContentResponse>(new ContentResponse(
|
||||
server.m_root,
|
||||
server.m_verbose.load(),
|
||||
raw,
|
||||
server.m_withTaskbar && !isHomePage,
|
||||
server.m_withLibraryButton,
|
||||
server.m_blockExternalLinks,
|
||||
content,
|
||||
mimetype));
|
||||
}
|
||||
@@ -376,11 +430,10 @@ std::unique_ptr<ContentResponse> ContentResponse::build(
|
||||
const InternalServer& server,
|
||||
const std::string& template_str,
|
||||
kainjow::mustache::data data,
|
||||
const std::string& mimetype,
|
||||
bool isHomePage)
|
||||
const std::string& mimetype)
|
||||
{
|
||||
auto content = render_template(template_str, data);
|
||||
return ContentResponse::build(server, content, mimetype, isHomePage);
|
||||
return ContentResponse::build(server, content, mimetype);
|
||||
}
|
||||
|
||||
ItemResponse::ItemResponse(bool verbose, const zim::Item& item, const std::string& mimetype, const ByteRange& byterange) :
|
||||
@@ -389,26 +442,26 @@ ItemResponse::ItemResponse(bool verbose, const zim::Item& item, const std::strin
|
||||
m_mimeType(mimetype)
|
||||
{
|
||||
m_byteRange = byterange;
|
||||
set_cacheable();
|
||||
set_kind(Response::ZIM_CONTENT);
|
||||
add_header(MHD_HTTP_HEADER_CONTENT_TYPE, m_mimeType);
|
||||
}
|
||||
|
||||
std::unique_ptr<Response> ItemResponse::build(const InternalServer& server, const RequestContext& request, const zim::Item& item, bool raw)
|
||||
std::unique_ptr<Response> ItemResponse::build(const InternalServer& server, const RequestContext& request, const zim::Item& item)
|
||||
{
|
||||
const std::string mimetype = get_mime_type(item);
|
||||
auto byteRange = request.get_range().resolve(item.getSize());
|
||||
const bool noRange = byteRange.kind() == ByteRange::RESOLVED_FULL_CONTENT;
|
||||
if (noRange && is_compressible_mime_type(mimetype)) {
|
||||
// Return a contentResponse
|
||||
auto response = ContentResponse::build(server, item.getData(), mimetype, /*isHomePage=*/false, raw);
|
||||
response->set_cacheable();
|
||||
auto response = ContentResponse::build(server, item.getData(), mimetype);
|
||||
response->set_kind(Response::ZIM_CONTENT);
|
||||
response->m_byteRange = byteRange;
|
||||
return std::move(response);
|
||||
}
|
||||
|
||||
if (byteRange.kind() == ByteRange::RESOLVED_UNSATISFIABLE) {
|
||||
auto response = Response::build_416(server, item.getSize());
|
||||
response->set_cacheable();
|
||||
response->set_kind(Response::ZIM_CONTENT);
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,37 +26,48 @@
|
||||
|
||||
#include <mustache.hpp>
|
||||
#include "byte_range.h"
|
||||
#include "entry.h"
|
||||
#include "etag.h"
|
||||
#include "i18n.h"
|
||||
|
||||
#include <zim/item.h>
|
||||
|
||||
extern "C" {
|
||||
#include "microhttpd_wrapper.h"
|
||||
}
|
||||
|
||||
namespace zim {
|
||||
class Archive;
|
||||
} // namespace zim
|
||||
|
||||
namespace kiwix {
|
||||
|
||||
class InternalServer;
|
||||
class RequestContext;
|
||||
|
||||
class EntryResponse;
|
||||
|
||||
class Response {
|
||||
public:
|
||||
enum Kind
|
||||
{
|
||||
STATIC_RESOURCE,
|
||||
ZIM_CONTENT,
|
||||
DYNAMIC_CONTENT
|
||||
};
|
||||
|
||||
public:
|
||||
Response(bool verbose);
|
||||
virtual ~Response() = default;
|
||||
|
||||
static std::unique_ptr<Response> build(const InternalServer& server);
|
||||
static std::unique_ptr<Response> build_304(const InternalServer& server, const ETag& etag);
|
||||
static std::unique_ptr<Response> build_404(const InternalServer& server, const std::string& url, const std::string& bookName, const std::string& bookTitle, const std::string& details="");
|
||||
static std::unique_ptr<Response> build_416(const InternalServer& server, size_t resourceLength);
|
||||
static std::unique_ptr<Response> build_500(const InternalServer& server, const std::string& msg);
|
||||
static std::unique_ptr<Response> build_redirect(const InternalServer& server, const std::string& redirectUrl);
|
||||
|
||||
MHD_Result send(const RequestContext& request, MHD_Connection* connection);
|
||||
|
||||
void set_code(int code) { m_returnCode = code; }
|
||||
void set_cacheable() { m_etag.set_option(ETag::CACHEABLE_ENTITY); }
|
||||
void set_server_id(const std::string& id) { m_etag.set_server_id(id); }
|
||||
void set_kind(Kind k);
|
||||
Kind get_kind() const { return m_kind; }
|
||||
void set_etag_body(const std::string& id) { m_etag.set_body(id); }
|
||||
void add_header(const std::string& name, const std::string& value) { m_customHeaders[name] = value; }
|
||||
|
||||
int getReturnCode() const { return m_returnCode; }
|
||||
@@ -66,6 +77,7 @@ class Response {
|
||||
MHD_Response* create_error_response(const RequestContext& request) const;
|
||||
|
||||
protected: // data
|
||||
Kind m_kind = DYNAMIC_CONTENT;
|
||||
bool m_verbose;
|
||||
int m_returnCode;
|
||||
ByteRange m_byteRange;
|
||||
@@ -81,53 +93,128 @@ class ContentResponse : public Response {
|
||||
ContentResponse(
|
||||
const std::string& root,
|
||||
bool verbose,
|
||||
bool raw,
|
||||
bool withTaskbar,
|
||||
bool withLibraryButton,
|
||||
bool blockExternalLinks,
|
||||
const std::string& content,
|
||||
const std::string& mimetype);
|
||||
|
||||
static std::unique_ptr<ContentResponse> build(
|
||||
const InternalServer& server,
|
||||
const std::string& content,
|
||||
const std::string& mimetype,
|
||||
bool isHomePage = false,
|
||||
bool raw = false);
|
||||
const std::string& mimetype);
|
||||
|
||||
static std::unique_ptr<ContentResponse> build(
|
||||
const InternalServer& server,
|
||||
const std::string& template_str,
|
||||
kainjow::mustache::data data,
|
||||
const std::string& mimetype,
|
||||
bool isHomePage = false);
|
||||
|
||||
void set_taskbar(const std::string& bookName, const std::string& bookTitle);
|
||||
const std::string& mimetype);
|
||||
|
||||
private:
|
||||
MHD_Response* create_mhd_response(const RequestContext& request);
|
||||
|
||||
void introduce_taskbar();
|
||||
void inject_externallinks_blocker();
|
||||
void inject_root_link();
|
||||
bool can_compress(const RequestContext& request) const;
|
||||
bool contentDecorationAllowed() const;
|
||||
|
||||
|
||||
private:
|
||||
std::string m_root;
|
||||
std::string m_content;
|
||||
std::string m_mimeType;
|
||||
bool m_raw;
|
||||
bool m_withTaskbar;
|
||||
bool m_withLibraryButton;
|
||||
bool m_blockExternalLinks;
|
||||
std::string m_bookName;
|
||||
std::string m_bookTitle;
|
||||
};
|
||||
|
||||
class ContentResponseBlueprint
|
||||
{
|
||||
public: // functions
|
||||
ContentResponseBlueprint(const InternalServer* server,
|
||||
const RequestContext* request,
|
||||
int httpStatusCode,
|
||||
const std::string& mimeType,
|
||||
const std::string& templateStr)
|
||||
: m_server(*server)
|
||||
, m_request(*request)
|
||||
, m_httpStatusCode(httpStatusCode)
|
||||
, m_mimeType(mimeType)
|
||||
, m_template(templateStr)
|
||||
{}
|
||||
|
||||
virtual ~ContentResponseBlueprint() = default;
|
||||
|
||||
operator std::unique_ptr<ContentResponse>() const
|
||||
{
|
||||
return generateResponseObject();
|
||||
}
|
||||
|
||||
operator std::unique_ptr<Response>() const
|
||||
{
|
||||
return operator std::unique_ptr<ContentResponse>();
|
||||
}
|
||||
|
||||
|
||||
protected: // functions
|
||||
std::string getMessage(const std::string& msgId) const;
|
||||
virtual std::unique_ptr<ContentResponse> generateResponseObject() const;
|
||||
|
||||
public: //data
|
||||
const InternalServer& m_server;
|
||||
const RequestContext& m_request;
|
||||
const int m_httpStatusCode;
|
||||
const std::string m_mimeType;
|
||||
const std::string m_template;
|
||||
kainjow::mustache::data m_data;
|
||||
};
|
||||
|
||||
struct HTTPErrorResponse : ContentResponseBlueprint
|
||||
{
|
||||
HTTPErrorResponse(const InternalServer& server,
|
||||
const RequestContext& request,
|
||||
int httpStatusCode,
|
||||
const std::string& pageTitleMsgId,
|
||||
const std::string& headingMsgId,
|
||||
const std::string& cssUrl = "");
|
||||
|
||||
HTTPErrorResponse& operator+(const std::string& msg);
|
||||
HTTPErrorResponse& operator+(const ParameterizedMessage& errorDetails);
|
||||
HTTPErrorResponse& operator+=(const ParameterizedMessage& errorDetails);
|
||||
};
|
||||
|
||||
class UrlNotFoundMsg {};
|
||||
|
||||
extern const UrlNotFoundMsg urlNotFoundMsg;
|
||||
|
||||
struct HTTP404Response : HTTPErrorResponse
|
||||
{
|
||||
HTTP404Response(const InternalServer& server,
|
||||
const RequestContext& request);
|
||||
|
||||
using HTTPErrorResponse::operator+;
|
||||
HTTPErrorResponse& operator+(UrlNotFoundMsg /*unused*/);
|
||||
};
|
||||
|
||||
class InvalidUrlMsg {};
|
||||
|
||||
extern const InvalidUrlMsg invalidUrlMsg;
|
||||
|
||||
struct HTTP400Response : HTTPErrorResponse
|
||||
{
|
||||
HTTP400Response(const InternalServer& server,
|
||||
const RequestContext& request);
|
||||
|
||||
using HTTPErrorResponse::operator+;
|
||||
HTTPErrorResponse& operator+(InvalidUrlMsg /*unused*/);
|
||||
};
|
||||
|
||||
struct HTTP500Response : HTTPErrorResponse
|
||||
{
|
||||
HTTP500Response(const InternalServer& server,
|
||||
const RequestContext& request);
|
||||
|
||||
private: // overrides
|
||||
// generateResponseObject() is overriden in order to produce a minimal
|
||||
// response without any need for additional resources from the server
|
||||
std::unique_ptr<ContentResponse> generateResponseObject() const override;
|
||||
};
|
||||
|
||||
class ItemResponse : public Response {
|
||||
public:
|
||||
ItemResponse(bool verbose, const zim::Item& item, const std::string& mimetype, const ByteRange& byterange);
|
||||
static std::unique_ptr<Response> build(const InternalServer& server, const RequestContext& request, const zim::Item& item, bool raw = false);
|
||||
static std::unique_ptr<Response> build(const InternalServer& server, const RequestContext& request, const zim::Item& item);
|
||||
|
||||
private:
|
||||
MHD_Response* create_mhd_response(const RequestContext& request);
|
||||
|
||||
@@ -93,10 +93,6 @@ std::string getMetaFlavour(const zim::Archive& archive) {
|
||||
return getMetadata(archive, "Flavour");
|
||||
}
|
||||
|
||||
std::string getArchiveId(const zim::Archive& archive) {
|
||||
return (std::string) archive.getUuid();
|
||||
}
|
||||
|
||||
bool getArchiveFavicon(const zim::Archive& archive, unsigned size,
|
||||
std::string& content, std::string& mimeType){
|
||||
try {
|
||||
@@ -109,22 +105,6 @@ bool getArchiveFavicon(const zim::Archive& archive, unsigned size,
|
||||
return false;
|
||||
}
|
||||
|
||||
// should this be in libzim
|
||||
unsigned int getArchiveMediaCount(const zim::Archive& archive) {
|
||||
std::map<const std::string, unsigned int> counterMap = parseArchiveCounter(archive);
|
||||
unsigned int counter = 0;
|
||||
|
||||
for (auto &pair:counterMap) {
|
||||
if (startsWith(pair.first, "image/") ||
|
||||
startsWith(pair.first, "video/") ||
|
||||
startsWith(pair.first, "audio/")) {
|
||||
counter += pair.second;
|
||||
}
|
||||
}
|
||||
|
||||
return counter;
|
||||
}
|
||||
|
||||
unsigned int getArchiveFileSize(const zim::Archive& archive) {
|
||||
return archive.getFilesize() / 1024;
|
||||
}
|
||||
@@ -145,14 +125,4 @@ zim::Entry getEntryFromPath(const zim::Archive& archive, const std::string& path
|
||||
}
|
||||
throw zim::EntryNotFound("Cannot find entry for non empty path");
|
||||
}
|
||||
|
||||
MimeCounterType parseArchiveCounter(const zim::Archive& archive) {
|
||||
try {
|
||||
auto counterContent = archive.getMetadata("Counter");
|
||||
return parseMimetypeCounter(counterContent);
|
||||
} catch (zim::EntryNotFound& e) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
} // kiwix
|
||||
|
||||
@@ -40,20 +40,17 @@ namespace kiwix
|
||||
std::string getMetaCreator(const zim::Archive& archive);
|
||||
std::string getMetaPublisher(const zim::Archive& archive);
|
||||
std::string getMetaFlavour(const zim::Archive& archive);
|
||||
std::string getArchiveId(const zim::Archive& archive);
|
||||
|
||||
bool getArchiveFavicon(const zim::Archive& archive, unsigned size,
|
||||
std::string& content, std::string& mimeType);
|
||||
|
||||
unsigned int getArchiveMediaCount(const zim::Archive& archive);
|
||||
unsigned int getArchiveArticleCount(const zim::Archive& archive);
|
||||
unsigned int getArchiveFileSize(const zim::Archive& archive);
|
||||
|
||||
zim::Item getFinalItem(const zim::Archive& archive, const zim::Entry& entry);
|
||||
|
||||
zim::Entry getEntryFromPath(const zim::Archive& archive, const std::string& path);
|
||||
|
||||
MimeCounterType parseArchiveCounter(const zim::Archive& archive);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
208
src/tools/concurrent_cache.h
Normal file
208
src/tools/concurrent_cache.h
Normal file
@@ -0,0 +1,208 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) 2021 Matthieu Gautier <mgautier@kymeria.fr>
|
||||
* Copyright (C) 2020 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 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* is provided AS IS, WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and
|
||||
* NON-INFRINGEMENT. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ZIM_CONCURRENT_CACHE_H
|
||||
#define ZIM_CONCURRENT_CACHE_H
|
||||
|
||||
#include "lrucache.h"
|
||||
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
/**
|
||||
ConcurrentCache implements a concurrent thread-safe cache
|
||||
|
||||
Compared to kiwix::lru_cache, each access operation is slightly more expensive.
|
||||
However, different slots of the cache can be safely accessed concurrently
|
||||
with minimal blocking. Concurrent access to the same element is also
|
||||
safe, and, in case of a cache miss, will block until that element becomes
|
||||
available.
|
||||
*/
|
||||
template <typename Key, typename Value>
|
||||
class ConcurrentCache
|
||||
{
|
||||
private: // types
|
||||
typedef std::shared_future<Value> ValuePlaceholder;
|
||||
typedef lru_cache<Key, ValuePlaceholder> Impl;
|
||||
|
||||
public: // types
|
||||
explicit ConcurrentCache(size_t maxEntries)
|
||||
: impl_(maxEntries)
|
||||
{}
|
||||
|
||||
// Gets the entry corresponding to the given key. If the entry is not in the
|
||||
// cache, it is obtained by calling f() (without any arguments) and the
|
||||
// result is put into the cache.
|
||||
//
|
||||
// The cache as a whole is locked only for the duration of accessing
|
||||
// the respective slot. If, in the case of the a cache miss, the generation
|
||||
// of the missing element takes a long time, only attempts to access that
|
||||
// element will block - the rest of the cache remains open to concurrent
|
||||
// access.
|
||||
template<class F>
|
||||
Value getOrPut(const Key& key, F f)
|
||||
{
|
||||
std::promise<Value> valuePromise;
|
||||
std::unique_lock<std::mutex> l(lock_);
|
||||
const auto x = impl_.getOrPut(key, valuePromise.get_future().share());
|
||||
l.unlock();
|
||||
if ( x.miss() ) {
|
||||
try {
|
||||
valuePromise.set_value(f());
|
||||
} catch (std::exception& e) {
|
||||
drop(key);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return x.value().get();
|
||||
}
|
||||
|
||||
bool drop(const Key& key)
|
||||
{
|
||||
std::unique_lock<std::mutex> l(lock_);
|
||||
return impl_.drop(key);
|
||||
}
|
||||
|
||||
size_t setMaxSize(size_t new_size) {
|
||||
std::unique_lock<std::mutex> l(lock_);
|
||||
return impl_.setMaxSize(new_size);
|
||||
}
|
||||
|
||||
protected: // data
|
||||
Impl impl_;
|
||||
std::mutex lock_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
WeakStore represent a thread safe store (map) of weak ptr.
|
||||
It allows to store weak_ptr from shared_ptr and retrieve shared_ptr from
|
||||
potential non expired weak_ptr.
|
||||
It is not limited in size.
|
||||
*/
|
||||
template<typename Key, typename Value>
|
||||
class WeakStore {
|
||||
private: // types
|
||||
typedef std::weak_ptr<Value> WeakValue;
|
||||
|
||||
public:
|
||||
explicit WeakStore() = default;
|
||||
|
||||
std::shared_ptr<Value> get(const Key& key)
|
||||
{
|
||||
std::lock_guard<std::mutex> l(m_lock);
|
||||
auto it = m_weakMap.find(key);
|
||||
if (it != m_weakMap.end()) {
|
||||
auto shared = it->second.lock();
|
||||
if (shared) {
|
||||
return shared;
|
||||
} else {
|
||||
m_weakMap.erase(it);
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("No weak ptr");
|
||||
}
|
||||
|
||||
void add(const Key& key, std::shared_ptr<Value> shared)
|
||||
{
|
||||
std::lock_guard<std::mutex> l(m_lock);
|
||||
m_weakMap[key] = WeakValue(shared);
|
||||
}
|
||||
|
||||
|
||||
private: //data
|
||||
std::map<Key, WeakValue> m_weakMap;
|
||||
std::mutex m_lock;
|
||||
};
|
||||
|
||||
template <typename Key, typename RawValue>
|
||||
class ConcurrentCache<Key, std::shared_ptr<RawValue>>
|
||||
{
|
||||
private: // types
|
||||
typedef std::shared_ptr<RawValue> Value;
|
||||
typedef std::shared_future<Value> ValuePlaceholder;
|
||||
typedef lru_cache<Key, ValuePlaceholder> Impl;
|
||||
|
||||
public: // types
|
||||
explicit ConcurrentCache(size_t maxEntries)
|
||||
: impl_(maxEntries)
|
||||
{}
|
||||
|
||||
// Gets the entry corresponding to the given key. If the entry is not in the
|
||||
// cache, it is obtained by calling f() (without any arguments) and the
|
||||
// result is put into the cache.
|
||||
//
|
||||
// The cache as a whole is locked only for the duration of accessing
|
||||
// the respective slot. If, in the case of the a cache miss, the generation
|
||||
// of the missing element takes a long time, only attempts to access that
|
||||
// element will block - the rest of the cache remains open to concurrent
|
||||
// access.
|
||||
template<class F>
|
||||
Value getOrPut(const Key& key, F f)
|
||||
{
|
||||
std::promise<Value> valuePromise;
|
||||
std::unique_lock<std::mutex> l(lock_);
|
||||
const auto x = impl_.getOrPut(key, valuePromise.get_future().share());
|
||||
l.unlock();
|
||||
if ( x.miss() ) {
|
||||
// Try to get back the shared_ptr from the weak_ptr first.
|
||||
try {
|
||||
valuePromise.set_value(m_weakStore.get(key));
|
||||
} catch(const std::runtime_error& e) {
|
||||
try {
|
||||
const auto value = f();
|
||||
valuePromise.set_value(value);
|
||||
m_weakStore.add(key, value);
|
||||
} catch (std::exception& e) {
|
||||
drop(key);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return x.value().get();
|
||||
}
|
||||
|
||||
bool drop(const Key& key)
|
||||
{
|
||||
std::unique_lock<std::mutex> l(lock_);
|
||||
return impl_.drop(key);
|
||||
}
|
||||
|
||||
size_t setMaxSize(size_t new_size) {
|
||||
std::unique_lock<std::mutex> l(lock_);
|
||||
return impl_.setMaxSize(new_size);
|
||||
}
|
||||
|
||||
protected: // data
|
||||
std::mutex lock_;
|
||||
Impl impl_;
|
||||
WeakStore<Key, RawValue> m_weakStore;
|
||||
};
|
||||
|
||||
} // namespace kiwix
|
||||
|
||||
#endif // ZIM_CONCURRENT_CACHE_H
|
||||
|
||||
175
src/tools/lrucache.h
Normal file
175
src/tools/lrucache.h
Normal file
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyrigth (c) 2021, Matthieu Gautier <mgautier@kymeria.fr>
|
||||
* Copyright (c) 2020, Veloman Yunkan
|
||||
* Copyright (c) 2014, lamerman
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of lamerman nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* File: lrucache.hpp
|
||||
* Author: Alexander Ponomarev
|
||||
*
|
||||
* Created on June 20, 2013, 5:09 PM
|
||||
*/
|
||||
|
||||
#ifndef _LRUCACHE_HPP_INCLUDED_
|
||||
#define _LRUCACHE_HPP_INCLUDED_
|
||||
|
||||
#include <map>
|
||||
#include <list>
|
||||
#include <set>
|
||||
#include <cstddef>
|
||||
#include <stdexcept>
|
||||
#include <cassert>
|
||||
|
||||
namespace kiwix {
|
||||
|
||||
template<typename key_t, typename value_t>
|
||||
class lru_cache {
|
||||
public: // types
|
||||
typedef typename std::pair<key_t, value_t> key_value_pair_t;
|
||||
typedef typename std::list<key_value_pair_t>::iterator list_iterator_t;
|
||||
|
||||
enum AccessStatus {
|
||||
HIT, // key was found in the cache
|
||||
PUT, // key was not in the cache but was created by the getOrPut() access
|
||||
MISS // key was not in the cache; get() access failed
|
||||
};
|
||||
|
||||
class AccessResult
|
||||
{
|
||||
const AccessStatus status_;
|
||||
const value_t val_;
|
||||
public:
|
||||
AccessResult(const value_t& val, AccessStatus status)
|
||||
: status_(status), val_(val)
|
||||
{}
|
||||
AccessResult() : status_(MISS), val_() {}
|
||||
|
||||
bool hit() const { return status_ == HIT; }
|
||||
bool miss() const { return !hit(); }
|
||||
const value_t& value() const
|
||||
{
|
||||
if ( status_ == MISS )
|
||||
throw std::range_error("There is no such key in cache");
|
||||
return val_;
|
||||
}
|
||||
|
||||
operator const value_t& () const { return value(); }
|
||||
};
|
||||
|
||||
public: // functions
|
||||
explicit lru_cache(size_t max_size) :
|
||||
_max_size(max_size) {
|
||||
}
|
||||
|
||||
// If 'key' is present in the cache, returns the associated value,
|
||||
// otherwise puts the given value into the cache (and returns it with
|
||||
// a status of a cache miss).
|
||||
AccessResult getOrPut(const key_t& key, const value_t& value) {
|
||||
auto it = _cache_items_map.find(key);
|
||||
if (it != _cache_items_map.end()) {
|
||||
_cache_items_list.splice(_cache_items_list.begin(), _cache_items_list, it->second);
|
||||
return AccessResult(it->second->second, HIT);
|
||||
} else {
|
||||
putMissing(key, value);
|
||||
return AccessResult(value, PUT);
|
||||
}
|
||||
}
|
||||
|
||||
void put(const key_t& key, const value_t& value) {
|
||||
auto it = _cache_items_map.find(key);
|
||||
if (it != _cache_items_map.end()) {
|
||||
_cache_items_list.splice(_cache_items_list.begin(), _cache_items_list, it->second);
|
||||
it->second->second = value;
|
||||
} else {
|
||||
putMissing(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
AccessResult get(const key_t& key) {
|
||||
auto it = _cache_items_map.find(key);
|
||||
if (it == _cache_items_map.end()) {
|
||||
return AccessResult();
|
||||
} else {
|
||||
_cache_items_list.splice(_cache_items_list.begin(), _cache_items_list, it->second);
|
||||
return AccessResult(it->second->second, HIT);
|
||||
}
|
||||
}
|
||||
|
||||
bool drop(const key_t& key) {
|
||||
try {
|
||||
auto list_it = _cache_items_map.at(key);
|
||||
_cache_items_list.erase(list_it);
|
||||
_cache_items_map.erase(key);
|
||||
return true;
|
||||
} catch (std::out_of_range& e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool exists(const key_t& key) const {
|
||||
return _cache_items_map.find(key) != _cache_items_map.end();
|
||||
}
|
||||
|
||||
size_t size() const {
|
||||
return _cache_items_map.size();
|
||||
}
|
||||
|
||||
size_t setMaxSize(size_t new_size) {
|
||||
size_t previous = _max_size;
|
||||
_max_size = new_size;
|
||||
return previous;
|
||||
}
|
||||
|
||||
std::set<key_t> keys() const {
|
||||
std::set<key_t> keys;
|
||||
for(auto& item:_cache_items_map) {
|
||||
keys.insert(item.first);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
private: // functions
|
||||
void putMissing(const key_t& key, const value_t& value) {
|
||||
assert(_cache_items_map.find(key) == _cache_items_map.end());
|
||||
_cache_items_list.push_front(key_value_pair_t(key, value));
|
||||
_cache_items_map[key] = _cache_items_list.begin();
|
||||
while (_cache_items_map.size() > _max_size) {
|
||||
_cache_items_map.erase(_cache_items_list.back().first);
|
||||
_cache_items_list.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
private: // data
|
||||
std::list<key_value_pair_t> _cache_items_list;
|
||||
std::map<key_t, list_iterator_t> _cache_items_map;
|
||||
size_t _max_size;
|
||||
};
|
||||
|
||||
} // namespace kiwix
|
||||
|
||||
#endif /* _LRUCACHE_HPP_INCLUDED_ */
|
||||
@@ -32,12 +32,15 @@
|
||||
#endif
|
||||
|
||||
#include "tools/stringTools.h"
|
||||
#include "server/i18n.h"
|
||||
#include "libkiwix-resources.h"
|
||||
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <pugixml.hpp>
|
||||
|
||||
#include <zim/uuid.h>
|
||||
#include <zim/suggestion_iterator.h>
|
||||
|
||||
|
||||
static std::map<std::string, std::string> codeisomapping {
|
||||
@@ -288,67 +291,6 @@ bool kiwix::convertStrToBool(const std::string& value)
|
||||
throw std::domain_error(ss.str());
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// The counter metadata format is a list of item separated by a `;` :
|
||||
// item0;item1;item2
|
||||
// Each item is a "tuple" mimetype=number.
|
||||
// However, the mimetype may contains parameters:
|
||||
// text/html;raw=true;foo=bar
|
||||
// So the final format may be complex to parse:
|
||||
// key0=value0;key1;foo=bar=value1;key2=value2
|
||||
|
||||
typedef kiwix::MimeCounterType::value_type MimetypeAndCounter;
|
||||
|
||||
std::string readFullMimetypeAndCounterString(std::istream& in)
|
||||
{
|
||||
std::string mtcStr, params;
|
||||
getline(in, mtcStr, ';');
|
||||
if ( mtcStr.find('=') == std::string::npos )
|
||||
{
|
||||
do
|
||||
{
|
||||
if ( !getline(in, params, ';' ) )
|
||||
return std::string();
|
||||
mtcStr += ";" + params;
|
||||
}
|
||||
while ( std::count(params.begin(), params.end(), '=') != 2 );
|
||||
}
|
||||
return mtcStr;
|
||||
}
|
||||
|
||||
MimetypeAndCounter parseASingleMimetypeCounter(const std::string& s)
|
||||
{
|
||||
const std::string::size_type k = s.find_last_of("=");
|
||||
if ( k != std::string::npos )
|
||||
{
|
||||
const std::string mimeType = s.substr(0, k);
|
||||
std::istringstream counterSS(s.substr(k+1));
|
||||
unsigned int counter;
|
||||
if (counterSS >> counter && counterSS.eof())
|
||||
return MimetypeAndCounter{mimeType, counter};
|
||||
}
|
||||
return MimetypeAndCounter{"", 0};
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
kiwix::MimeCounterType kiwix::parseMimetypeCounter(const std::string& counterData)
|
||||
{
|
||||
kiwix::MimeCounterType counters;
|
||||
std::istringstream ss(counterData);
|
||||
|
||||
while (ss)
|
||||
{
|
||||
const std::string mtcStr = readFullMimetypeAndCounterString(ss);
|
||||
const MimetypeAndCounter mtc = parseASingleMimetypeCounter(mtcStr);
|
||||
if ( !mtc.first.empty() )
|
||||
counters.insert(mtc);
|
||||
}
|
||||
|
||||
return counters;
|
||||
}
|
||||
|
||||
std::string kiwix::gen_date_str()
|
||||
{
|
||||
auto now = std::time(0);
|
||||
@@ -370,13 +312,86 @@ std::string kiwix::gen_uuid(const std::string& s)
|
||||
return kiwix::to_string(zim::Uuid::generate(s));
|
||||
}
|
||||
|
||||
kainjow::mustache::data kiwix::onlyAsNonEmptyMustacheValue(const std::string& s)
|
||||
{
|
||||
return s.empty()
|
||||
? kainjow::mustache::data(false)
|
||||
: kainjow::mustache::data(s);
|
||||
}
|
||||
|
||||
std::string kiwix::render_template(const std::string& template_str, kainjow::mustache::data data)
|
||||
{
|
||||
kainjow::mustache::mustache tmpl(template_str);
|
||||
kainjow::mustache::data urlencode{kainjow::mustache::lambda2{
|
||||
[](const std::string& str,const kainjow::mustache::renderer& r) { return urlEncode(r(str), true); }}};
|
||||
data.set("urlencoded", urlencode);
|
||||
std::stringstream ss;
|
||||
tmpl.render(data, [&ss](const std::string& str) { ss << str; });
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
std::string escapeBackslashes(const std::string& s)
|
||||
{
|
||||
std::string es;
|
||||
es.reserve(s.size());
|
||||
for (char c : s) {
|
||||
if ( c == '\\' ) {
|
||||
es.push_back('\\');
|
||||
}
|
||||
es.push_back(c);
|
||||
}
|
||||
return es;
|
||||
}
|
||||
|
||||
std::string makeFulltextSearchSuggestion(const std::string& lang,
|
||||
const std::string& queryString)
|
||||
{
|
||||
return kiwix::i18n::expandParameterizedString(lang, "suggest-full-text-search",
|
||||
{
|
||||
{"SEARCH_TERMS", queryString}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
kiwix::Suggestions::Suggestions()
|
||||
: m_data(kainjow::mustache::data::type::list)
|
||||
{
|
||||
}
|
||||
|
||||
void kiwix::Suggestions::add(const zim::SuggestionItem& suggestion)
|
||||
{
|
||||
kainjow::mustache::data result;
|
||||
|
||||
const std::string label = suggestion.hasSnippet()
|
||||
? suggestion.getSnippet()
|
||||
: suggestion.getTitle();
|
||||
|
||||
result.set("label", escapeBackslashes(label));
|
||||
result.set("value", escapeBackslashes(suggestion.getTitle()));
|
||||
result.set("kind", "path");
|
||||
result.set("path", escapeBackslashes(suggestion.getPath()));
|
||||
result.set("first", m_data.is_empty_list());
|
||||
m_data.push_back(result);
|
||||
}
|
||||
|
||||
void kiwix::Suggestions::addFTSearchSuggestion(const std::string& uiLang,
|
||||
const std::string& queryString)
|
||||
{
|
||||
kainjow::mustache::data result;
|
||||
const std::string label = makeFulltextSearchSuggestion(uiLang, queryString);
|
||||
result.set("label", escapeBackslashes(label));
|
||||
result.set("value", escapeBackslashes(queryString + " "));
|
||||
result.set("kind", "pattern");
|
||||
result.set("first", m_data.is_empty_list());
|
||||
m_data.push_back(result);
|
||||
}
|
||||
|
||||
std::string kiwix::Suggestions::getJSON() const
|
||||
{
|
||||
kainjow::mustache::data data;
|
||||
data.set("suggestions", m_data);
|
||||
|
||||
return render_template(RESOURCE::templates::suggestion_json, data);
|
||||
}
|
||||
|
||||
@@ -23,13 +23,20 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <cstdlib>
|
||||
#include <zim/zim.h>
|
||||
#include <mustache.hpp>
|
||||
|
||||
#include "stringTools.h"
|
||||
|
||||
namespace pugi {
|
||||
class xml_node;
|
||||
}
|
||||
|
||||
namespace zim {
|
||||
class SuggestionItem;
|
||||
}
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
std::string nodeToString(const pugi::xml_node& node);
|
||||
@@ -42,13 +49,44 @@ namespace kiwix
|
||||
const std::string& tagName);
|
||||
bool convertStrToBool(const std::string& value);
|
||||
|
||||
using MimeCounterType = std::map<const std::string, zim::entry_index_type>;
|
||||
MimeCounterType parseMimetypeCounter(const std::string& counterData);
|
||||
|
||||
std::string gen_date_str();
|
||||
std::string gen_uuid(const std::string& s);
|
||||
|
||||
// if s is empty then returns kainjow::mustache::data(false)
|
||||
// otherwise kainjow::mustache::data(value)
|
||||
kainjow::mustache::data onlyAsNonEmptyMustacheValue(const std::string& s);
|
||||
|
||||
std::string render_template(const std::string& template_str, kainjow::mustache::data data);
|
||||
|
||||
template<typename T>
|
||||
T getEnvVar(const char* name, const T& defaultValue)
|
||||
{
|
||||
try {
|
||||
const char* envString = std::getenv(name);
|
||||
if (envString == nullptr) {
|
||||
throw std::runtime_error("Environment variable not set");
|
||||
}
|
||||
return extractFromString<T>(envString);
|
||||
} catch (...) {}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
class Suggestions
|
||||
{
|
||||
public:
|
||||
Suggestions();
|
||||
|
||||
void add(const zim::SuggestionItem& suggestion);
|
||||
|
||||
void addFTSearchSuggestion(const std::string& uiLang,
|
||||
const std::string& query);
|
||||
|
||||
std::string getJSON() const;
|
||||
|
||||
private:
|
||||
kainjow::mustache::data m_data;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -75,41 +75,3 @@ std::string replaceRegex(const std::string& content,
|
||||
uresult.toUTF8String(tmp);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
std::string appendToFirstOccurence(const std::string& content,
|
||||
const std::string& regex,
|
||||
const std::string& replacement)
|
||||
{
|
||||
ucnv_setDefaultName("UTF-8");
|
||||
icu::UnicodeString ucontent(content.c_str());
|
||||
icu::UnicodeString ureplacement(replacement.c_str());
|
||||
auto matcher = buildMatcher(regex, 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;
|
||||
}
|
||||
|
||||
std::string prependToFirstOccurence(const std::string& content,
|
||||
const std::string& regex,
|
||||
const std::string& replacement)
|
||||
{
|
||||
ucnv_setDefaultName("UTF-8");
|
||||
icu::UnicodeString ucontent(content.c_str());
|
||||
icu::UnicodeString ureplacement(replacement.c_str());
|
||||
auto matcher = buildMatcher(regex, ucontent);
|
||||
if (matcher->find()) {
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
ucontent.insert(matcher->start(status), ureplacement);
|
||||
std::string tmp;
|
||||
ucontent.toUTF8String(tmp);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
@@ -26,11 +26,5 @@ 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);
|
||||
std::string prependToFirstOccurence(const std::string& content,
|
||||
const std::string& regex,
|
||||
const std::string& replacement);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -49,6 +49,24 @@ void kiwix::loadICUExternalTables()
|
||||
#endif
|
||||
}
|
||||
|
||||
kiwix::ICULanguageInfo::ICULanguageInfo(const std::string& langCode)
|
||||
: locale(langCode.c_str())
|
||||
{}
|
||||
|
||||
std::string kiwix::ICULanguageInfo::iso3Code() const
|
||||
{
|
||||
return locale.getISO3Language();
|
||||
}
|
||||
|
||||
std::string kiwix::ICULanguageInfo::selfName() const
|
||||
{
|
||||
icu::UnicodeString langSelfNameICUString;
|
||||
locale.getDisplayLanguage(locale, langSelfNameICUString);
|
||||
std::string langSelfName;
|
||||
langSelfNameICUString.toUTF8String(langSelfName);
|
||||
return langSelfName;
|
||||
}
|
||||
|
||||
std::string kiwix::removeAccents(const std::string& text)
|
||||
{
|
||||
loadICUExternalTables();
|
||||
@@ -143,15 +161,14 @@ std::string kiwix::encodeDiples(const std::string& str)
|
||||
return result;
|
||||
}
|
||||
|
||||
/* urlEncode() based on javascript encodeURI() &
|
||||
encodeURIComponent(). Mostly code from rstudio/httpuv (GPLv3) */
|
||||
namespace
|
||||
{
|
||||
|
||||
bool isReservedUrlChar(char c)
|
||||
{
|
||||
switch (c) {
|
||||
case ';':
|
||||
case ',':
|
||||
case '/':
|
||||
case '?':
|
||||
case ':':
|
||||
case '@':
|
||||
@@ -159,22 +176,22 @@ bool isReservedUrlChar(char c)
|
||||
case '=':
|
||||
case '+':
|
||||
case '$':
|
||||
case '#':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool needsEscape(char c, bool encodeReserved)
|
||||
bool isHarmlessUriChar(char c)
|
||||
{
|
||||
if (c >= 'a' && c <= 'z')
|
||||
return false;
|
||||
return true;
|
||||
if (c >= 'A' && c <= 'Z')
|
||||
return false;
|
||||
return true;
|
||||
if (c >= '0' && c <= '9')
|
||||
return false;
|
||||
if (isReservedUrlChar(c))
|
||||
return encodeReserved;
|
||||
return true;
|
||||
|
||||
switch (c) {
|
||||
case '-':
|
||||
case '_':
|
||||
@@ -185,8 +202,46 @@ bool needsEscape(char c, bool encodeReserved)
|
||||
case '\'':
|
||||
case '(':
|
||||
case ')':
|
||||
return false;
|
||||
case '/':
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool mustBeUriEncodedFor(kiwix::URIComponentKind target, char c)
|
||||
{
|
||||
if (isHarmlessUriChar(c))
|
||||
return false;
|
||||
|
||||
switch (c) {
|
||||
case '/': // There is no reason to encode the path separator in the general
|
||||
// case. It must be encoded only in a path component when its
|
||||
// semantics of a path separator has to be suppressed.
|
||||
return false;
|
||||
|
||||
case '@': // In a relative URL of the form abc@def/xyz (with no / in abc)
|
||||
// a non-encoded @ will make "abc" and "def" to be interpreted as
|
||||
// username and host components, respectively
|
||||
return target == kiwix::URIComponentKind::PATH;
|
||||
|
||||
case ':': // In a relative URL of the form abc:def/xyz (with no / in abc)
|
||||
// a non-encoded : will make "abc" and "def" to be interpreted as
|
||||
// host and port components, respectively
|
||||
return target == kiwix::URIComponentKind::PATH;
|
||||
|
||||
case '?': // A non-encoded '?' acts as a separator between the path
|
||||
// and query components
|
||||
return target == kiwix::URIComponentKind::PATH;
|
||||
|
||||
case '&': return target == kiwix::URIComponentKind::QUERY;
|
||||
case '=': return target == kiwix::URIComponentKind::QUERY;
|
||||
case '+': return target == kiwix::URIComponentKind::QUERY;
|
||||
|
||||
case '#': // A non-encoded '#' in either path or query-component
|
||||
// would mark the beginning of the fragment component
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -212,23 +267,43 @@ int hexToInt(char c) {
|
||||
}
|
||||
}
|
||||
|
||||
std::string kiwix::urlEncode(const std::string& value, bool encodeReserved)
|
||||
} // unnamed namespace
|
||||
|
||||
std::string kiwix::urlEncode(const std::string& value)
|
||||
{
|
||||
std::ostringstream os;
|
||||
os << std::hex << std::uppercase;
|
||||
for (std::string::const_iterator it = value.begin();
|
||||
it != value.end();
|
||||
it++) {
|
||||
|
||||
if (!needsEscape(*it, encodeReserved)) {
|
||||
os << *it;
|
||||
for (const char c : value) {
|
||||
if (isHarmlessUriChar(c)) {
|
||||
os << c;
|
||||
} else {
|
||||
os << '%' << std::setw(2) << static_cast<unsigned int>(static_cast<unsigned char>(*it));
|
||||
const unsigned int charVal = static_cast<unsigned char>(c);
|
||||
os << '%' << std::setw(2) << std::setfill('0') << charVal;
|
||||
}
|
||||
}
|
||||
return os.str();
|
||||
}
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
std::string uriEncode(URIComponentKind target, const std::string& value)
|
||||
{
|
||||
std::ostringstream os;
|
||||
os << std::hex << std::uppercase;
|
||||
for (const char c : value) {
|
||||
if ( mustBeUriEncodedFor(target, c) ) {
|
||||
const unsigned int charVal = static_cast<unsigned char>(c);
|
||||
os << '%' << std::setw(2) << std::setfill('0') << charVal;
|
||||
} else {
|
||||
os << c;
|
||||
}
|
||||
}
|
||||
return os.str();
|
||||
}
|
||||
|
||||
} // namespace kiwix
|
||||
|
||||
std::string kiwix::urlDecode(const std::string& value, bool component)
|
||||
{
|
||||
std::ostringstream os;
|
||||
@@ -249,15 +324,15 @@ std::string kiwix::urlDecode(const std::string& value, bool component)
|
||||
int iHi = hexToInt(hi);
|
||||
int iLo = hexToInt(lo);
|
||||
if (iHi < 0 || iLo < 0) {
|
||||
// Invalid escape sequence
|
||||
os << '%' << hi << lo;
|
||||
continue;
|
||||
// Invalid escape sequence
|
||||
os << '%' << hi << lo;
|
||||
continue;
|
||||
}
|
||||
char c = (char)(iHi << 4 | iLo);
|
||||
if (!component && isReservedUrlChar(c)) {
|
||||
os << '%' << hi << lo;
|
||||
os << '%' << hi << lo;
|
||||
} else {
|
||||
os << c;
|
||||
os << c;
|
||||
}
|
||||
} else {
|
||||
os << *it;
|
||||
@@ -405,3 +480,8 @@ std::vector<std::string> kiwix::getTitleVariants(const std::string& title) {
|
||||
variants.push_back(kiwix::toTitle(title));
|
||||
return variants;
|
||||
}
|
||||
|
||||
template<>
|
||||
std::string kiwix::extractFromString(const std::string& str) {
|
||||
return str;
|
||||
}
|
||||
|
||||
@@ -21,10 +21,12 @@
|
||||
#define KIWIX_STRINGTOOLS_H
|
||||
|
||||
#include <unicode/unistr.h>
|
||||
#include <unicode/locid.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
@@ -40,9 +42,35 @@ std::string encodeDiples(const std::string& str);
|
||||
std::string removeAccents(const std::string& text);
|
||||
void loadICUExternalTables();
|
||||
|
||||
std::string urlEncode(const std::string& value, bool encodeReserved = false);
|
||||
class ICULanguageInfo
|
||||
{
|
||||
public:
|
||||
explicit ICULanguageInfo(const std::string& langCode);
|
||||
|
||||
std::string iso3Code() const;
|
||||
std::string selfName() const;
|
||||
|
||||
private:
|
||||
const icu::Locale locale;
|
||||
};
|
||||
|
||||
|
||||
/* 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);
|
||||
|
||||
// Only URI components that are of interest to libkiwix
|
||||
// are included in the below enumeration type
|
||||
enum class URIComponentKind
|
||||
{
|
||||
PATH,
|
||||
QUERY
|
||||
};
|
||||
|
||||
// Encode 'value' for usage in a URI componenet specified by 'target'
|
||||
std::string uriEncode(URIComponentKind target, const std::string& value);
|
||||
|
||||
std::string join(const std::vector<std::string>& list, const std::string& sep);
|
||||
|
||||
std::string ucAll(const std::string& word);
|
||||
@@ -65,9 +93,15 @@ 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::vector<std::string> getTitleVariants(const std::string& title);
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<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>
|
||||
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Matthieu Gautier <mgautier@kymeria.fr>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
|
||||
#include <jni.h>
|
||||
#include "org_kiwix_kiwixlib_Book.h"
|
||||
|
||||
#include "utils.h"
|
||||
#include "book.h"
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_kiwix_kiwixlib_Book_allocate(
|
||||
JNIEnv* env, jobject thisObj)
|
||||
{
|
||||
allocate<kiwix::Book>(env, thisObj);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_kiwix_kiwixlib_Book_dispose(JNIEnv* env, jobject thisObj)
|
||||
{
|
||||
dispose<kiwix::Book>(env, thisObj);
|
||||
}
|
||||
|
||||
#define BOOK (getPtr<kiwix::Book>(env, thisObj))
|
||||
|
||||
METHOD(void, Book, update__Lorg_kiwix_kiwixlib_Book_2, jobject otherBook)
|
||||
{
|
||||
BOOK->update(*getPtr<kiwix::Book>(env, otherBook));
|
||||
}
|
||||
|
||||
METHOD(void, Book, update__Lorg_kiwix_kiwixlib_JNIKiwixReader_2, jobject reader)
|
||||
{
|
||||
BOOK->update(**Handle<kiwix::Reader>::getHandle(env, reader));
|
||||
}
|
||||
|
||||
#define GETTER(retType, name) JNIEXPORT retType JNICALL \
|
||||
Java_org_kiwix_kiwixlib_Book_##name (JNIEnv* env, jobject thisObj) \
|
||||
{ \
|
||||
auto cRet = BOOK->name(); \
|
||||
retType ret = c2jni(cRet, env); \
|
||||
return ret; \
|
||||
}
|
||||
|
||||
GETTER(jstring, getId)
|
||||
GETTER(jstring, getPath)
|
||||
GETTER(jboolean, isPathValid)
|
||||
GETTER(jstring, getTitle)
|
||||
GETTER(jstring, getDescription)
|
||||
GETTER(jstring, getLanguage)
|
||||
GETTER(jstring, getCreator)
|
||||
GETTER(jstring, getPublisher)
|
||||
GETTER(jstring, getDate)
|
||||
GETTER(jstring, getUrl)
|
||||
GETTER(jstring, getName)
|
||||
GETTER(jstring, getFlavour)
|
||||
GETTER(jstring, getCategory)
|
||||
GETTER(jstring, getTags)
|
||||
GETTER(jlong, getArticleCount)
|
||||
GETTER(jlong, getMediaCount)
|
||||
GETTER(jlong, getSize)
|
||||
GETTER(jstring, getFavicon)
|
||||
GETTER(jstring, getFaviconUrl)
|
||||
GETTER(jstring, getFaviconMimeType)
|
||||
|
||||
METHOD(jstring, Book, getTagStr, jstring tagName) try {
|
||||
auto cRet = BOOK->getTagStr(jni2c(tagName, env));
|
||||
return c2jni(cRet, env);
|
||||
} catch(...) {
|
||||
return c2jni<std::string>("", env);
|
||||
}
|
||||
|
||||
#undef GETTER
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019-2020 Matthieu Gautier <mgautier@kymeria.fr>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
|
||||
#include <jni.h>
|
||||
#include "org_kiwix_kiwixlib_Filter.h"
|
||||
|
||||
#include "library.h"
|
||||
#include "utils.h"
|
||||
|
||||
/* Kiwix Reader JNI functions */
|
||||
METHOD0(void, Filter, allocate) {
|
||||
allocate<kiwix::Filter>(env, thisObj);
|
||||
}
|
||||
|
||||
METHOD0(void, Filter, dispose) {
|
||||
dispose<kiwix::Library>(env, thisObj);
|
||||
}
|
||||
|
||||
#define FILTER (getPtr<kiwix::Filter>(env, thisObj))
|
||||
|
||||
#define FORWARD(name, args_type) \
|
||||
METHOD(jobject, Filter, name, args_type value) { \
|
||||
FILTER->name(jni2c(value, env)); \
|
||||
return thisObj; \
|
||||
}
|
||||
|
||||
#define FORWARDA(name, args_type) \
|
||||
METHOD(jobject, Filter, name, jobjectArray value) { \
|
||||
FILTER->name(jni2c<args_type>(value, env)); \
|
||||
return thisObj; \
|
||||
}
|
||||
|
||||
|
||||
|
||||
FORWARD(local, jboolean)
|
||||
FORWARD(remote, jboolean)
|
||||
FORWARD(valid, jboolean)
|
||||
FORWARDA(acceptTags, jstring)
|
||||
FORWARDA(rejectTags, jstring)
|
||||
FORWARD(lang, jstring)
|
||||
FORWARD(publisher, jstring)
|
||||
FORWARD(creator, jstring)
|
||||
FORWARD(maxSize, jlong)
|
||||
FORWARD(query, jstring)
|
||||
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
* Copyright (C) 2017 Matthieu Gautier <mgautier@kymeria.fr>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <jni.h>
|
||||
#include "org_kiwix_kiwixlib_JNIICU.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "unicode/putil.h"
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
std::mutex globalLock;
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_kiwix_kiwixlib_JNIICU_setDataDirectory(
|
||||
JNIEnv* env, jclass kclass, jstring dirStr)
|
||||
{
|
||||
std::string cPath = jni2c(dirStr, env);
|
||||
|
||||
Lock l;
|
||||
try {
|
||||
u_setDataDirectory(cPath.c_str());
|
||||
} catch (...) {
|
||||
std::cerr << "Unable to set data directory " << cPath << std::endl;
|
||||
}
|
||||
}
|
||||
@@ -1,561 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
* Copyright (C) 2017 Matthieu Gautier <mgautier@kymeria.fr>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
|
||||
#include <jni.h>
|
||||
#include <exception>
|
||||
#include "org_kiwix_kiwixlib_JNIKiwixReader.h"
|
||||
|
||||
#include "tools/base64.h"
|
||||
#include "reader.h"
|
||||
#include "utils.h"
|
||||
|
||||
/* Kiwix Reader JNI functions */
|
||||
JNIEXPORT jlong JNICALL Java_org_kiwix_kiwixlib_JNIKiwixReader_getNativeReader(
|
||||
JNIEnv* env, jobject obj, jstring filename)
|
||||
{
|
||||
std::string cPath = jni2c(filename, env);
|
||||
|
||||
LOG("Attempting to create reader with: %s", cPath.c_str());
|
||||
Lock l;
|
||||
try {
|
||||
kiwix::Reader* reader = new kiwix::Reader(cPath);
|
||||
return reinterpret_cast<jlong>(new Handle<kiwix::Reader>(reader));
|
||||
} catch (std::exception& e) {
|
||||
LOG("Error opening ZIM file");
|
||||
LOG(e.what());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
int jni2fd(const jobject& fdObj, JNIEnv* env)
|
||||
{
|
||||
jclass class_fdesc = env->FindClass("java/io/FileDescriptor");
|
||||
jfieldID field_fd = env->GetFieldID(class_fdesc, "fd", "I");
|
||||
if ( field_fd == NULL )
|
||||
{
|
||||
env->ExceptionClear();
|
||||
// Under Android the (private) 'fd' field of java.io.FileDescriptor has been
|
||||
// renamed to 'descriptor'. See, for example,
|
||||
// https://android.googlesource.com/platform/libcore/+/refs/tags/android-8.1.0_r1/ojluni/src/main/java/java/io/FileDescriptor.java#55
|
||||
field_fd = env->GetFieldID(class_fdesc, "descriptor", "I");
|
||||
}
|
||||
return env->GetIntField(fdObj, field_fd);
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
JNIEXPORT jlong JNICALL Java_org_kiwix_kiwixlib_JNIKiwixReader_getNativeReaderByFD(
|
||||
JNIEnv* env, jobject obj, jobject fdObj)
|
||||
{
|
||||
#ifndef _WIN32
|
||||
int fd = jni2fd(fdObj, env);
|
||||
|
||||
LOG("Attempting to create reader with fd: %d", fd);
|
||||
Lock l;
|
||||
try {
|
||||
kiwix::Reader* reader = new kiwix::Reader(fd);
|
||||
return reinterpret_cast<jlong>(new Handle<kiwix::Reader>(reader));
|
||||
} catch (std::exception& e) {
|
||||
LOG("Error opening ZIM file");
|
||||
LOG(e.what());
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
jclass exception = env->FindClass("java/lang/UnsupportedOperationException");
|
||||
env->ThrowNew(exception, "org.kiwix.kiwixlib.JNIKiwixReader.getNativeReaderByFD() is not supported under Windows");
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL Java_org_kiwix_kiwixlib_JNIKiwixReader_getNativeReaderEmbedded(
|
||||
JNIEnv* env, jobject obj, jobject fdObj, jlong offset, jlong size)
|
||||
{
|
||||
#ifndef _WIN32
|
||||
int fd = jni2fd(fdObj, env);
|
||||
|
||||
LOG("Attempting to create reader with fd: %d", fd);
|
||||
Lock l;
|
||||
try {
|
||||
kiwix::Reader* reader = new kiwix::Reader(fd, offset, size);
|
||||
return reinterpret_cast<jlong>(new Handle<kiwix::Reader>(reader));
|
||||
} catch (std::exception& e) {
|
||||
LOG("Error opening ZIM file");
|
||||
LOG(e.what());
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
jclass exception = env->FindClass("java/lang/UnsupportedOperationException");
|
||||
env->ThrowNew(exception, "org.kiwix.kiwixlib.JNIKiwixReader.getNativeReaderEmbedded() is not supported under Windows");
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixReader_dispose(JNIEnv* env, jobject obj)
|
||||
{
|
||||
Handle<kiwix::Reader>::dispose(env, obj);
|
||||
}
|
||||
|
||||
#define READER (Handle<kiwix::Reader>::getHandle(env, obj))
|
||||
|
||||
/* Kiwix library functions */
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixReader_getMainPage(JNIEnv* env, jobject obj)
|
||||
{
|
||||
jstring url;
|
||||
|
||||
try {
|
||||
std::string cUrl = READER->getMainPage().getPath();
|
||||
url = c2jni(cUrl, env);
|
||||
} catch (std::exception& e) {
|
||||
LOG("Unable to get ZIM main page");
|
||||
LOG(e.what());
|
||||
url = NULL;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixReader_getId(JNIEnv* env, jobject obj)
|
||||
{
|
||||
jstring id;
|
||||
|
||||
try {
|
||||
std::string cId = READER->getId();
|
||||
id = c2jni(cId, env);
|
||||
} catch (std::exception& e) {
|
||||
LOG("Unable to get ZIM id");
|
||||
LOG(e.what());
|
||||
id = NULL;
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixReader_getFileSize(JNIEnv* env, jobject obj)
|
||||
{
|
||||
jint size = 0;
|
||||
|
||||
try {
|
||||
int cSize = READER->getFileSize();
|
||||
size = c2jni(cSize, env);
|
||||
} catch (std::exception& e) {
|
||||
LOG("Unable to get ZIM file size");
|
||||
LOG(e.what());
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixReader_getCreator(JNIEnv* env, jobject obj)
|
||||
{
|
||||
jstring creator;
|
||||
|
||||
try {
|
||||
std::string cCreator = READER->getCreator();
|
||||
creator = c2jni(cCreator, env);
|
||||
} catch (std::exception& e) {
|
||||
LOG("Unable to get ZIM creator");
|
||||
LOG(e.what());
|
||||
creator = NULL;
|
||||
}
|
||||
|
||||
return creator;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixReader_getPublisher(JNIEnv* env, jobject obj)
|
||||
{
|
||||
jstring publisher;
|
||||
|
||||
try {
|
||||
std::string cPublisher = READER->getPublisher();
|
||||
publisher = c2jni(cPublisher, env);
|
||||
} catch (std::exception& e) {
|
||||
LOG("Unable to get ZIM publish");
|
||||
LOG(e.what());
|
||||
publisher = NULL;
|
||||
}
|
||||
return publisher;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixReader_getName(JNIEnv* env, jobject obj)
|
||||
{
|
||||
jstring name;
|
||||
|
||||
try {
|
||||
std::string cName = READER->getName();
|
||||
name = c2jni(cName, env);
|
||||
} catch (std::exception& e) {
|
||||
LOG("Unable to get ZIM name");
|
||||
LOG(e.what());
|
||||
name = NULL;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixReader_getFavicon(JNIEnv* env, jobject obj)
|
||||
{
|
||||
jstring favicon;
|
||||
|
||||
try {
|
||||
std::string cContent;
|
||||
std::string cMime;
|
||||
READER->getFavicon(cContent, cMime);
|
||||
favicon = c2jni(
|
||||
base64_encode(cContent),
|
||||
env);
|
||||
} catch (std::exception& e) {
|
||||
LOG("Unable to get ZIM favicon");
|
||||
LOG(e.what());
|
||||
favicon = NULL;
|
||||
}
|
||||
return favicon;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixReader_getDate(JNIEnv* env, jobject obj)
|
||||
{
|
||||
jstring date;
|
||||
|
||||
try {
|
||||
std::string cDate = READER->getDate();
|
||||
date = c2jni(cDate, env);
|
||||
} catch (std::exception& e) {
|
||||
LOG("Unable to get ZIM date");
|
||||
LOG(e.what());
|
||||
date = NULL;
|
||||
}
|
||||
return date;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixReader_getLanguage(JNIEnv* env, jobject obj)
|
||||
{
|
||||
jstring language;
|
||||
|
||||
try {
|
||||
std::string cLanguage = READER->getLanguage();
|
||||
language = c2jni(cLanguage, env);
|
||||
} catch (std::exception& e) {
|
||||
LOG("Unable to get ZIM language");
|
||||
LOG(e.what());
|
||||
language = NULL;
|
||||
}
|
||||
|
||||
return language;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_org_kiwix_kiwixlib_JNIKiwixReader_getMimeType(
|
||||
JNIEnv* env, jobject obj, jstring url)
|
||||
{
|
||||
jstring mimeType;
|
||||
|
||||
std::string cUrl = jni2c(url, env);
|
||||
try {
|
||||
auto entry = READER->getEntryFromEncodedPath(cUrl);
|
||||
auto cMimeType = entry.getMimetype();
|
||||
mimeType = c2jni(cMimeType, env);
|
||||
} catch (std::exception& e) {
|
||||
LOG("Unable to get mime-type for url: %s", cUrl.c_str());
|
||||
LOG(e.what());
|
||||
mimeType = NULL;
|
||||
}
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_org_kiwix_kiwixlib_JNIKiwixReader_checkUrl(
|
||||
JNIEnv* env, jobject obj, jstring url)
|
||||
{
|
||||
jstring finalUrl;
|
||||
std::string cUrl = jni2c(url, env);
|
||||
try {
|
||||
auto entry = READER->getEntryFromEncodedPath(cUrl);
|
||||
entry = entry.getFinalEntry();
|
||||
finalUrl = c2jni(entry.getPath(), env);
|
||||
} catch (std::exception& e) {
|
||||
finalUrl = c2jni(std::string(), env);
|
||||
}
|
||||
return finalUrl;
|
||||
}
|
||||
|
||||
JNIEXPORT jbyteArray JNICALL Java_org_kiwix_kiwixlib_JNIKiwixReader_getContent(
|
||||
JNIEnv* env, jobject obj, jobject 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 */
|
||||
std::string cUrl = getStringObjValue(url, env);
|
||||
unsigned int cSize = 0;
|
||||
|
||||
try {
|
||||
auto entry = READER->getEntryFromEncodedPath(cUrl);
|
||||
bool isRedirect = entry.isRedirect();
|
||||
entry = entry.getFinalEntry();
|
||||
cSize = entry.getSize();
|
||||
setIntObjValue(cSize, sizeObj, env);
|
||||
setStringObjValue(entry.getMimetype(), mimeTypeObj, env);
|
||||
setStringObjValue(entry.getTitle(), titleObj, env);
|
||||
if (isRedirect) {
|
||||
setStringObjValue(entry.getPath(), url, env);
|
||||
} else {
|
||||
data = env->NewByteArray(cSize);
|
||||
env->SetByteArrayRegion(
|
||||
data, 0, cSize, reinterpret_cast<const jbyte*>(entry.getBlob().data()));
|
||||
}
|
||||
} catch (std::exception& e) {
|
||||
LOG("Unable to get content for url: %s", cUrl.c_str());
|
||||
LOG(e.what());
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
JNIEXPORT jbyteArray JNICALL Java_org_kiwix_kiwixlib_JNIKiwixReader_getContentPart(
|
||||
JNIEnv* env, jobject obj, jstring url, jint offset, jint len, jobject sizeObj)
|
||||
{
|
||||
jbyteArray data = env->NewByteArray(0);
|
||||
setIntObjValue(0, sizeObj, env);
|
||||
|
||||
/* Default values */
|
||||
/* Retrieve the content */
|
||||
std::string cUrl = jni2c(url, env);
|
||||
unsigned int cOffset = jni2c(offset, env);
|
||||
unsigned int cLen = jni2c(len, env);
|
||||
try {
|
||||
auto entry = READER->getEntryFromEncodedPath(cUrl);
|
||||
entry = entry.getFinalEntry();
|
||||
|
||||
if (cLen == 0) {
|
||||
setIntObjValue(entry.getSize(), sizeObj, env);
|
||||
} else if (cOffset+cLen < entry.getSize()) {
|
||||
auto blob = entry.getBlob(cOffset, cLen);
|
||||
data = env->NewByteArray(cLen);
|
||||
env->SetByteArrayRegion(
|
||||
data, 0, cLen, reinterpret_cast<const jbyte*>(blob.data()));
|
||||
setIntObjValue(cLen, sizeObj, env);
|
||||
}
|
||||
} catch (std::exception& e) {
|
||||
LOG("Unable to get partial content for url: %s (%u : %u)", cUrl.c_str(), cOffset, cLen);
|
||||
LOG(e.what());
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixReader_getArticleSize(
|
||||
JNIEnv* env, jobject obj, jstring url)
|
||||
{
|
||||
std::string cUrl = jni2c(url, env);
|
||||
try {
|
||||
auto entry = READER->getEntryFromEncodedPath(cUrl);
|
||||
entry = entry.getFinalEntry();
|
||||
return c2jni(entry.getSize(), env);
|
||||
} catch(std::exception& e) {
|
||||
LOG("Unable to get size for url : %s", cUrl.c_str());
|
||||
LOG(e.what());
|
||||
}
|
||||
return c2jni(0, env);
|
||||
}
|
||||
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixReader_getDirectAccessInformation(
|
||||
JNIEnv* env, jobject obj, jstring url)
|
||||
{
|
||||
jclass daiClass = env->FindClass("org/kiwix/kiwixlib/DirectAccessInfo");
|
||||
jmethodID daiInitMethod = env->GetMethodID(daiClass, "<init>", "()V");
|
||||
jobject dai = env->NewObject(daiClass, daiInitMethod);
|
||||
setDaiObjValue("", 0, dai, env);
|
||||
|
||||
std::string cUrl = jni2c(url, env);
|
||||
try {
|
||||
auto entry = READER->getEntryFromEncodedPath(cUrl);
|
||||
entry = entry.getFinalEntry();
|
||||
auto part_info = entry.getDirectAccessInfo();
|
||||
setDaiObjValue(part_info.first, part_info.second, dai, env);
|
||||
} catch (std::exception& e) {
|
||||
LOG("Unable to get direct access info for url: %s", cUrl.c_str());
|
||||
LOG(e.what());
|
||||
}
|
||||
return dai;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixReader_searchSuggestions(JNIEnv* env,
|
||||
jobject obj,
|
||||
jstring prefix,
|
||||
jint count)
|
||||
{
|
||||
jboolean retVal = JNI_FALSE;
|
||||
std::string cPrefix = jni2c(prefix, env);
|
||||
unsigned int cCount = jni2c(count, env);
|
||||
|
||||
try {
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
if (READER->searchSuggestionsSmart(cPrefix, cCount)) {
|
||||
retVal = JNI_TRUE;
|
||||
}
|
||||
#pragma GCC diagnostic pop
|
||||
} catch (std::exception& e) {
|
||||
LOG("Unable to get search results for pattern: %s", cPrefix.c_str());
|
||||
LOG(e.what());
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixReader_getNextSuggestion(JNIEnv* env,
|
||||
jobject obj,
|
||||
jobject titleObj,
|
||||
jobject urlObj)
|
||||
{
|
||||
jboolean retVal = JNI_FALSE;
|
||||
std::string cTitle;
|
||||
std::string cUrl;
|
||||
|
||||
try {
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
if (READER->getNextSuggestion(cTitle, cUrl)) {
|
||||
setStringObjValue(cTitle, titleObj, env);
|
||||
setStringObjValue(cUrl, urlObj, env);
|
||||
retVal = JNI_TRUE;
|
||||
}
|
||||
#pragma GCC diagnostic pop
|
||||
} catch (std::exception& e) {
|
||||
LOG("Unable to get next suggestion");
|
||||
LOG(e.what());
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixReader_getPageUrlFromTitle(JNIEnv* env,
|
||||
jobject obj,
|
||||
jstring title,
|
||||
jobject urlObj)
|
||||
{
|
||||
std::string cTitle = jni2c(title, env);
|
||||
|
||||
try {
|
||||
auto entry = READER->getEntryFromTitle(cTitle);
|
||||
entry = entry.getFinalEntry();
|
||||
setStringObjValue(entry.getPath(), urlObj, env);
|
||||
return JNI_TRUE;
|
||||
} catch (std::exception& e) {
|
||||
LOG("Unable to get url for title %s: ", cTitle.c_str());
|
||||
LOG(e.what());
|
||||
}
|
||||
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_org_kiwix_kiwixlib_JNIKiwixReader_getTitle(
|
||||
JNIEnv* env, jobject obj)
|
||||
{
|
||||
jstring title;
|
||||
|
||||
try {
|
||||
std::string cTitle = READER->getTitle();
|
||||
title = c2jni(cTitle, env);
|
||||
} catch (std::exception& e) {
|
||||
LOG("Unable to get zim title");
|
||||
LOG(e.what());
|
||||
title = NULL;
|
||||
}
|
||||
return title;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixReader_getDescription(JNIEnv* env, jobject obj)
|
||||
{
|
||||
jstring description;
|
||||
|
||||
try {
|
||||
std::string cDescription = READER->getDescription();
|
||||
description = c2jni(cDescription, env);
|
||||
} catch (std::exception& e) {
|
||||
LOG("Unable to get zim description");
|
||||
LOG(e.what());
|
||||
description = NULL;
|
||||
}
|
||||
return description;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixReader_getArticleCount(JNIEnv* env, jobject obj)
|
||||
{
|
||||
jint articleCount = 0;
|
||||
try {
|
||||
auto cArticleCount = READER->getArticleCount();
|
||||
articleCount = c2jni(cArticleCount, env);
|
||||
} catch (std::exception& e) {
|
||||
LOG("Unable to get article count.");
|
||||
LOG(e.what());
|
||||
}
|
||||
return articleCount;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixReader_getMediaCount(JNIEnv* env, jobject obj)
|
||||
{
|
||||
jint mediaCount = 0;
|
||||
try {
|
||||
auto cMediaCount = READER->getMediaCount();
|
||||
mediaCount = c2jni(cMediaCount, env);
|
||||
} catch (std::exception& e) {
|
||||
LOG("Unable to get media count.");
|
||||
LOG(e.what());
|
||||
}
|
||||
return mediaCount;
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_kiwix_kiwixlib_JNIKiwixReader_getRandomPage(
|
||||
JNIEnv* env, jobject obj, jobject urlObj)
|
||||
{
|
||||
jboolean retVal = JNI_FALSE;
|
||||
std::string cUrl;
|
||||
|
||||
try {
|
||||
std::string cUrl = READER->getRandomPage().getPath();
|
||||
setStringObjValue(cUrl, urlObj, env);
|
||||
retVal = JNI_TRUE;
|
||||
} catch (std::exception& e) {
|
||||
LOG("Unable to get random page");
|
||||
LOG(e.what());
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
* Copyright (C) 2017 Matthieu Gautier <mgautier@kymeria.fr>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
|
||||
#include "org_kiwix_kiwixlib_JNIKiwixSearcher.h"
|
||||
#include "org_kiwix_kiwixlib_JNIKiwixSearcher_Result.h"
|
||||
|
||||
#include "reader.h"
|
||||
#include "searcher.h"
|
||||
#include "utils.h"
|
||||
|
||||
#define SEARCHER (Handle<kiwix::Searcher>::getHandle(env, obj))
|
||||
#define RESULT (Handle<kiwix::Result>::getHandle(env, obj))
|
||||
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixSearcher_dispose(JNIEnv* env, jobject obj)
|
||||
{
|
||||
Handle<kiwix::Searcher>::dispose(env, obj);
|
||||
}
|
||||
|
||||
/* Kiwix Reader JNI functions */
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixSearcher_getNativeHandle(JNIEnv* env,
|
||||
jobject obj)
|
||||
{
|
||||
kiwix::Searcher* searcher = new kiwix::Searcher();
|
||||
return reinterpret_cast<jlong>(new Handle<kiwix::Searcher>(searcher));
|
||||
}
|
||||
|
||||
/* Kiwix library functions */
|
||||
JNIEXPORT void JNICALL Java_org_kiwix_kiwixlib_JNIKiwixSearcher_addReader(
|
||||
JNIEnv* env, jobject obj, jobject reader)
|
||||
{
|
||||
auto searcher = SEARCHER;
|
||||
|
||||
searcher->add_reader(*(Handle<kiwix::Reader>::getHandle(env, reader)));
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_kiwix_kiwixlib_JNIKiwixSearcher_search(
|
||||
JNIEnv* env, jobject obj, jstring query, jint count)
|
||||
{
|
||||
std::string cquery = jni2c(query, env);
|
||||
unsigned int ccount = jni2c(count, env);
|
||||
|
||||
SEARCHER->search(cquery, 0, ccount);
|
||||
}
|
||||
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixSearcher_getNextResult(JNIEnv* env,
|
||||
jobject obj)
|
||||
{
|
||||
jobject result = nullptr;
|
||||
|
||||
kiwix::Result* cresult = SEARCHER->getNextResult();
|
||||
if (cresult != nullptr) {
|
||||
jclass resultclass
|
||||
= env->FindClass("org/kiwix/kiwixlib/JNIKiwixSearcher$Result");
|
||||
jmethodID ctor = env->GetMethodID(
|
||||
resultclass, "<init>", "(Lorg/kiwix/kiwixlib/JNIKiwixSearcher;JLorg/kiwix/kiwixlib/JNIKiwixSearcher;)V");
|
||||
result = env->NewObject(resultclass, ctor, obj, reinterpret_cast<jlong>(new Handle<kiwix::Result>(cresult)), obj);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_kiwix_kiwixlib_JNIKiwixSearcher_00024Result_dispose(
|
||||
JNIEnv* env, jobject obj)
|
||||
{
|
||||
Handle<kiwix::Result>::dispose(env, obj);
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixSearcher_00024Result_getUrl(JNIEnv* env,
|
||||
jobject obj)
|
||||
{
|
||||
try {
|
||||
return c2jni(RESULT->get_url(), env);
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixSearcher_00024Result_getTitle(JNIEnv* env,
|
||||
jobject obj)
|
||||
{
|
||||
try {
|
||||
return c2jni(RESULT->get_title(), env);
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixSearcher_00024Result_getSnippet(JNIEnv* env,
|
||||
jobject obj)
|
||||
{
|
||||
return c2jni(RESULT->get_snippet(), env);
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixSearcher_00024Result_getContent(JNIEnv* env,
|
||||
jobject obj)
|
||||
{
|
||||
return c2jni(RESULT->get_content(), env);
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
* Copyright (C) 2017 Matthieu Gautier <mgautier@kymeria.fr>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
|
||||
#include <jni.h>
|
||||
#include "org_kiwix_kiwixlib_JNIKiwixServer.h"
|
||||
|
||||
#include "tools/base64.h"
|
||||
#include "server.h"
|
||||
#include "utils.h"
|
||||
|
||||
/* Kiwix Reader JNI functions */
|
||||
JNIEXPORT jlong JNICALL Java_org_kiwix_kiwixlib_JNIKiwixServer_getNativeServer(
|
||||
JNIEnv* env, jobject obj, jobject jLibrary)
|
||||
{
|
||||
LOG("Attempting to create server");
|
||||
Lock l;
|
||||
try {
|
||||
auto library = getPtr<kiwix::Library>(env, jLibrary);
|
||||
kiwix::Server* server = new kiwix::Server(library);
|
||||
return reinterpret_cast<jlong>(new Handle<kiwix::Server>(server));
|
||||
} catch (std::exception& e) {
|
||||
LOG("Error creating the server");
|
||||
LOG(e.what());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixServer_dispose(JNIEnv* env, jobject obj)
|
||||
{
|
||||
Handle<kiwix::Server>::dispose(env, obj);
|
||||
}
|
||||
|
||||
#define SERVER (Handle<kiwix::Server>::getHandle(env, obj))
|
||||
|
||||
/* Kiwix library functions */
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixServer_setRoot(JNIEnv* env, jobject obj, jstring jRoot)
|
||||
{
|
||||
std::string root = jni2c(jRoot, env);
|
||||
SERVER->setRoot(root);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixServer_setAddress(JNIEnv* env, jobject obj, jstring jAddress)
|
||||
{
|
||||
std::string address = jni2c(jAddress, env);
|
||||
SERVER->setAddress(address);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixServer_setPort(JNIEnv* env, jobject obj, int port)
|
||||
{
|
||||
SERVER->setPort(port);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixServer_setNbThreads(JNIEnv* env, jobject obj, int threads)
|
||||
{
|
||||
SERVER->setNbThreads(threads);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixServer_setTaskbar(JNIEnv* env, jobject obj, jboolean withTaskbar, jboolean withLibraryButton)
|
||||
{
|
||||
SERVER->setTaskbar(withTaskbar, withLibraryButton);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixServer_setBlockExternalLinks(JNIEnv* env, jobject obj, jboolean blockExternalLinks)
|
||||
{
|
||||
SERVER->setBlockExternalLinks(blockExternalLinks);
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixServer_start(JNIEnv* env, jobject obj)
|
||||
{
|
||||
return SERVER->start();
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_kiwix_kiwixlib_JNIKiwixServer_stop(JNIEnv* env, jobject obj)
|
||||
{
|
||||
SERVER->stop();
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019-2020 Matthieu Gautier <mgautier@kymeria.fr>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
|
||||
#include <jni.h>
|
||||
#include "org_kiwix_kiwixlib_Library.h"
|
||||
|
||||
#include "library.h"
|
||||
#include "reader.h"
|
||||
#include "utils.h"
|
||||
|
||||
/* Kiwix Reader JNI functions */
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_kiwix_kiwixlib_Library_allocate(
|
||||
JNIEnv* env, jobject thisObj)
|
||||
{
|
||||
allocate<kiwix::Library>(env, thisObj);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_kiwix_kiwixlib_Library_dispose(JNIEnv* env, jobject thisObj)
|
||||
{
|
||||
dispose<kiwix::Library>(env, thisObj);
|
||||
}
|
||||
|
||||
#define LIBRARY (getPtr<kiwix::Library>(env, thisObj))
|
||||
|
||||
/* Kiwix library functions */
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_org_kiwix_kiwixlib_Library_addBook(
|
||||
JNIEnv* env, jobject thisObj, jstring path)
|
||||
{
|
||||
auto cPath = jni2c(path, env);
|
||||
|
||||
try {
|
||||
kiwix::Reader reader(cPath);
|
||||
kiwix::Book book;
|
||||
book.update(reader);
|
||||
return LIBRARY->addBook(book);
|
||||
} catch (std::exception& e) {
|
||||
LOG("Unable to add the book");
|
||||
LOG(e.what()); }
|
||||
return false;
|
||||
}
|
||||
|
||||
METHOD(jobject, Library, getBookById, jstring id) {
|
||||
auto cId = jni2c(id, env);
|
||||
auto cBook = new kiwix::Book(LIBRARY->getBookById(cId));
|
||||
jclass cls = env->FindClass("org/kiwix/kiwixlib/Book");
|
||||
jmethodID constructorId = env->GetMethodID(cls, "<init>", "()V");
|
||||
jobject book = env->NewObject(cls, constructorId);
|
||||
setPtr(env, book, cBook);
|
||||
return book;
|
||||
}
|
||||
|
||||
METHOD(jint, Library, getBookCount, jboolean localBooks, jboolean remoteBooks) {
|
||||
return LIBRARY->getBookCount(localBooks, remoteBooks);
|
||||
}
|
||||
|
||||
METHOD0(jobjectArray, Library, getBooksIds) {
|
||||
return c2jni(LIBRARY->getBooksIds(), env);
|
||||
}
|
||||
|
||||
METHOD(jobjectArray, Library, filter, jobject filterObj) {
|
||||
auto filter = getPtr<kiwix::Filter>(env, filterObj);
|
||||
return c2jni(LIBRARY->filter(*filter), env);
|
||||
}
|
||||
|
||||
METHOD0(jobjectArray, Library, getBooksLanguages) {
|
||||
return c2jni(LIBRARY->getBooksLanguages(), env);
|
||||
}
|
||||
|
||||
METHOD0(jobjectArray, Library, getBooksCreators) {
|
||||
return c2jni(LIBRARY->getBooksCreators(), env);
|
||||
}
|
||||
|
||||
METHOD0(jobjectArray, Library, getBooksPublisher) {
|
||||
return c2jni(LIBRARY->getBooksPublishers(), env);
|
||||
}
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Matthieu Gautier <mgautier@kymeria.fr>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
|
||||
#include <jni.h>
|
||||
#include "org_kiwix_kiwixlib_Manager.h"
|
||||
|
||||
#include "manager.h"
|
||||
#include "utils.h"
|
||||
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_kiwix_kiwixlib_Manager_allocate(
|
||||
JNIEnv* env, jobject thisObj, jobject libraryObj)
|
||||
{
|
||||
auto lib = getPtr<kiwix::Library>(env, libraryObj);
|
||||
allocate<kiwix::Manager>(env, thisObj, lib);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_kiwix_kiwixlib_Manager_dispose(JNIEnv* env, jobject thisObj)
|
||||
{
|
||||
dispose<kiwix::Manager>(env, thisObj);
|
||||
}
|
||||
|
||||
#define MANAGER (getPtr<kiwix::Manager>(env, thisObj))
|
||||
|
||||
/* Kiwix manager functions */
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_org_kiwix_kiwixlib_Manager_readFile(
|
||||
JNIEnv* env, jobject thisObj, jstring path)
|
||||
{
|
||||
auto cPath = jni2c(path, env);
|
||||
|
||||
try {
|
||||
return MANAGER->readFile(cPath);
|
||||
} catch (std::exception& e) {
|
||||
LOG("Unable to get readFile");
|
||||
LOG(e.what());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_org_kiwix_kiwixlib_Manager_readXml(
|
||||
JNIEnv* env, jobject thisObj, jstring content, jstring libraryPath)
|
||||
{
|
||||
auto cContent = jni2c(content, env);
|
||||
auto cPath = jni2c(libraryPath, env);
|
||||
|
||||
try {
|
||||
return MANAGER->readXml(cContent, false, cPath);
|
||||
} catch (std::exception& e) {
|
||||
LOG("Unable to get ZIM id");
|
||||
LOG(e.what());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_org_kiwix_kiwixlib_Manager_readOpds(
|
||||
JNIEnv* env, jobject thisObj, jstring content, jstring urlHost)
|
||||
{
|
||||
auto cContent = jni2c(content, env);
|
||||
auto cUrl = jni2c(urlHost, env);
|
||||
|
||||
try {
|
||||
return MANAGER->readOpds(cContent, cUrl);
|
||||
} catch (std::exception& e) {
|
||||
LOG("Unable to get ZIM id");
|
||||
LOG(e.what());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_org_kiwix_kiwixlib_Manager_readBookmarkFile(
|
||||
JNIEnv* env, jobject thisObj, jstring path)
|
||||
{
|
||||
auto cPath = jni2c(path, env);
|
||||
|
||||
try {
|
||||
return MANAGER->readBookmarkFile(cPath);
|
||||
} catch (std::exception& e) {
|
||||
LOG("Unable to get ZIM id");
|
||||
LOG(e.what());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_kiwix_kiwixlib_Manager_addBookFromPath(
|
||||
JNIEnv* env, jobject thisObj,
|
||||
jstring pathToOpen, jstring pathToSave, jstring url, jboolean checkMetaData)
|
||||
{
|
||||
auto cPathToOpen = jni2c(pathToOpen, env);
|
||||
auto cPathToSave = jni2c(pathToSave, env);
|
||||
auto cUrl = jni2c(url, env);
|
||||
jstring id = NULL;
|
||||
|
||||
try {
|
||||
auto cId = MANAGER->addBookFromPathAndGetId(cPathToOpen, cPathToSave, cUrl, checkMetaData);
|
||||
if ( !cId.empty() ) {
|
||||
id = c2jni(cId, env);
|
||||
}
|
||||
} catch (std::exception& e) {
|
||||
LOG("Unable to get ZIM file size");
|
||||
LOG(e.what());
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
|
||||
java_sources = files([
|
||||
'org/kiwix/kiwixlib/JNIICU.java',
|
||||
'org/kiwix/kiwixlib/Book.java',
|
||||
'org/kiwix/kiwixlib/JNIKiwixReader.java',
|
||||
'org/kiwix/kiwixlib/Library.java',
|
||||
'org/kiwix/kiwixlib/Manager.java',
|
||||
'org/kiwix/kiwixlib/Filter.java',
|
||||
'org/kiwix/kiwixlib/JNIKiwixSearcher.java',
|
||||
'org/kiwix/kiwixlib/JNIKiwixServer.java',
|
||||
'org/kiwix/kiwixlib/JNIKiwixInt.java',
|
||||
'org/kiwix/kiwixlib/JNIKiwixString.java',
|
||||
'org/kiwix/kiwixlib/JNIKiwixBool.java',
|
||||
'org/kiwix/kiwixlib/JNIKiwixException.java',
|
||||
'org/kiwix/kiwixlib/DirectAccessInfo.java'
|
||||
])
|
||||
|
||||
kiwix_jni = custom_target('jni',
|
||||
input: java_sources,
|
||||
output: ['org_kiwix_kiwixlib_JNIKiwix.h',
|
||||
'org_kiwix_kiwixlib_Book.h',
|
||||
'org_kiwix_kiwixlib_JNIKiwixReader.h',
|
||||
'org_kiwix_kiwixlib_Library.h',
|
||||
'org_kiwix_kiwixlib_Manager.h',
|
||||
'org_kiwix_kiwixlib_Filter.h',
|
||||
'org_kiwix_kiwixlib_JNIKiwixServer.h',
|
||||
'org_kiwix_kiwixlib_JNIKiwixSearcher.h',
|
||||
'org_kiwix_kiwixlib_JNIKiwixSearcher_Result.h'],
|
||||
command:['javac', '-d', '@OUTDIR@', '-h', '@OUTDIR@', '@INPUT@']
|
||||
)
|
||||
|
||||
jni_sources = files([
|
||||
'kiwixicu.cpp',
|
||||
'book.cpp',
|
||||
'kiwixreader.cpp',
|
||||
'library.cpp',
|
||||
'manager.cpp',
|
||||
'filter.cpp',
|
||||
'kiwixsearcher.cpp',
|
||||
'kiwixserver.cpp',
|
||||
])
|
||||
|
||||
kiwix_sources += jni_sources + [kiwix_jni]
|
||||
|
||||
if 'java' in wrapper
|
||||
kiwix_jar = jar('kiwixlib', java_sources)
|
||||
#junit_jar = files('org/kiwix/testing/junit-4.13.jar')
|
||||
#test_jar = jar('testing', 'org/kiwix/testing/test.java',
|
||||
# link_with: [kiwix_jar, junit_jar])
|
||||
#test('javatest', test_jar)
|
||||
endif
|
||||
|
||||
install_subdir('org', install_dir: 'kiwix-lib/java', exclude_directories: ['kiwix/testing'])
|
||||
install_subdir('res', install_dir: 'kiwix-lib')
|
||||
install_data('AndroidManifest.xml', install_dir: 'kiwix-lib')
|
||||
@@ -1,48 +0,0 @@
|
||||
|
||||
package org.kiwix.kiwixlib;
|
||||
|
||||
public class Book
|
||||
{
|
||||
public Book() { allocate(); }
|
||||
|
||||
|
||||
public native void update(Book book);
|
||||
public native void update(JNIKiwixReader reader);
|
||||
|
||||
@Override
|
||||
protected void finalize() { dispose(); }
|
||||
|
||||
public native String getId();
|
||||
public native String getPath();
|
||||
public native boolean isPathValid();
|
||||
public native String getTitle();
|
||||
public native String getDescription();
|
||||
public native String getLanguage();
|
||||
public native String getCreator();
|
||||
public native String getPublisher();
|
||||
public native String getDate();
|
||||
public native String getUrl();
|
||||
public native String getName();
|
||||
public native String getFlavour();
|
||||
public native String getCategory();
|
||||
public native String getTags();
|
||||
/**
|
||||
* Return the value associated to the tag tagName
|
||||
*
|
||||
* @param tagName the tag name to search for.
|
||||
* @return The value of the tag. If the tag is not found, return empty string.
|
||||
*/
|
||||
public native String getTagStr(String tagName);
|
||||
|
||||
public native long getArticleCount();
|
||||
public native long getMediaCount();
|
||||
public native long getSize();
|
||||
|
||||
public native String getFavicon();
|
||||
public native String getFaviconUrl();
|
||||
public native String getFaviconMimeType();
|
||||
|
||||
private native void allocate();
|
||||
private native void dispose();
|
||||
private long nativeHandle;
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Matthieu Gautier <mgautier@kymeria.fr>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package org.kiwix.kiwixlib;
|
||||
|
||||
public class DirectAccessInfo
|
||||
{
|
||||
public String filename;
|
||||
public long offset;
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019-2020 Matthieu Gautier <mgautier@kymeria.fr>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package org.kiwix.kiwixlib;
|
||||
|
||||
public class Filter
|
||||
{
|
||||
|
||||
public native Filter local(boolean accept);
|
||||
public native Filter remote(boolean accept);
|
||||
public native Filter valid(boolean accept);
|
||||
public native Filter acceptTags(String[] tags);
|
||||
public native Filter rejectTags(String[] tags);
|
||||
public native Filter lang(String lang);
|
||||
public native Filter publisher(String publisher);
|
||||
public native Filter creator(String creator);
|
||||
public native Filter maxSize(long size);
|
||||
public native Filter query(String query);
|
||||
|
||||
|
||||
public Filter() { allocate(); }
|
||||
|
||||
@Override
|
||||
protected void finalize() { dispose(); }
|
||||
private native void allocate();
|
||||
private native void dispose();
|
||||
private long nativeHandle;
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
* Copyright (C) 2017 Matthieu Gautier <mgautier@kymeria.fr>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package org.kiwix.kiwixlib;
|
||||
|
||||
public class JNIICU
|
||||
{
|
||||
static public native void setDataDirectory(String icuDataDir);
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
* Copyright (C) 2017 Matthieu Gautier <mgautier@kymeria.fr>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package org.kiwix.kiwixlib;
|
||||
|
||||
import android.content.Context;
|
||||
import com.getkeepsafe.relinker.ReLinker;
|
||||
import org.kiwix.kiwixlib.JNIICU;
|
||||
|
||||
public class JNIKiwix
|
||||
{
|
||||
public JNIKiwix(final Context context){
|
||||
ReLinker.loadLibrary(context, "kiwix");
|
||||
}
|
||||
|
||||
public void setDataDirectory(String icuDataDir) {
|
||||
JNIICU.setDataDirectory(icuDataDir);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
public class JNIKiwixBool
|
||||
{
|
||||
public boolean value;
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Matthieu Gautier <mgautier@kymeria.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;
|
||||
|
||||
public class JNIKiwixException extends Exception
|
||||
{
|
||||
public JNIKiwixException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
public class JNIKiwixInt
|
||||
{
|
||||
public int value;
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
* Copyright (C) 2017 Matthieu Gautier <mgautier@kymeria.fr>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package org.kiwix.kiwixlib;
|
||||
|
||||
import org.kiwix.kiwixlib.JNIKiwixException;
|
||||
import org.kiwix.kiwixlib.JNIKiwixString;
|
||||
import org.kiwix.kiwixlib.JNIKiwixInt;
|
||||
import org.kiwix.kiwixlib.JNIKiwixSearcher;
|
||||
import org.kiwix.kiwixlib.DirectAccessInfo;
|
||||
import java.io.FileDescriptor;
|
||||
|
||||
public class JNIKiwixReader
|
||||
{
|
||||
public native String getMainPage();
|
||||
|
||||
public native String getTitle();
|
||||
|
||||
public native String getId();
|
||||
|
||||
public native String getLanguage();
|
||||
|
||||
public native String getMimeType(String url);
|
||||
|
||||
/**
|
||||
* Check if a url exists and is a redirect or not.
|
||||
*
|
||||
* Return an empty string if the url doesn't exist in the reader.
|
||||
* Return the url of the "final" entry.
|
||||
* - equal to the input url if the entry is not a redirection.
|
||||
* - different if the url is a redirection (and the webview should redirect to it).
|
||||
*/
|
||||
public native String checkUrl(String url);
|
||||
|
||||
/**
|
||||
* Get the content of a article.
|
||||
*
|
||||
* Return a byte array of the content of the article.
|
||||
* Set the title, mimeType to the title and mimeType of the article.
|
||||
* Set the size to the size of the returned array.
|
||||
*
|
||||
* If the entry doesn't exist :
|
||||
* - return a empty byte array
|
||||
* - set all arguments (except url) to empty/0.
|
||||
* If the entry exist but is a redirection :
|
||||
* - return an empty byte array
|
||||
* - set all arguments (including url) to information of the targeted article.
|
||||
*/
|
||||
public native byte[] getContent(JNIKiwixString url,
|
||||
JNIKiwixString title,
|
||||
JNIKiwixString mimeType,
|
||||
JNIKiwixInt size);
|
||||
|
||||
/**
|
||||
* getContentPart.
|
||||
*
|
||||
* Get only a part of the content of the article.
|
||||
* Return a byte array of `len` size starting from offset `offset`.
|
||||
* Set `size` to the number of bytes read
|
||||
* (`len` if everything is ok, 0 in case of error).
|
||||
* If `len` == 0, no bytes are read but `size` is set to the total size of the
|
||||
* article.
|
||||
*/
|
||||
public native byte[] getContentPart(String url,
|
||||
int offest,
|
||||
int len,
|
||||
JNIKiwixInt size);
|
||||
|
||||
/**
|
||||
*
|
||||
* Get the size of an article.
|
||||
*
|
||||
* @param url The url of the article.
|
||||
* @return The size of the final (redirections are resolved) article (in byte).
|
||||
* Return 0 if the article is not found.
|
||||
*/
|
||||
public native long getArticleSize(String url);
|
||||
|
||||
/**
|
||||
* getDirectAccessInformation.
|
||||
*
|
||||
* Return information giving where the content is located in the zim file.
|
||||
*
|
||||
* Some contents (binary content) are stored uncompressed in the zim file.
|
||||
* Knowing this information, it could be interesting to directly open
|
||||
* the zim file (or zim part) and directly read the content from it (and so
|
||||
* bypassing the libzim).
|
||||
*
|
||||
* Return a `DirectAccessInfo` (filename, offset) where the content is located.
|
||||
*
|
||||
* If the content cannot be directly accessed (content is compressed or zim
|
||||
* file is cut in the middle of the content), the filename is an empty string
|
||||
* and offset is zero.
|
||||
*/
|
||||
public native DirectAccessInfo getDirectAccessInformation(String url);
|
||||
|
||||
public native boolean searchSuggestions(String prefix, int count);
|
||||
|
||||
public native boolean getNextSuggestion(JNIKiwixString title, JNIKiwixString url);
|
||||
|
||||
public native boolean getPageUrlFromTitle(String title, JNIKiwixString url);
|
||||
|
||||
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 JNIKiwixSearcher search(String query, int count)
|
||||
{
|
||||
JNIKiwixSearcher searcher = new JNIKiwixSearcher();
|
||||
searcher.addKiwixReader(this);
|
||||
searcher.search(query, count);
|
||||
return searcher;
|
||||
}
|
||||
|
||||
public JNIKiwixReader(String filename) throws JNIKiwixException
|
||||
{
|
||||
nativeHandle = getNativeReader(filename);
|
||||
if (nativeHandle == 0) {
|
||||
throw new JNIKiwixException("Cannot open zimfile "+filename);
|
||||
}
|
||||
}
|
||||
|
||||
public JNIKiwixReader(FileDescriptor fd) throws JNIKiwixException
|
||||
{
|
||||
nativeHandle = getNativeReaderByFD(fd);
|
||||
if (nativeHandle == 0) {
|
||||
throw new JNIKiwixException("Cannot open zimfile by fd "+fd.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public JNIKiwixReader(FileDescriptor fd, long offset, long size)
|
||||
throws JNIKiwixException
|
||||
{
|
||||
nativeHandle = getNativeReaderEmbedded(fd, offset, size);
|
||||
if (nativeHandle == 0) {
|
||||
throw new JNIKiwixException(String.format("Cannot open embedded zimfile (fd=%s, offset=%d, size=%d)", fd, offset, size));
|
||||
}
|
||||
}
|
||||
|
||||
public JNIKiwixReader() {
|
||||
|
||||
}
|
||||
public native void dispose();
|
||||
|
||||
private native long getNativeReader(String filename);
|
||||
private native long getNativeReaderByFD(FileDescriptor fd);
|
||||
private native long getNativeReaderEmbedded(FileDescriptor fd, long offset, long size);
|
||||
private long nativeHandle;
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
* Copyright (C) 2017 Matthieu Gautier <mgautier@kymeria.fr>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
|
||||
package org.kiwix.kiwixlib;
|
||||
|
||||
import org.kiwix.kiwixlib.JNIKiwixReader;
|
||||
import java.util.Vector;
|
||||
|
||||
public class JNIKiwixSearcher
|
||||
{
|
||||
public class Result
|
||||
{
|
||||
private long nativeHandle;
|
||||
private JNIKiwixSearcher searcher;
|
||||
public Result(long handle, JNIKiwixSearcher _searcher)
|
||||
{
|
||||
nativeHandle = handle;
|
||||
searcher = _searcher;
|
||||
}
|
||||
public native String getUrl();
|
||||
public native String getTitle();
|
||||
public native String getContent();
|
||||
public native String getSnippet();
|
||||
public native void dispose();
|
||||
}
|
||||
|
||||
public JNIKiwixSearcher()
|
||||
{
|
||||
nativeHandle = getNativeHandle();
|
||||
usedReaders = new Vector();
|
||||
}
|
||||
public native void dispose();
|
||||
|
||||
private native long getNativeHandle();
|
||||
private long nativeHandle;
|
||||
private Vector usedReaders;
|
||||
|
||||
public native void addReader(JNIKiwixReader reader);
|
||||
public void addKiwixReader(JNIKiwixReader reader)
|
||||
{
|
||||
addReader(reader);
|
||||
usedReaders.addElement(reader);
|
||||
};
|
||||
|
||||
public native void search(String query, int count);
|
||||
|
||||
public native Result getNextResult();
|
||||
public native boolean hasMoreResult();
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 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.
|
||||
*/
|
||||
|
||||
package org.kiwix.kiwixlib;
|
||||
|
||||
import org.kiwix.kiwixlib.JNIKiwixException;
|
||||
import org.kiwix.kiwixlib.Library;
|
||||
|
||||
public class JNIKiwixServer
|
||||
{
|
||||
public native void setRoot(String root);
|
||||
|
||||
public native void setAddress(String address);
|
||||
|
||||
public native void setPort(int port);
|
||||
|
||||
public native void setNbThreads(int nbTreads);
|
||||
|
||||
public native void setTaskbar(boolean withTaskBar, boolean witLibraryButton);
|
||||
|
||||
public native void setBlockExternalLinks(boolean blockExternalLinks);
|
||||
|
||||
public native boolean start();
|
||||
|
||||
public native void stop();
|
||||
|
||||
public JNIKiwixServer(Library library)
|
||||
{
|
||||
nativeHandle = getNativeServer(library);
|
||||
}
|
||||
|
||||
private native long getNativeServer(Library library);
|
||||
private long nativeHandle;
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
public class JNIKiwixString
|
||||
{
|
||||
public String value;
|
||||
|
||||
public JNIKiwixString(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public JNIKiwixString() {
|
||||
this("");
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019-2020 Matthieu Gautier <mgautier@kymeria.fr>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package org.kiwix.kiwixlib;
|
||||
|
||||
import org.kiwix.kiwixlib.Book;
|
||||
import org.kiwix.kiwixlib.JNIKiwixException;
|
||||
|
||||
public class Library
|
||||
{
|
||||
public native boolean addBook(String path) throws JNIKiwixException;
|
||||
|
||||
public native Book getBookById(String id);
|
||||
public native int getBookCount(boolean localBooks, boolean remoteBooks);
|
||||
|
||||
public native String[] getBooksIds();
|
||||
public native String[] filter(Filter filter);
|
||||
|
||||
public native String[] getBooksLanguages();
|
||||
public native String[] getBooksCreators();
|
||||
public native String[] getBooksPublishers();
|
||||
|
||||
public Library()
|
||||
{
|
||||
allocate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() { dispose(); }
|
||||
private native void allocate();
|
||||
private native void dispose();
|
||||
private long nativeHandle;
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Matthieu Gautier <mgautier@kymeria.fr>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package org.kiwix.kiwixlib;
|
||||
|
||||
import org.kiwix.kiwixlib.Library;
|
||||
|
||||
public class Manager
|
||||
{
|
||||
/**
|
||||
* Read a `library.xml` file and add books in the library.
|
||||
*
|
||||
* @param path The (utf8) path to the `library.xml` file.
|
||||
* @return True if the file has been properly parsed.
|
||||
*/
|
||||
public native boolean readFile(String path);
|
||||
|
||||
/**
|
||||
* Load a library content stored in a string (at `library.xml` format).
|
||||
*
|
||||
* @param content The content corresponding of the library xml.
|
||||
* @param libraryPath The library path (used to resolve relative paths)
|
||||
* @return True if the content has been properly parsed.
|
||||
*/
|
||||
public native boolean readXml(String content, String libraryPath);
|
||||
|
||||
/**
|
||||
* Load a library content stored in a string (at OPDS stream format)
|
||||
*
|
||||
* @param content the content of the OPDS stream.
|
||||
* @param urlHost the url of the stream (used to resolve relative url)
|
||||
* @return True if the content has been properly parsed.
|
||||
*/
|
||||
public native boolean readOpds(String content, String urlHost);
|
||||
|
||||
/**
|
||||
* Load a bookmark file
|
||||
*
|
||||
* @param path The path of the file to read.
|
||||
* @return True if the content has been properly parsed
|
||||
*/
|
||||
public native boolean readBookmarkFile(String path);
|
||||
|
||||
/**
|
||||
* Add a book to the library.
|
||||
*
|
||||
* @param pathToOpen The path of the zim file to add.
|
||||
* @param pathToSave The path to store in the library in place of
|
||||
* pathToOpen.
|
||||
* @param url The url of the book to store in the library
|
||||
* (useful for kiiwix-serve catalog)
|
||||
* @param checkMetaData Tell if we check metadata before adding a book to the
|
||||
* library.
|
||||
* @return The id of te book if the book has been added to the library.
|
||||
* Empty string if not.
|
||||
*/
|
||||
public native String addBookFromPath(String pathToOpen,
|
||||
String pathToSave,
|
||||
String url,
|
||||
boolean checkMetaData);
|
||||
|
||||
public Manager(Library library) {
|
||||
allocate(library);
|
||||
_library = library;
|
||||
}
|
||||
|
||||
private Library _library;
|
||||
|
||||
@Override
|
||||
protected void finalize() { dispose(); }
|
||||
private native void allocate(Library library);
|
||||
private native void dispose();
|
||||
private long nativeHandle;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:opds="http://opds-spec.org/2010/catalog">
|
||||
<id>00000000-0000-0000-0000-000000000000</id>
|
||||
<entry>
|
||||
<title>Test ZIM file</title>
|
||||
<id>urn:uuid:86c91e51-55bf-8882-464e-072aca37a3e8</id>
|
||||
<icon>/meta?name=favicon&content=small</icon>
|
||||
<updated>2020-11-27:00::00:Z</updated>
|
||||
<language>en</language>
|
||||
<summary>This is a ZIM file used in libzim unit-tests</summary>
|
||||
<tags>unit;test</tags>
|
||||
<link type="text/html" href="/small" />
|
||||
<author>
|
||||
<name>Kiwix</name>
|
||||
</author>
|
||||
<link rel="http://opds-spec.org/acquisition/open-access" type="application/x-zim" href="http://localhost/small.zim" length="78982" />
|
||||
<link rel="http://opds-spec.org/image/thumbnail" type="image/png" href="/meta?name=favicon&content=small" />
|
||||
</entry>
|
||||
</feed>
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
# This script compiles and runs the unit test to test the java wrapper.
|
||||
# This is not integrated in meson because ... this is not so easy.
|
||||
|
||||
die()
|
||||
{
|
||||
echo >&2 "!!! ERROR: $*"
|
||||
exit 1
|
||||
}
|
||||
|
||||
|
||||
KIWIX_LIB_JAR=$1
|
||||
if [ -z $KIWIX_LIB_JAR ]
|
||||
then
|
||||
die "You must give the path to the kiwixlib.jar as first argument"
|
||||
fi
|
||||
|
||||
KIWIX_LIB_DIR=$2
|
||||
if [ -z $KIWIX_LIB_DIR ]
|
||||
then
|
||||
die "You must give the path to directory containing libkiwix.so as second argument"
|
||||
fi
|
||||
|
||||
KIWIX_LIB_JAR=$(readlink -f "$KIWIX_LIB_JAR")
|
||||
KIWIX_LIB_DIR=$(readlink -f "$KIWIX_LIB_DIR")
|
||||
TEST_SOURCE_DIR=$(dirname "$(readlink -f $0)")
|
||||
|
||||
cd "$TEST_SOURCE_DIR"
|
||||
|
||||
javac -g -d . -s . -cp "junit-4.13.jar:$KIWIX_LIB_JAR" test.java \
|
||||
|| die "Compilation failed"
|
||||
|
||||
java -Djava.library.path="$KIWIX_LIB_DIR" \
|
||||
-cp "junit-4.13.jar:hamcrest-core-1.3.jar:$KIWIX_LIB_JAR:." \
|
||||
org.junit.runner.JUnitCore test \
|
||||
|| die "Unit test failed"
|
||||
@@ -1,28 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
die()
|
||||
{
|
||||
echo >&2 "!!! ERROR: $*"
|
||||
exit 1
|
||||
}
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
rm -f small.zim
|
||||
zimwriterfs --withoutFTIndex \
|
||||
-w main.html \
|
||||
-f favicon.png \
|
||||
-l en \
|
||||
-t "Test ZIM file" \
|
||||
-d "N/A" \
|
||||
-c "N/A" \
|
||||
-p "N/A" \
|
||||
small_zimfile_data \
|
||||
small.zim \
|
||||
&& echo 'small.zim was successfully created' \
|
||||
|| die 'Failed to create small.zim'
|
||||
|
||||
printf "BEGINZIM" > small.zim.embedded \
|
||||
&& cat small.zim >> small.zim.embedded \
|
||||
&& printf "ENDZIM" >> small.zim.embedded \
|
||||
&& echo 'small.zim.embedded was successfully created' \
|
||||
|| die 'Failed to create small.zim.embedded'
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 3.1 KiB |
@@ -1,160 +0,0 @@
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.*;
|
||||
import org.kiwix.kiwixlib.*;
|
||||
|
||||
public class test {
|
||||
static {
|
||||
System.loadLibrary("kiwix");
|
||||
}
|
||||
|
||||
private static byte[] getFileContent(String path)
|
||||
throws IOException
|
||||
{
|
||||
File file = new File(path);
|
||||
DataInputStream in = new DataInputStream(
|
||||
new BufferedInputStream(
|
||||
new FileInputStream(file)));
|
||||
byte[] data = new byte[(int)file.length()];
|
||||
in.read(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
private static byte[] getFileContentPartial(String path, int offset, int size)
|
||||
throws IOException
|
||||
{
|
||||
File file = new File(path);
|
||||
DataInputStream in = new DataInputStream(
|
||||
new BufferedInputStream(
|
||||
new FileInputStream(file)));
|
||||
byte[] data = new byte[size];
|
||||
in.skipBytes(offset);
|
||||
in.read(data, 0, size);
|
||||
return data;
|
||||
}
|
||||
|
||||
private static String getTextFileContent(String path)
|
||||
throws IOException
|
||||
{
|
||||
return new String(getFileContent(path));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReader()
|
||||
throws JNIKiwixException, IOException
|
||||
{
|
||||
JNIKiwixReader reader = new JNIKiwixReader("small.zim");
|
||||
assertEquals("Test ZIM file", reader.getTitle());
|
||||
assertEquals(45, reader.getFileSize()); // The file size is in KiB
|
||||
assertEquals("A/main.html", reader.getMainPage());
|
||||
String s = getTextFileContent("small_zimfile_data/main.html");
|
||||
byte[] c = reader.getContent(new JNIKiwixString("A/main.html"),
|
||||
new JNIKiwixString(),
|
||||
new JNIKiwixString(),
|
||||
new JNIKiwixInt());
|
||||
assertEquals(s, new String(c));
|
||||
|
||||
byte[] faviconData = getFileContent("small_zimfile_data/favicon.png");
|
||||
assertEquals(faviconData.length, reader.getArticleSize("I/favicon.png"));
|
||||
c = reader.getContent(new JNIKiwixString("I/favicon.png"),
|
||||
new JNIKiwixString(),
|
||||
new JNIKiwixString(),
|
||||
new JNIKiwixInt());
|
||||
assertTrue(Arrays.equals(faviconData, c));
|
||||
|
||||
DirectAccessInfo dai = reader.getDirectAccessInformation("I/favicon.png");
|
||||
assertNotEquals("", dai.filename);
|
||||
c = getFileContentPartial(dai.filename, (int)dai.offset, faviconData.length);
|
||||
assertTrue(Arrays.equals(faviconData, c));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReaderByFd()
|
||||
throws JNIKiwixException, IOException
|
||||
{
|
||||
FileInputStream fis = new FileInputStream("small.zim");
|
||||
JNIKiwixReader reader = new JNIKiwixReader(fis.getFD());
|
||||
assertEquals("Test ZIM file", reader.getTitle());
|
||||
assertEquals(45, reader.getFileSize()); // The file size is in KiB
|
||||
assertEquals("A/main.html", reader.getMainPage());
|
||||
String s = getTextFileContent("small_zimfile_data/main.html");
|
||||
byte[] c = reader.getContent(new JNIKiwixString("A/main.html"),
|
||||
new JNIKiwixString(),
|
||||
new JNIKiwixString(),
|
||||
new JNIKiwixInt());
|
||||
assertEquals(s, new String(c));
|
||||
|
||||
byte[] faviconData = getFileContent("small_zimfile_data/favicon.png");
|
||||
assertEquals(faviconData.length, reader.getArticleSize("I/favicon.png"));
|
||||
c = reader.getContent(new JNIKiwixString("I/favicon.png"),
|
||||
new JNIKiwixString(),
|
||||
new JNIKiwixString(),
|
||||
new JNIKiwixInt());
|
||||
assertTrue(Arrays.equals(faviconData, c));
|
||||
|
||||
DirectAccessInfo dai = reader.getDirectAccessInformation("I/favicon.png");
|
||||
assertNotEquals("", dai.filename);
|
||||
c = getFileContentPartial(dai.filename, (int)dai.offset, faviconData.length);
|
||||
assertTrue(Arrays.equals(faviconData, c));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReaderWithAnEmbeddedArchive()
|
||||
throws JNIKiwixException, IOException
|
||||
{
|
||||
File plainArchive = new File("small.zim");
|
||||
FileInputStream fis = new FileInputStream("small.zim.embedded");
|
||||
JNIKiwixReader reader = new JNIKiwixReader(fis.getFD(), 8, plainArchive.length());
|
||||
assertEquals("Test ZIM file", reader.getTitle());
|
||||
assertEquals(45, reader.getFileSize()); // The file size is in KiB
|
||||
assertEquals("A/main.html", reader.getMainPage());
|
||||
String s = getTextFileContent("small_zimfile_data/main.html");
|
||||
byte[] c = reader.getContent(new JNIKiwixString("A/main.html"),
|
||||
new JNIKiwixString(),
|
||||
new JNIKiwixString(),
|
||||
new JNIKiwixInt());
|
||||
assertEquals(s, new String(c));
|
||||
|
||||
byte[] faviconData = getFileContent("small_zimfile_data/favicon.png");
|
||||
assertEquals(faviconData.length, reader.getArticleSize("I/favicon.png"));
|
||||
c = reader.getContent(new JNIKiwixString("I/favicon.png"),
|
||||
new JNIKiwixString(),
|
||||
new JNIKiwixString(),
|
||||
new JNIKiwixInt());
|
||||
assertTrue(Arrays.equals(faviconData, c));
|
||||
|
||||
DirectAccessInfo dai = reader.getDirectAccessInformation("I/favicon.png");
|
||||
assertNotEquals("", dai.filename);
|
||||
c = getFileContentPartial(dai.filename, (int)dai.offset, faviconData.length);
|
||||
assertTrue(Arrays.equals(faviconData, c));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLibrary()
|
||||
throws IOException
|
||||
{
|
||||
Library lib = new Library();
|
||||
Manager manager = new Manager(lib);
|
||||
String content = getTextFileContent("catalog.xml");
|
||||
manager.readOpds(content, "http://localhost");
|
||||
assertEquals(lib.getBookCount(true, true), 1);
|
||||
String[] bookIds = lib.getBooksIds();
|
||||
assertEquals(bookIds.length, 1);
|
||||
Book book = lib.getBookById(bookIds[0]);
|
||||
assertEquals(book.getTitle(), "Test ZIM file");
|
||||
assertEquals(book.getTags(), "unit;test");
|
||||
assertEquals(book.getFaviconUrl(), "http://localhost/meta?name=favicon&content=small");
|
||||
assertEquals(book.getUrl(), "http://localhost/small.zim");
|
||||
}
|
||||
|
||||
static
|
||||
public void main(String[] args) {
|
||||
Library lib = new Library();
|
||||
lib.getBookCount(true, true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">Kiwix Lib</string>
|
||||
</resources>
|
||||
@@ -1,259 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
* Copyright (C) 2017 Matthieu Gautier <mgautier@kymeria.fr>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _ANDROID_JNI_UTILS_H
|
||||
#define _ANDROID_JNI_UTILS_H
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
#if __ANDROID__
|
||||
#include <android/log.h>
|
||||
#define LOG(...) __android_log_print(ANDROID_LOG_ERROR, "kiwix", __VA_ARGS__)
|
||||
#else
|
||||
#define LOG(...)
|
||||
#endif
|
||||
|
||||
extern std::mutex globalLock;
|
||||
|
||||
template<typename T>
|
||||
void setPtr(JNIEnv* env, jobject thisObj, T* ptr)
|
||||
{
|
||||
jclass thisClass = env->GetObjectClass(thisObj);
|
||||
jfieldID fieldId = env->GetFieldID(thisClass, "nativeHandle", "J");
|
||||
env->SetLongField(thisObj, fieldId, reinterpret_cast<jlong>(ptr));
|
||||
}
|
||||
|
||||
template<typename T, typename ...Args>
|
||||
void allocate(JNIEnv* env, jobject thisObj, Args && ...args)
|
||||
{
|
||||
T* ptr = new T(std::forward<Args>(args)...);
|
||||
setPtr(env, thisObj, ptr);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T* getPtr(JNIEnv* env, jobject thisObj)
|
||||
{
|
||||
jclass thisClass = env->GetObjectClass(thisObj);
|
||||
jfieldID fidNumber = env->GetFieldID(thisClass, "nativeHandle", "J");
|
||||
return reinterpret_cast<T*>(env->GetLongField(thisObj, fidNumber));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void dispose(JNIEnv* env, jobject thisObj)
|
||||
{
|
||||
delete getPtr<T>(env, thisObj);
|
||||
}
|
||||
|
||||
#define METHOD0(retType, class, name) \
|
||||
JNIEXPORT retType JNICALL Java_org_kiwix_kiwixlib_##class##_##name( \
|
||||
JNIEnv* env, jobject thisObj)
|
||||
|
||||
#define METHOD(retType, class, name, ...) \
|
||||
JNIEXPORT retType JNICALL Java_org_kiwix_kiwixlib_##class##_##name( \
|
||||
JNIEnv* env, jobject thisObj, __VA_ARGS__)
|
||||
|
||||
inline jfieldID getHandleField(JNIEnv* env, jobject obj)
|
||||
{
|
||||
jclass c = env->GetObjectClass(obj);
|
||||
// J is the type signature for long:
|
||||
return env->GetFieldID(c, "nativeHandle", "J");
|
||||
}
|
||||
|
||||
inline jobjectArray createArray(JNIEnv* env, size_t length, const std::string& type_sig)
|
||||
{
|
||||
jclass c = env->FindClass(type_sig.c_str());
|
||||
return env->NewObjectArray(length, c, NULL);
|
||||
}
|
||||
|
||||
class Lock : public std::unique_lock<std::mutex>
|
||||
{
|
||||
public:
|
||||
Lock() : std::unique_lock<std::mutex>(globalLock) { }
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class LockedHandle;
|
||||
|
||||
template <class T>
|
||||
class Handle
|
||||
{
|
||||
protected:
|
||||
T* h;
|
||||
|
||||
public:
|
||||
Handle(T* h) : h(h){};
|
||||
|
||||
// No destructor. This must and will be handled by dispose method.
|
||||
|
||||
static LockedHandle<T> getHandle(JNIEnv* env, jobject obj)
|
||||
{
|
||||
jlong handle = env->GetLongField(obj, getHandleField(env, obj));
|
||||
return LockedHandle<T>(reinterpret_cast<Handle<T>*>(handle));
|
||||
}
|
||||
|
||||
static void dispose(JNIEnv* env, jobject obj)
|
||||
{
|
||||
auto lHandle = getHandle(env, obj);
|
||||
auto handle = lHandle.h;
|
||||
delete handle->h;
|
||||
delete handle;
|
||||
}
|
||||
friend class LockedHandle<T>;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct LockedHandle : public Lock {
|
||||
Handle<T>* h;
|
||||
LockedHandle(Handle<T>* h) : h(h) {}
|
||||
T* operator->() { return h->h; }
|
||||
T* operator*() { return h->h; }
|
||||
operator bool() const { return (h->h != nullptr); }
|
||||
operator T*() const { return h->h; }
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct JType { };
|
||||
|
||||
template<> struct JType<bool>{ typedef jboolean type_t; };
|
||||
template<> struct JType<int>{ typedef jint type_t; };
|
||||
template<> struct JType<long>{ typedef jlong type_t; };
|
||||
template<> struct JType<uint64_t> { typedef jlong type_t; };
|
||||
template<> struct JType<uint32_t> { typedef jlong type_t; };
|
||||
template<> struct JType<std::string>{ typedef jstring type_t; };
|
||||
template<> struct JType<std::vector<std::string>>{ typedef jobjectArray type_t; };
|
||||
|
||||
template<typename T>
|
||||
inline typename JType<T>::type_t c2jni(const T& val, JNIEnv* env) {
|
||||
return static_cast<typename JType<T>::type_t>(val);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline jboolean c2jni(const bool& val, JNIEnv* env) { return val ? JNI_TRUE : JNI_FALSE; }
|
||||
|
||||
template<>
|
||||
inline jstring c2jni(const std::string& val, JNIEnv* env)
|
||||
{
|
||||
return env->NewStringUTF(val.c_str());
|
||||
}
|
||||
|
||||
template<>
|
||||
inline jobjectArray c2jni(const std::vector<std::string>& val, JNIEnv* env)
|
||||
{
|
||||
auto array = createArray(env, val.size(), "java/lang/String");
|
||||
size_t index = 0;
|
||||
for (auto& elem: val) {
|
||||
auto jElem = c2jni(elem, env);
|
||||
env->SetObjectArrayElement(array, index++, jElem);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
template<typename T, typename U=T>
|
||||
struct CType { };
|
||||
|
||||
template<> struct CType<jboolean>{ typedef bool type_t; };
|
||||
template<> struct CType<jint>{ typedef int type_t; };
|
||||
template<> struct CType<jlong>{ typedef long type_t; };
|
||||
template<> struct CType<jstring>{ typedef std::string type_t; };
|
||||
|
||||
template<typename U>
|
||||
struct CType<jobjectArray, U>{ typedef std::vector<typename CType<U>::type_t> type_t; };
|
||||
|
||||
/* jni2c type conversion functions */
|
||||
template<typename T, typename U=T>
|
||||
inline typename CType<T>::type_t jni2c(const T& val, JNIEnv* env) {
|
||||
return static_cast<typename CType<T>::type_t>(val);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline bool jni2c(const jboolean& val, JNIEnv* env) { return val == JNI_TRUE; }
|
||||
|
||||
template<>
|
||||
inline std::string jni2c(const jstring& val, JNIEnv* env)
|
||||
{
|
||||
const char* chars = env->GetStringUTFChars(val, 0);
|
||||
std::string ret(chars);
|
||||
env->ReleaseStringUTFChars(val, chars);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
inline typename CType<jobjectArray, U>::type_t jni2c(const jobjectArray& val, JNIEnv* env)
|
||||
{
|
||||
jsize length = env->GetArrayLength(val);
|
||||
typename CType<jobjectArray, U>::type_t v(length);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < length; i++) {
|
||||
U obj = (U) env->GetObjectArrayElement(val, i);
|
||||
auto cobj = jni2c<U>(obj, env);
|
||||
v.push_back(cobj);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
/* Method to deal with variable passed by reference */
|
||||
inline std::string getStringObjValue(const jobject obj, JNIEnv* env)
|
||||
{
|
||||
jclass objClass = env->GetObjectClass(obj);
|
||||
jfieldID objFid = env->GetFieldID(objClass, "value", "Ljava/lang/String;");
|
||||
jstring jstr = (jstring)env->GetObjectField(obj, objFid);
|
||||
return jni2c(jstr, env);
|
||||
}
|
||||
inline 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));
|
||||
}
|
||||
|
||||
inline 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);
|
||||
}
|
||||
|
||||
inline void setBoolObjValue(const bool value, const jobject obj, JNIEnv* env)
|
||||
{
|
||||
jclass objClass = env->GetObjectClass(obj);
|
||||
jfieldID objFid = env->GetFieldID(objClass, "value", "Z");
|
||||
env->SetIntField(obj, objFid, c2jni(value, env));
|
||||
}
|
||||
|
||||
inline void setDaiObjValue(const std::string& filename, const long offset,
|
||||
const jobject obj, JNIEnv* env)
|
||||
{
|
||||
jclass objClass = env->GetObjectClass(obj);
|
||||
jfieldID filenameFid = env->GetFieldID(objClass, "filename", "Ljava/lang/String;");
|
||||
env->SetObjectField(obj, filenameFid, c2jni(filename, env));
|
||||
jfieldID offsetFid = env->GetFieldID(objClass, "offset", "J");
|
||||
env->SetLongField(obj, offsetFid, offset);
|
||||
}
|
||||
|
||||
#endif // _ANDROID_JNI_UTILS_H
|
||||
10
static/ft_opensearchdescription.xml
Normal file
10
static/ft_opensearchdescription.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
|
||||
<ShortName>Fulltext articles search</ShortName>
|
||||
<Description>Search for articles in the Library.</Description>
|
||||
<Url type="application/atom+xml;profile=opds-catalog"
|
||||
xmlns:atom="http://www.w3.org/2005/Atom"
|
||||
xmlns:k="http://kiwix.org/opensearchextension/1.0"
|
||||
indexOffset="0"
|
||||
template="{{root}}/search?format=xml&pattern={searchTerms}&books.filter.lang={language?}&books.name={k:name?}&pageLength={count?}&start={startIndex?}"/>
|
||||
</OpenSearchDescription>
|
||||
31
static/generate_i18n_resources_list.py
Executable file
31
static/generate_i18n_resources_list.py
Executable file
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2020 Matthieu Gautier <mgautier@kymeria.fr>
|
||||
#
|
||||
# This file is part of libkiwix.
|
||||
#
|
||||
# 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
|
||||
# (at your option) 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, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
script_path = Path(__file__)
|
||||
|
||||
resource_file = script_path.parent / "i18n_resources_list.txt"
|
||||
translation_dir = script_path.parent / "i18n"
|
||||
|
||||
json_files = translation_dir.glob("*.json")
|
||||
with open(resource_file, 'w', encoding="utf-8") as f:
|
||||
for json in sorted(translation_dir.glob("*.json")):
|
||||
if json.name == "qqq.json":
|
||||
continue
|
||||
f.write(str(json.relative_to(script_path.parent)) + '\n')
|
||||
33
static/i18n/ar.json
Normal file
33
static/i18n/ar.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Asma",
|
||||
"Ravan",
|
||||
"محمد أحمد عبد الفتاح"
|
||||
]
|
||||
},
|
||||
"name": "الإنجليزية",
|
||||
"no-such-book": "لا يوجد مثل هذا الكتاب: {{BOOK_NAME}}",
|
||||
"too-many-books": "طلب العديد من الكتب {{NB_BOOKS}} حيث الحد {{LIMIT}}",
|
||||
"no-book-found": "لا يوجد كتاب يطابق معايير الاختيار",
|
||||
"url-not-found": "لم يتم العثور على عنوان URL المطلوب \"{{url}}\" على هذا الخادم.",
|
||||
"suggest-search": "قم بإجراء بحث عن النص الكامل لـ <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
||||
"random-article-failure": "مع الأسف! فشل اختيار مقال عشوائي :(",
|
||||
"invalid-raw-data-type": "{{DATATYPE}} ليس طلبًا صالحًا للمحتوى الأولي.",
|
||||
"no-value-for-arg": "لم يتم تقديم قيمة للوسيطة {{ARGUMENT}}",
|
||||
"no-query": "لم يتم تقديم ملخص.",
|
||||
"raw-entry-not-found": "لا يمكن العثور على إدخال {{DATATYPE}} {{ENTRY}}",
|
||||
"400-page-title": "طلب غير صالح",
|
||||
"400-page-heading": "طلب غير صالح",
|
||||
"404-page-title": "المحتوى غير موجود",
|
||||
"404-page-heading": "لم يتم العثور عليه",
|
||||
"500-page-title": "خطأ في الخادم الداخلي",
|
||||
"500-page-heading": "خطأ في الخادم الداخلي",
|
||||
"fulltext-search-unavailable": "البحث عن النص الكامل غير متاح",
|
||||
"no-search-results": "محرك البحث عن النص الكامل غير متاح لهذا المحتوى.",
|
||||
"library-button-text": "اذهب لصفحة الترحيب",
|
||||
"home-button-text": "انتقل إلى الصفحة الرئيسية لـ \"{{BOOK_TITLE}}\"",
|
||||
"random-page-button-text": "اذهب إلى صفحة عشوائية",
|
||||
"searchbox-tooltip": "بحث \"{{BOOK_TITLE}}\"",
|
||||
"confusion-of-tongues": "قد يشارك في البحث كتابان أو أكثر بلغات مختلفة، مما قد يؤدي إلى نتائج محيرة."
|
||||
}
|
||||
14
static/i18n/bn.json
Normal file
14
static/i18n/bn.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"আফতাবুজ্জামান"
|
||||
]
|
||||
},
|
||||
"name": "বাংলা",
|
||||
"404-page-heading": "পাওয়া যায়নি",
|
||||
"500-page-title": "অভ্যন্তরীণ সার্ভার ত্রুটি",
|
||||
"500-page-heading": "অভ্যন্তরীণ সার্ভার ত্রুটি",
|
||||
"library-button-text": "স্বাগত পাতায় চলুন",
|
||||
"home-button-text": "'{{BOOK_TITLE}}'-এর প্রধান পাতায় চলুন",
|
||||
"searchbox-tooltip": "'{{BOOK_TITLE}}' অনুসন্ধান করুন"
|
||||
}
|
||||
31
static/i18n/cs.json
Normal file
31
static/i18n/cs.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"Spotter"
|
||||
]
|
||||
},
|
||||
"name": "Čeština",
|
||||
"suggest-full-text-search": "obsahující '{{{SEARCH_TERMS}}}'...",
|
||||
"no-such-book": "Žádná taková kniha: {{BOOK_NAME}}",
|
||||
"too-many-books": "Bylo požadováno příliš mnoho knih ({{NB_BOOKS}}), kde je limit {{LIMIT}}",
|
||||
"no-book-found": "Výběrovým kritériím nevyhovuje žádná kniha",
|
||||
"url-not-found": "Požadovaná adresa URL \"{{url}}\" nebyla na tomto serveru nalezena.",
|
||||
"suggest-search": "Proveďte fulltextové vyhledávání <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
||||
"random-article-failure": "Jejda! Nepodařilo se vybrat náhodný článek :(",
|
||||
"invalid-raw-data-type": "{{DATATYPE}} není platný požadavek na nezpracovaný obsah.",
|
||||
"no-value-for-arg": "Pro argument {{ARGUMENT}} nebyla zadána žádná hodnota",
|
||||
"no-query": "Nebyl poskytnut žádný dotaz.",
|
||||
"raw-entry-not-found": "Nelze najít položku {{DATATYPE}} {{ENTRY}}",
|
||||
"400-page-title": "Neplatný požadavek",
|
||||
"400-page-heading": "Neplatný požadavek",
|
||||
"404-page-title": "Obsah nenalezen",
|
||||
"404-page-heading": "Nenalezeno",
|
||||
"500-page-title": "Interní chyba serveru",
|
||||
"500-page-heading": "Interní chyba serveru",
|
||||
"fulltext-search-unavailable": "Fulltextové vyhledávání není k dispozici",
|
||||
"no-search-results": "Fulltextový vyhledávač není pro tento obsah dostupný.",
|
||||
"library-button-text": "Přejít na uvítací stránku",
|
||||
"home-button-text": "Přejít na hlavní stránku '{{BOOK_TITLE}}'",
|
||||
"random-page-button-text": "Přejít na náhodně vybranou stránku",
|
||||
"searchbox-tooltip": "Hledat '{{BOOK_TITLE}}'"
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user