Compare commits

...

136 Commits
4.0.0 ... 6.0.2

Author SHA1 Message Date
Matthieu Gautier
2ea0e5bab0 New version 6.0.2 2019-08-22 16:05:35 +02:00
Matthieu Gautier
b92a4b2e04 Correctly set the groupId in the pom file. 2019-08-22 16:03:46 +02:00
Matthieu Gautier
ef758aa0a6 New version 6.0.1 2019-08-21 15:38:39 +02:00
Matthieu Gautier
b8e5de6a47 Merge pull request #262 from kiwix/pom_gradle
Generate the pom file from gradle
2019-08-21 14:28:52 +02:00
Matthieu Gautier
aed808ae5e Generate the pom file from gradle 2019-08-21 14:22:07 +02:00
Matthieu Gautier
e25b27b354 Fix use of strtok on windows. (#261)
Fix use of strtok on windows.
2019-08-19 17:42:55 +02:00
Matthieu Gautier
12a93c3e29 Fix use of strtok on windows.
On windows, strtok_r is called strto_s.
2019-08-19 17:35:22 +02:00
Matthieu Gautier
b3abb3a35b New version 6.0.0 (#260)
New version 6.0.0
2019-08-19 17:27:20 +02:00
Matthieu Gautier
8c3b51b81c New version 6.0.0 2019-08-19 17:18:16 +02:00
Matthieu Gautier
5f81fab1e2 Specs says ZIM favicon should be at '-/favicon', should be tried… (#257)
Specs says ZIM favicon should be at '-/favicon', should be tried first
2019-08-19 17:17:34 +02:00
Emmanuel Engelhart
cea201b394 Specs says ZIM favicon should be at '-/favicon', should be tried first 2019-08-19 16:27:55 +02:00
Matthieu Gautier
d0f6fadda2 Add JNIKiwixString constructors (#256)
Add JNIKiwixString constructors
2019-08-14 10:38:36 +02:00
mhutti1
8672aede97 Add JNIKiwixString constructors 2019-08-13 17:55:48 +02:00
Matthieu Gautier
8edac07fcc Merge pull request #255 from kiwix/JNI_checkUrl
Add a JNI method to check if an url exists and is a redirection.
2019-08-13 11:30:36 +02:00
Matthieu Gautier
6ab52e2b9e Add a JNI method to check if an url exists and is a redirection. 2019-08-13 11:19:39 +02:00
Matthieu Gautier
858c2fecbe Fix nameMapper initialization. (#254)
Fix nameMapper initialization.
2019-08-13 11:19:21 +02:00
Matthieu Gautier
d90a27af11 Fix nameMapper initialization. 2019-08-12 16:16:26 +02:00
Matthieu Gautier
a8668db2fe Make the gtest dependency optional. (#253)
Make the gtest dependency optional.
2019-08-12 15:59:31 +02:00
Matthieu Gautier
3e3e1683f5 Make the gtest dependency optional.
This is needed to build in flatpak.
2019-08-12 15:31:16 +02:00
Matthieu Gautier
1e0c9a120a Do not embed the gtest dependency. (#252)
Do not embed the gtest dependency.
2019-08-12 15:01:40 +02:00
Matthieu Gautier
ca27ddf41a Do not embed the gtest dependency.
We have a special wrap file for that.
2019-08-12 14:55:39 +02:00
Matthieu Gautier
99dcdd849a Jni fix (#251)
Jni fix
2019-08-12 14:55:30 +02:00
Matthieu Gautier
a65e192f0f [JNI] Allow android to know that an article is a redirect.
Android need to handle the redirection by doing a redirection in the web
view, not by providing the content of the targeted article.

This is already what we do in kiwix-serve or ios.

The API should be far better by returning a Entry but for now,
we just change the given url if the article is a redirection.
2019-08-12 13:03:20 +02:00
Matthieu Gautier
513cc9c90f [JNI] Fix log typo. 2019-08-12 12:43:52 +02:00
Matthieu Gautier
231ae095f6 Correctly set that book's path is valid when updating it from a reader. 2019-08-12 12:43:52 +02:00
Matthieu Gautier
9a0c6da018 Library construction doesn't take argument 2019-08-12 12:43:52 +02:00
Matthieu Gautier
52299ef767 Fix computeAbsolutePath.
Correctly delete the duplicated string.
Use strtok_r to be thread safe.
2019-08-12 12:43:52 +02:00
Matthieu Gautier
44bec86f31 Ci test (#248)
Ci test
2019-08-12 12:39:51 +02:00
Matthieu Gautier
c4963268ba Fix regexTools.
The buildMatcher must not take a rvalue as it will keep a reference
to it.
2019-08-12 12:05:51 +02:00
Matthieu Gautier
fee2da57e6 Add tests of regex tools. 2019-08-12 12:05:51 +02:00
Matthieu Gautier
0154131d74 Run test on the CI. 2019-08-12 12:05:51 +02:00
Matthieu Gautier
3fa503efb1 [JNI] Fix implementation of setDataDirectory. (#247)
[JNI] Fix implementation of setDataDirectory.
2019-08-11 15:14:24 +02:00
Matthieu Gautier
73a29ccb24 [JNI] Fix implementation of setDataDirectory.
Now that setDataDirectory is a static method, we need to take an
jclass instead of a jobject.
2019-08-11 15:09:26 +02:00
Matthieu Gautier
1e94665c07 Http integration (#246)
Http integration
2019-08-11 11:54:09 +02:00
Matthieu Gautier
7060afae66 [SERVER] Catch any error and return a 500 response instead of crashing.
The server will be running some code on the behalf of the calling code.
We really don't what to crash the library (and the binary) because
of a wrong request.
2019-08-11 11:30:43 +02:00
Matthieu Gautier
de819dff25 Fix few errors in static files. 2019-08-11 11:30:43 +02:00
Matthieu Gautier
4d3df4e889 Add JNI wrapper around the library and the server. 2019-08-11 11:30:43 +02:00
Matthieu Gautier
cd050ddcc8 Use camelCase. 2019-08-11 11:30:43 +02:00
Matthieu Gautier
635d4438e5 Make the server take a pointer to the library instead of a reference. 2019-08-11 11:30:43 +02:00
Matthieu Gautier
ce09375c6c Reduce complexity of handle_search. 2019-08-11 11:30:43 +02:00
Matthieu Gautier
fae0918f49 Reduce complexity of handle_catalog. 2019-08-11 11:30:43 +02:00
Matthieu Gautier
d90f8b0f05 Add a name_mapper mapping the HumanReadable name to the id. 2019-08-11 11:30:43 +02:00
Matthieu Gautier
abb5db0193 Clean up includes in server.cpp 2019-08-11 11:30:43 +02:00
Matthieu Gautier
e452f5cf36 Ensure that the root is correctly formatted. 2019-08-11 11:30:43 +02:00
Matthieu Gautier
c890e1c87e Add support of a binding to a specific ip address. 2019-08-11 11:30:43 +02:00
Matthieu Gautier
e5ef3780db Rename humanReadableBookId to bookName.
`humanReadableBookId` is a bit long and doesn't represent what it is
(this is not a id).
`bookName` is far better.
2019-08-11 11:30:43 +02:00
Matthieu Gautier
2aeed65205 Better handling of invalid request.
Do no crash if we can get a book or a reader for the requested content.
2019-08-11 11:30:43 +02:00
Matthieu Gautier
c1faf55ae8 Introduce the server functionality in the kiwix-lib.
This code is mainly copied from kiwix-tools.

But :
- Move all the response thing in a new class Response.
- This Response class is responsible to handle all the MHD_response
  configuration. This way the server handle a global object and do
  no call to MHD_response*
- Server uses a lot more the templating system with mustache.
  There are still few regex operations (because we need to
  change a content already existing).
- By default, the server serves the content using the id as name.
- Server creates a new Searcher per request. This way, we don't have
  to protect the search for multi-thread and we can do several search
  in the same time.
- search results are not cached, this will allow future improvement in the
  search algorithm.
- the home page is not cached.
- Few more verbose information (number of request served, time spend to
  respond to a request).

TOOD:
 - Readd interface selection.
 - Do Android wrapper.
 - Remove KiwixServer (who use a external process).
 -
2019-08-11 11:30:43 +02:00
Matthieu Gautier
64dfea2547 Move the search html renderer in a different class than the searcher.
This is two different functionnalies, we don't need to polute the searcher
api with things to render the html.
2019-08-11 10:19:48 +02:00
Matthieu Gautier
cca5980b27 Remove limitation of the search len in the Searcher.
The limitation should be made elsewhere (the code using the searcher).
2019-08-11 10:19:48 +02:00
Matthieu Gautier
68a768f58a Introduce the new class NameMapper.
This class is used to map an id (uuid) to a name (potentially human
readable).

This will be use by the server to or renderer to use a different "name"
than the id.

The default NameMapper provided use the id as a name.
2019-08-11 10:19:48 +02:00
Matthieu Gautier
ce8fff0b42 Make the library create the reader. 2019-08-11 10:19:48 +02:00
Matthieu Gautier
e56335109c Make appendToFirstOccurence take argument by reference. 2019-08-11 10:19:48 +02:00
Matthieu Gautier
656bf183b7 Make getHumanReadableFromPath method const. 2019-08-11 10:19:48 +02:00
Matthieu Gautier
cbe8e20118 Fix include in otherTools.h 2019-08-11 10:19:48 +02:00
Matthieu Gautier
e013d38cc6 Add startWith function in stringTools.h 2019-08-11 10:19:48 +02:00
Matthieu Gautier
6234457920 Fix include in stringTools.h 2019-08-11 10:19:48 +02:00
Matthieu Gautier
72223d69fe Fix include in pathTools.h 2019-08-10 11:02:23 +02:00
Matthieu Gautier
ddeb862395 Add getMimeTypeForFile in pathTool. 2019-08-10 10:59:13 +02:00
Matthieu Gautier
61c28f0e3d Make the regexTool thread safe. 2019-08-10 10:59:13 +02:00
Matthieu Gautier
c8e719101e Add a new tool Lock to lock a mutex.
And automaticlly unlock it when the `Lock` got out of scope.
2019-08-10 10:59:13 +02:00
Kelson
1b8e7849bd Merge pull request #244 from kiwix/badges
Add badges to the README
2019-07-31 18:09:46 +02:00
Emmanuel Engelhart
efce01aec4 Add badges to the README 2019-07-31 18:08:09 +02:00
Matthieu Gautier
d55976271f Merge pull request #243 from kiwix/new_version
New version 5.2.0
2019-07-30 18:01:31 +02:00
Matthieu Gautier
5eeccbae21 New version 5.2.0 2019-07-30 17:40:46 +02:00
Matthieu Gautier
77770e1bd4 Merge pull request #105 from kiwix/verbose_search
Set the verbosity of the underlying zim::Search.
2019-07-30 17:33:01 +02:00
Matthieu Gautier
654a8e304c Set the verbosity of the underlying zim::Search. 2019-07-30 17:03:44 +02:00
Matthieu Gautier
ed7a8343fa Merge pull request #241 from kiwix/relinker
#239 ReLinker should be used to load library
2019-07-30 16:58:22 +02:00
Sean Mac Gillicuddy
5adf7891cc #239 ReLinker should be used to load library 2019-07-29 18:31:57 +02:00
Kelson
93c404a952 Merge pull request #238 from kiwix/http-https
Replace HTTP link by HTTPs one
2019-07-23 16:06:56 +02:00
Kelson
f33f4eb381 Replace HTTP links by HTTPs one 2019-07-23 15:19:01 +02:00
Matthieu Gautier
4bece7017f Merge pull request #231 from kiwix/macgills/feature/auto-publish-android-library
Create library and set up publishing task
2019-07-23 15:16:56 +02:00
Sean Mac Gillicuddy
cab8eec00b create library to build kiwix-lib for android 2019-07-23 11:23:20 +02:00
Matthieu Gautier
f41155e060 Merge pull request #236 from kiwix/return-port-kiwix-serve
kiwix local server port can be set at the constructor
2019-06-27 17:59:14 +02:00
luddens
c17abdae5e port can be set with the constructor
add a method to return the port
2019-06-27 16:50:22 +02:00
Matthieu Gautier
8164b4dadc Merge pull request #234 from kiwix/listBook_api
List book api
2019-06-26 17:32:00 +02:00
Matthieu Gautier
31c9375a3a Better API to filter books in a library.
Instead of having a single method `listBooksIds` that tries to be
exhaustive about all the filter and sort option, split the method in
two separated methods `filter` and `sort`.

The `filter` method takes a `Filter` object that represent on what we are
filtering. This object has to be construct before calling `filter`.

```cpp
Filter filter;
filter.query("Astring");
filter.acceptTags({"nopic"});
// return all book in eng and with "Astring" in the tile or description".
library.filter(filter);
//equivalent to
library.listBooksIds(ALL, UNSORTED, "Astring", "", "", "", {"nopic"});
// or better
library.filter(Filter().query("Astring").acceptTags({"nopic"}));
```

The method `listBooksIds` has been marked as deprecated.

Add a small test on the library.
2019-06-26 16:41:01 +02:00
Matthieu Gautier
21592af8b2 Remove invalid method declaration in kiwix::Manager.
Those methods were totally obsolete. They were even not implemented.
2019-06-26 16:41:01 +02:00
Matthieu Gautier
15b3ed24b7 Merge pull request #235 from kiwix/fix_exit
Force the exit of the forked process.
2019-06-25 18:53:31 +02:00
Matthieu Gautier
3d689e790b Force the exit of the forked process.
The `exit` function will call the functions registered.
Qt may (and does) register function and may (and does) hangup waiting
for some mutex to be free.

Here we are in a forked process and we want to process to exit without
doing any cleanup (because we are a clone of a process that will continue
and we don't want to mess it).
2019-06-25 16:52:46 +02:00
Matthieu Gautier
e740a511c6 Merge pull request #233 from kiwix/kiwix-serve-is-running
add isRunning method to check if the local server is running
2019-06-24 15:16:31 +02:00
luddens
87f5b56b72 add isRunning method 2019-06-24 15:05:20 +02:00
Matthieu Gautier
cfdd634c3c Merge pull request #228 from kiwix/fix-crash-suggestion
Fix crash searchbar's suggestions
2019-06-24 11:54:23 +02:00
luddens
df76db4f47 Fix crash searchbar's suggestions
The suggestion's list was filled without checking if the current suggestion
was wrong which caused crash in this case.
2019-06-24 11:48:14 +02:00
Matthieu Gautier
15f7eaa400 Merge pull request #232 from kiwix/fix_fork_wait_child
Fix waitpid option.
2019-06-24 11:47:56 +02:00
Matthieu Gautier
6fe6f88b10 Fix waitpid option.
`WEXITED` is an option for the `waitid` function not `waitpid`.

We don't need to pass a option to `waitpid`.
2019-06-24 11:34:03 +02:00
Matthieu Gautier
687a15877a Merge pull request #230 from kiwix/fix_kiwix-serve
Remove unnecessary include.
2019-06-12 14:33:33 +02:00
Matthieu Gautier
4e746916a7 Remove unnecessary include.
And unistd.h is not available on windows.
2019-06-12 14:27:15 +02:00
Matthieu Gautier
7c1d051305 Merge pull request #221 from kiwix/kiwix-serve
kiwix-serve integration in kiwix-lib
2019-06-11 10:54:35 +02:00
luddens
5dc96d7145 kiwix-serve integration 2019-06-11 10:35:16 +02:00
Matthieu Gautier
3721d7439d Merge pull request #227 from kiwix/new_version
New version 5.1.0
2019-05-28 23:47:43 +02:00
Matthieu Gautier
701829ae11 New version 5.1.0 2019-05-28 17:40:09 +02:00
Matthieu Gautier
91a0e100cc Merge pull request #226 from kiwix/new_api_libzim
suggestions method return a unique_ptr instead of a raw pointer.
2019-05-28 16:48:04 +02:00
Matthieu Gautier
1f672056a9 [CI] Build on xenial. 2019-05-28 15:12:53 +02:00
Matthieu Gautier
48347825a9 suggestions method return a unique_ptr instead of a raw pointer. 2019-05-28 15:08:16 +02:00
Kelson
d0d7e11093 Merge pull request #223 from kiwix/tags-opds-stream
Add tags to OPDS stream
2019-05-15 09:50:24 +02:00
luddens
519dd110f5 add tags in opds stream 2019-05-14 10:51:22 +02:00
Matthieu Gautier
568b2b0e0c Merge pull request #218 from kiwix/downloadPauseResume
Add pause and unpause functions for aria2
2019-05-13 10:41:29 +02:00
luddens
491b6d655b add remove fct with aria2 2019-05-13 10:30:19 +02:00
luddens
ec8f1ffe9c Add pause and unpause functions for aria2
This functions enable to stop and resume download with aria2.

The Downloader's constructor now checks the paused downloads with the
function "tellWaiting()" to get them at the start of kiwix-desktop.
2019-05-13 10:30:19 +02:00
luddens
12ffad55f7 update download's status when we get it 2019-05-13 10:30:19 +02:00
Matthieu Gautier
8698407e1e Merge pull request #219 from kiwix/fix_sleep_windows
Revert Do not use this_thread::sleep.
2019-05-03 15:54:49 +02:00
Matthieu Gautier
0b2c9fa7ce Revert Do not use this_thread::sleep.
We already have a custom sleep method.
2019-05-03 15:38:47 +02:00
Matthieu Gautier
f93c31754a Merge pull request #216 from kiwix/thread_win
Do not use this_thread::sleep.
2019-04-17 17:56:56 +02:00
Matthieu Gautier
fe682f855a Do not use this_thread::sleep.
Mingw doesn't implement it. So, we should not use it.
I suppose that it was working before because mingw package for debian trusty
simply no provides a "thread" header.
We may face to include the native "thread" header.
2019-04-17 15:44:14 +00:00
Matthieu Gautier
9321c589c8 Merge pull request #215 from kiwix/new_version
New version 5.0.0
2019-04-15 17:14:27 +02:00
Matthieu Gautier
ee330398b2 New version 5.0.0 2019-04-15 17:07:07 +02:00
Matthieu Gautier
ec4525fd47 Merge pull request #214 from kiwix/cyrillemoureaux-fix-self-redirect
Detect and reject infinite redirect loops.
2019-04-15 17:01:43 +02:00
cyrillemoureaux
e1980d190f Detect and reject infinite redirect loops.
This will prevent kiwix/kiwix-tools#207.
2019-04-15 16:55:48 +02:00
Matthieu Gautier
03b1750313 Merge pull request #212 from xvitaly/kill-rpath
Removed Rpath from compiled binaries
2019-04-15 16:54:20 +02:00
Vitaly Zaitsev
28f144796d Removed Rpath from compiled binaries.
Rpath is not needed to run installed binaries. Most of GNU/Linux
distributions strictly forbid it.

Signed-off-by: Vitaly Zaitsev <vitaly@easycoding.org>
2019-04-15 16:09:53 +02:00
Matthieu Gautier
6c27743663 Merge pull request #213 from kiwix/fix_include_windows
Remove unused include unistd.h
2019-04-15 10:56:21 +02:00
Matthieu Gautier
e7e88617d5 Remove unused include unistd.h
We don't use it anymore and it is not present on Windows.
2019-04-15 10:50:26 +02:00
Matthieu Gautier
4e90f3614d Merge pull request #211 from kiwix/makeTmpDirectory
Rewrite makeTmpDirectory to not use Uuid methods on windows.
2019-04-04 17:07:17 +02:00
Matthieu Gautier
f8522fb26e Remove getNetworkInterfaces and getBestPublicIp function.
They are not used at all and the windows version need some extra libs
that complexify the code.

Remove them for now. If it happens that they are needed, we will readd
them.
2019-04-04 17:00:45 +02:00
Matthieu Gautier
938e2a81c1 Rewrite makeTmpDirectory to not use Uuid methods on windows.
`UuidCreate`, `UuidToString` and `RpcStringFree` need special library
on windows. Lets not use them.
2019-04-04 16:11:36 +02:00
Matthieu Gautier
d9e72685ba Merge pull request #210 from kiwix/no_error_message
Remove error message when trying to opening a wrong zim file.
2019-04-02 16:02:47 +02:00
Matthieu Gautier
d40982f760 Remove error message when trying to opening a wrong zim file.
The library should not print a message.

Fix kiwix/kiwix-tools#235
2019-04-02 15:28:46 +02:00
Matthieu Gautier
42b7692f9b Merge pull request #208 from kiwix/new_version
Version 4.1.0
2019-03-19 16:35:57 +01:00
Matthieu Gautier
cd654b9cae Version 4.1.0 2019-03-19 16:26:14 +01:00
Matthieu Gautier
15dafcaa80 Force use of meson 0.49.2 2019-03-19 15:27:03 +01:00
Matthieu Gautier
f71f2935e0 Merge pull request #204 from kiwix/library_filter_tag
Allow the library to be filtered by tags.
2019-03-07 17:19:31 +01:00
Matthieu Gautier
c6254d9504 Allow the library to be filtered by tags.
This add an argument to `listBooksIds` to filter by tags.
So, this is an API break.
2019-03-07 17:08:39 +01:00
Matthieu Gautier
f1a046757e Merge pull request #203 from kiwix/fix_lang_mapping
Fix the language mapping.
2019-03-05 18:43:00 +01:00
Matthieu Gautier
93af3aa2d1 Fix the language mapping.
The previous mapping was taken from an unknown (:/) source.

The new mapping is generated with a script taking
https://www.loc.gov/standards/iso639-2/php/code_list.php as source.

The source list is sanitized to keep only language for which we
(http://library.kiwix.org/) have content.
2019-03-05 17:53:37 +01:00
Matthieu Gautier
336a987bb2 Merge pull request #202 from kiwix/update_readme_mustache
Add information about mustache dependency in the README.
2019-03-04 17:03:56 +01:00
Matthieu Gautier
72b4af4d65 Add information about mustache dependency in the README. 2019-03-04 14:26:40 +01:00
Matthieu Gautier
9aa1c65d7a Merge pull request #200 from kiwix/new_version
New version 4.0.1
2019-02-22 11:18:55 +01:00
Matthieu Gautier
ad6b20a530 New version 4.0.1 2019-02-22 10:29:16 +01:00
Matthieu Gautier
c1d04cc5b5 Merge pull request #199 from kiwix/fix_warning_android
Correctly initialize variable.
2019-02-19 14:15:20 +01:00
Matthieu Gautier
af9734c87f Correctly initialize variable. 2019-02-19 14:05:37 +01:00
Matthieu Gautier
a7a0798f99 Merge pull request #198 from kiwix/use_correct_dep_archive
Use new xz archive.
2019-02-19 14:05:08 +01:00
Matthieu Gautier
0154fdd190 Use new xz archive. 2019-02-19 13:31:26 +01:00
Matthieu Gautier
788d16ec01 Merge pull request #197 from kiwix/ensure_path_abs
Ensure path abs
2019-02-07 15:55:51 +01:00
Matthieu Gautier
35d812a5f7 Ensure the book's path is absolute.
We must use absolute path whenever possible.
Relative path has sense only related to the "interaction" with the user
(current directory, library location, ...).
2019-02-07 15:22:33 +01:00
Matthieu Gautier
432f9c30a3 Remove unused variable url. 2019-02-07 15:20:18 +01:00
439 changed files with 14689 additions and 154133 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.idea/
*.swp

View File

@@ -1,6 +1,6 @@
language: cpp
dist: trusty
sudo: false
dist: xenial
sudo: true
cache: ccache
before_install:
- PATH=$PATH:$HOME/bin

View File

@@ -1,3 +1,68 @@
kiwix-lib 6.0.2
===============
* Correctly set the groupId in the pom file.
kiwix-lib 6.0.1
===============
* Generate the pom file for android/maven
kiwix-lib 6.0.0
===============
* Move the server code in kiwix-lib (from kiwix-serve).
* Add unit test on regex functions.
* Fix computerAbsolutePath (thread safe, memory leak).
* Correctly set the book's path as valid if we construct the book from a
reader.
* [JNI] Add a method to know if a article is a redirection.
* Do not embed the gtest dependency.
* [JNI] Add a constructor to JNIKiwixString.
* Change order of search of the favicon urls.
* Clean a lot of unecessary includes in headers. (potential "API break")
kiwix-lib 5.2.0
===============
* kiwix-serve integration (as a seperated process).
* Fix crash in the suggestion search.
* Better API to filter the library books.
* New kiwix-lib application for android. (.aar)
* Use ReLinker to link with libkiwix.so in android.
* Correctly set the verbosity of zim search.
kiwix-lib 5.1.0
===============
* Add function to pause, resume and stop downloads.
* Add zim's tags in the opds stream.
* Addapt to new libzim 5.0.0 API.
kiwix-lib 5.0.0
===============
* Remove error message when trying to open an wrong zim file.
* Rewrite `makeTmpDirectory` to not use uuid functions on windows.
* [API break] Remove `getNetworkInterfaces` and `getBestPublicIp`.
* Remove rpath
* Detect infinite (and too long) redirect loops.
kiwix-lib 4.1.0
===============
* Allow the library to be filtered by tags.
* Fix language mapping.
* Update README about mustache dependency.
kiwix-lib 4.0.1
===============
* Fix "maybe uninitialize variable" issue.
* Ensure path are stored correctly (absolute path) in the library.
* [CI] Use the new deps archive xz
kiwix-lib 4.0.0
===============

View File

@@ -4,6 +4,11 @@ Kiwix library
The Kiwix library provides the Kiwix software core. It contains the
code shared by all Kiwix ports (Windows, Linux, OSX, Android, ...).
[![Download](https://api.bintray.com/packages/kiwix/kiwix/kiwixlib/images/download.svg)](https://bintray.com/kiwix/kiwix/kiwixlib/_latestVersion)
[![Build Status](https://travis-ci.org/kiwix/kiwix-lib.svg?branch=master)](https://travis-ci.org/kiwix/kiwix-lib)
[![CodeFactor](https://www.codefactor.io/repository/github/kiwix/kiwix-lib/badge)](https://www.codefactor.io/repository/github/kiwix/kiwix-lib)
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
Disclaimer
----------
@@ -27,14 +32,13 @@ The Kiwix library relies on many third parts software libraries. They
are prerequisites to the Kiwix library compilation. Following
libraries need to be available:
* ICU ................................... http://site.icu-project.org/
(package libicu-dev on Ubuntu)
* ZIM ........................................ http://www.openzim.org/
(package libzim-dev on Ubuntu)
* Pugixml ........................................ http://pugixml.org/
(package libpugixml-dev on Ubuntu)
* libaria2 .................................. https://aria2.github.io/
(no package on Ubuntu)
* [ICU](https://site.icu-project.org/) (package `libicu-dev` on Ubuntu)
* [ZIM](https://openzim.org/) (package `libzim-dev` on Ubuntu)
* [Pugixml](https://pugixml.org/) (package `libpugixml-dev` on Ubuntu)
* [Aria2](https://aria2.github.io/) (package `aria2` on Ubuntu)
* [Mustache](https://github.com/kainjow/Mustache) (Just copy the
header `mustache.hpp` somewhere it can be found by the compiler and/or
set CPPFLAGS with correct `-I` option)
These dependencies may or may not be packaged by your operating
system. They may also be packaged but only in an older version. The
@@ -43,13 +47,13 @@ In the worse case, you will have to download and compile bleeding edge
version by hand.
If you want to install these dependencies locally, then use the
kiwix-lib directory as install prefix.
`kiwix-lib` directory as install prefix.
Environment
-------------
The Kiwix library builds using [Meson](http://mesonbuild.com/) version
0.39 or higher. Meson relies itself on Ninja, pkg-config and few other
The Kiwix library builds using [Meson](https://mesonbuild.com/) version
0.43 or higher. Meson relies itself on Ninja, pkg-config and few other
compilation tools.
Install first the few common compilation tools:
@@ -58,14 +62,16 @@ Install first the few common compilation tools:
* Pkg-config
These tools should be packaged if you use a cutting edge operating
system. If not, have a look to the "Troubleshooting" section.
system. If not, have a look to the [Troubleshooting](#Troubleshooting)
section.
Compilation
-----------
Once all dependencies are installed, you can compile the Kiwix library
with:
```
```bash
meson . build
ninja -C build
```
@@ -83,31 +89,32 @@ Installation
If you want to install the Kiwix library and the headers you just have
compiled on your system, here we go:
```
```bash
ninja -C build install
```
You might need to run the command as root (or using 'sudo'), depending
You might need to run the command as root (or using `sudo`), depending
where you want to install the libraries. After the installation
succeeded, you may need to run ldconfig (as root).
succeeded, you may need to run `ldconfig` (as root).
Uninstallation
------------
If you want to uninstall the Kiwix library:
```
```bash
ninja -C build uninstall
```
Like for the installation, you might need to run the command as root
(or using 'sudo').
(or using `sudo`).
Troubleshooting
---------------
If you need to install Meson "manually":
```
```bash
virtualenv -p python3 ./ # Create virtualenv
source bin/activate # Activate the virtualenv
pip3 install meson # Install Meson
@@ -115,7 +122,8 @@ hash -r # Refresh bash paths
```
If you need to install Ninja "manually":
```
```bash
git clone git://github.com/ninja-build/ninja.git
cd ninja
git checkout release

13
android-kiwix-lib-publisher/.gitignore vendored Normal file
View File

@@ -0,0 +1,13 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild

View File

@@ -0,0 +1,25 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.4.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

View File

@@ -0,0 +1,15 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official

View File

Binary file not shown.

View File

@@ -0,0 +1,6 @@
#Wed Jun 19 15:28:39 BST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip

172
android-kiwix-lib-publisher/gradlew vendored Normal file
View File

@@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

84
android-kiwix-lib-publisher/gradlew.bat vendored Normal file
View File

@@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -0,0 +1 @@
/build

View File

@@ -0,0 +1,64 @@
apply plugin: 'com.android.library'
apply plugin: 'maven'
android {
compileSdkVersion 28
defaultConfig {
minSdkVersion 15
targetSdkVersion 28
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation 'com.getkeepsafe.relinker:relinker:1.3.1'
}
task writePom {
pom {
project {
groupId 'org.kiwix.kiwixlib'
artifactId 'kiwixlib'
version '6.0.2'
packaging 'aar'
name 'kiwixlib'
url 'https://github.com/kiwix/kiwix-lib'
licenses {
license {
name 'GPLv3'
url 'https://www.gnu.org/licenses/gpl-3.0.en.html'
}
}
developers {
developer {
id 'kiwix'
name 'kiwix'
email 'contact@kiwix.org'
}
}
scm {
connection 'https://github.com/kiwix/kiwix-lib.git'
developerConnection 'https://github.com/kiwix/kiwix-lib.git'
url 'https://github.com/kiwix/kiwix-lib'
}
}
}.withXml {
def dependenciesNode = asNode().appendNode('dependencies')
//Iterate over the implementation dependencies, adding a <dependency> node for each
configurations.implementation.allDependencies.each {
def dependencyNode = dependenciesNode.appendNode('dependency')
dependencyNode.appendNode('groupId', it.group)
dependencyNode.appendNode('artifactId', it.name)
dependencyNode.appendNode('version', it.version)
}
}.writeTo("$buildDir/pom.xml")
}

View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="kiwix.org.kiwixlib">
<application
android:allowBackup="true"
android:supportsRtl="true">
</application>
</manifest>

View File

@@ -0,0 +1 @@
include ':kiwixLibAndroid'

View File

@@ -45,7 +45,7 @@ class Book
void update(const Reader& reader);
void updateFromXml(const pugi::xml_node& node, const std::string& baseDir);
void updateFromOpds(const pugi::xml_node& node, const std::string& urlHost);
std::string getHumanReadableIdFromPath();
std::string getHumanReadableIdFromPath() const;
bool readOnly() const { return m_readOnly; }
const std::string& getId() const { return m_id; }

View File

@@ -54,6 +54,9 @@ class Download {
m_status(K_UNKNOWN),
m_did(did) {};
void updateStatus(bool follow=false);
void pauseDownload();
void resumeDownload();
void cancelDownload();
StatusResult getStatus() { return m_status; }
std::string getDid() { return m_did; }
std::string getFollowedBy() { return m_followedBy; }

View File

@@ -25,6 +25,8 @@
#include <exception>
#include <string>
#include "common.h"
using namespace std;
namespace kiwix

27
include/kiwixserve.h Normal file
View File

@@ -0,0 +1,27 @@
#ifndef KIWIXLIB_KIWIX_SERVE_H_
#define KIWIXLIB_KIWIX_SERVE_H_
#include <memory>
class Subprocess;
namespace kiwix {
class KiwixServe
{
public:
KiwixServe(int port = 8181);
~KiwixServe();
void run();
void shutDown();
bool isRunning();
int getPort() { return m_port; }
private:
std::unique_ptr<Subprocess> mp_kiwixServe;
int m_port;
};
}; //end namespace kiwix
#endif // KIWIXLIB_KIWIX_SERVE_H_

View File

@@ -23,9 +23,11 @@
#include <string>
#include <vector>
#include <map>
#include <memory>
#include "book.h"
#include "bookmark.h"
#include "common.h"
#define KIWIX_LIBRARY_VERSION "20110515"
@@ -44,12 +46,72 @@ enum supportedListMode {
VALID = 1 << 4,
NOVALID = 1 << 5
};
class Filter {
private:
uint64_t activeFilters;
std::vector<std::string> _acceptTags;
std::vector<std::string> _rejectTags;
std::string _lang;
std::string _publisher;
std::string _creator;
size_t _maxSize;
std::string _query;
public:
Filter();
~Filter() = default;
/**
* Set the filter to check local.
*
* A local book is a book with a path.
* If accept is true, only local book are accepted.
* If accept is false, only non local book are accepted.
*/
Filter& local(bool accept);
/**
* Set the filter to check remote.
*
* A remote book is a book with a url.
* If accept is true, only remote book are accepted.
* If accept is false, only non remote book are accepted.
*/
Filter& remote(bool accept);
/**
* Set the filter to check validity.
*
* A valid book is a book with a path pointing to a existing zim file.
* If accept is true, only valid book are accepted.
* If accept is false, only non valid book are accepted.
*/
Filter& valid(bool accept);
/**
* Set the filter to only accept book with corresponding tag.
*/
Filter& acceptTags(std::vector<std::string> tags);
Filter& rejectTags(std::vector<std::string> tags);
Filter& lang(std::string lang);
Filter& publisher(std::string publisher);
Filter& creator(std::string creator);
Filter& maxSize(size_t size);
Filter& query(std::string query);
bool accept(const Book& book) const;
};
/**
* A Library store several books.
*/
class Library
{
std::map<std::string, kiwix::Book> m_books;
std::map<std::string, std::shared_ptr<Reader>> m_readers;
std::vector<kiwix::Bookmark> m_bookmarks;
public:
@@ -85,6 +147,7 @@ class Library
bool removeBookmark(const std::string& zimId, const std::string& url);
Book& getBookById(const std::string& id);
std::shared_ptr<Reader> getReaderById(const std::string& id);
/**
* Remove a book from the library.
@@ -162,9 +225,27 @@ class Library
* @param search List only books with search in the title or description.
* @return The list of bookIds corresponding to the query.
*/
std::vector<std::string> filter(const std::string& search);
DEPRECATED std::vector<std::string> filter(const std::string& search);
/**
* Filter the library and return the id of the keep elements.
*
* @param filter The filter to use.
* @return The list of bookIds corresponding to the filter.
*/
std::vector<std::string> filter(const Filter& filter);
/**
* Sort (in place) bookIds using the given comparator.
*
* @param bookIds the list of book Ids to sort
* @param comparator how to sort the books
* @return The sorted list of books
*/
void sort(std::vector<std::string>& bookIds, supportedListSortBy sortBy, bool ascending);
/**
* List books in the library.
*
@@ -187,13 +268,14 @@ class Library
* Set to 0 to cancel this filter.
* @return The list of bookIds corresponding to the query.
*/
std::vector<std::string> listBooksIds(
DEPRECATED std::vector<std::string> listBooksIds(
int supportedListMode = ALL,
supportedListSortBy sortBy = UNSORTED,
const std::string& search = "",
const std::string& language = "",
const std::string& creator = "",
const std::string& publisher = "",
const std::vector<std::string>& tags = {},
size_t maxSize = 0);
friend class OPDSDumper;

View File

@@ -58,9 +58,6 @@ class DefaultLibraryManipulator : public LibraryManipulator {
/**
* A tool to manage a `Library`.
*
* A `Manager` handle a internal `Library`.
* This `Library` can be retrived with `cloneLibrary` method.
*/
class Manager
{
@@ -158,55 +155,6 @@ class Manager
const std::string& url = "",
const bool checkMetaData = false);
/**
* Get the book corresponding to an id.
*
* @param[in] id The id of the book
* @param[out] book The book corresponding to the id.
* @return True if the book has been found.
*/
bool getBookById(const std::string& id, Book& book);
/**
* Update the "last open date" of a book
*
* @param id the id of the book.
* @return True if the book is in the library.
*/
bool updateBookLastOpenDateById(const std::string& id);
/**
* Remove (set to empty) paths of all books in the library.
*/
void removeBookPaths();
/**
* List books in the library.
*
* The books list will be available in public vector member `bookIdList`.
*
* @param mode The mode of listing :
* - LASTOPEN sort by last opened book.
* - LOCAL list only local file.
* - REMOTE list only remote file.
* @param sortBy Attribute to sort by the book list.
* @param maxSize Do not list book bigger than maxSize MiB.
* Set to 0 to cancel this filter.
* @param language List only books in this language.
* @param creator List only books of this creator.
* @param publisher List only books of this publisher.
* @param search List only books with search in the title, description or
* language.
* @return True
*/
bool listBooks(const supportedListMode mode,
const supportedListSortBy sortBy,
const unsigned int maxSize,
const std::string& language,
const std::string& creator,
const std::string& publisher,
const std::string& search);
std::string writableLibraryPath;
bool m_hasSearchResult = false;
@@ -225,8 +173,6 @@ class Manager
bool parseOpdsDom(const pugi::xml_document& doc,
const std::string& urlHost);
private:
void checkAndCleanBookPaths(Book& book, const std::string& libraryPath);
};
}

View File

@@ -9,7 +9,11 @@ headers = [
'downloader.h',
'reader.h',
'entry.h',
'searcher.h'
'searcher.h',
'search_renderer.h',
'server.h',
'kiwixserve.h',
'name_mapper.h'
]
install_headers(headers, subdir:'kiwix')
@@ -21,6 +25,7 @@ install_headers(
'tools/pathTools.h',
'tools/regexTools.h',
'tools/stringTools.h',
'tools/lock.h',
subdir:'kiwix/tools'
)

61
include/name_mapper.h Normal file
View File

@@ -0,0 +1,61 @@
/*
* Copyright 2019 Matthieu Gautier <mgautier@kymeria.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#ifndef KIWIX_NAMEMAPPER_H
#define KIWIX_NAMEMAPPER_H
#include <string>
#include <map>
namespace kiwix
{
class Library;
class NameMapper {
public:
virtual ~NameMapper() = default;
virtual std::string getNameForId(const std::string& id) = 0;
virtual std::string getIdForName(const std::string& name) = 0;
};
class IdNameMapper : public NameMapper {
public:
virtual std::string getNameForId(const std::string& id) { return id; };
virtual std::string getIdForName(const std::string& name) { return name; };
};
class HumanReadableNameMapper : public NameMapper {
private:
std::map<std::string, std::string> m_idToName;
std::map<std::string, std::string> m_nameToId;
public:
HumanReadableNameMapper(kiwix::Library& library, bool withAlias);
virtual ~HumanReadableNameMapper() = default;
virtual std::string getNameForId(const std::string& id);
virtual std::string getIdForName(const std::string& name);
};
}
#endif

85
include/search_renderer.h Normal file
View File

@@ -0,0 +1,85 @@
/*
* Copyright 2011 Emmanuel Engelhart <kelson@kiwix.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#ifndef KIWIX_SEARCH_RENDERER_H
#define KIWIX_SEARCH_RENDERER_H
#include <string>
namespace kiwix
{
class Searcher;
class NameMapper;
/**
* The SearcherRenderer class is used to render a search result to a html page.
*/
class SearchRenderer
{
public:
/**
* The default constructor.
*
* @param humanReadableName The global zim's humanReadableName.
* Used to generate pagination links.
*/
SearchRenderer(Searcher* searcher, NameMapper* mapper);
~SearchRenderer();
void setSearchPattern(const std::string& pattern);
/**
* Set the search content id.
*/
void setSearchContent(const std::string& name);
/**
* Set protocol prefix.
*/
void setProtocolPrefix(const std::string& prefix);
/**
* Set search protocol prefix.
*/
void setSearchProtocolPrefix(const std::string& prefix);
/**
* Generate the html page with the resutls of the search.
*/
std::string getHtml();
protected:
std::string beautifyInteger(const unsigned int number);
Searcher* mp_searcher;
NameMapper* mp_nameMapper;
std::string searchContent;
std::string searchPattern;
std::string protocolPrefix;
std::string searchProtocolPrefix;
unsigned int resultCountPerPage;
unsigned int estimatedResultCount;
unsigned int resultStart;
unsigned int resultEnd;
};
}
#endif

View File

@@ -56,24 +56,14 @@ struct SearcherInternal;
/**
* The Searcher class is reponsible to do different kind of search using the
* fulltext index.
*
* Searcher may (if compiled with ctpp2) be used to
* generate a html page for the search result. This use a template that need a
* humanReaderName. This feature is only used by kiwix-serve and this should be
* move outside of Searcher (and with a better API). If you don't use the html
* rendering (getHtml method), you better should simply ignore the different
* humanReadeableName attributes (or give an empty string).
*/
class Searcher
{
public:
/**
* The default constructor.
*
* @param humanReadableName The global zim's humanReadableName.
* Used to generate pagination links.
*/
Searcher(const string& humanReadableName = "");
Searcher();
~Searcher();
@@ -81,11 +71,13 @@ class Searcher
* Add a reader (containing embedded fulltext index) to the search.
*
* @param reader The Reader for the zim containing the fulltext index.
* @param humanReaderName The human readable name of the reader.
* @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, const std::string& humanReaderName);
bool add_reader(Reader* reader);
Reader* get_reader(int index);
/**
* Start a search on the zim associated to the Searcher.
@@ -151,22 +143,8 @@ class Searcher
*/
unsigned int getEstimatedResultCount();
/**
* Set protocol prefix.
* Only used by getHtml.
*/
bool setProtocolPrefix(const std::string prefix);
/**
* Set search protocol prefix.
* Only used by getHtml.
*/
bool setSearchProtocolPrefix(const std::string prefix);
/**
* Generate the html page with the resutls of the search.
*/
string getHtml();
unsigned int getResultStart() { return resultStart; }
unsigned int getResultEnd() { return resultEnd; }
protected:
std::string beautifyInteger(const unsigned int number);
@@ -177,16 +155,11 @@ class Searcher
const bool verbose = false);
std::vector<Reader*> readers;
std::vector<std::string> humanReaderNames;
SearcherInternal* internal;
std::string searchPattern;
std::string protocolPrefix;
std::string searchProtocolPrefix;
unsigned int resultCountPerPage;
unsigned int estimatedResultCount;
unsigned int resultStart;
unsigned int resultEnd;
std::string contentHumanReadableId;
private:
void reset();

75
include/server.h Normal file
View File

@@ -0,0 +1,75 @@
/*
* Copyright 2019 Matthieu Gautier <mgautier@kymeria.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#ifndef KIWIX_SERVER_H
#define KIWIX_SERVER_H
#include <string>
#include <memory>
namespace kiwix
{
class Library;
class NameMapper;
class InternalServer;
class Server {
public:
/**
* The default constructor.
*
* @param library The library to serve.
*/
Server(Library* library, NameMapper* nameMapper=nullptr);
virtual ~Server();
/**
* Serve the content.
*/
bool start();
/**
* Stop the daemon.
*/
void stop();
void setRoot(const std::string& root);
void setAddress(const std::string& addr) { m_addr = addr; }
void setPort(int port) { m_port = port; }
void setNbThreads(int threads) { m_nbThreads = threads; }
void setVerbose(bool verbose) { m_verbose = verbose; }
void setTaskbar(bool withTaskbar, bool withLibraryButton)
{ m_withTaskbar = withTaskbar; m_withLibraryButton = withLibraryButton; }
protected:
Library* mp_library;
NameMapper* mp_nameMapper;
std::string m_root = "";
std::string m_addr = "";
int m_port = 80;
int m_nbThreads = 1;
bool m_verbose = false;
bool m_withTaskbar = true;
bool m_withLibraryButton = true;
std::unique_ptr<InternalServer> mp_server;
};
}
#endif

46
include/tools/lock.h Normal file
View File

@@ -0,0 +1,46 @@
#ifndef KIWIXLIB_TOOL_LOCK_H
#define KIWIXLIB_TOOL_LOCK_H
#include <pthread.h>
namespace kiwix {
class Lock
{
public:
explicit Lock(pthread_mutex_t* mutex) :
mp_mutex(mutex)
{
pthread_mutex_lock(mp_mutex);
}
~Lock() {
if (mp_mutex != nullptr) {
pthread_mutex_unlock(mp_mutex);
}
}
Lock(Lock && other) :
mp_mutex(other.mp_mutex)
{
other.mp_mutex = nullptr;
}
Lock & operator=(Lock && other)
{
mp_mutex = other.mp_mutex;
other.mp_mutex = nullptr;
return *this;
}
private:
pthread_mutex_t* mp_mutex;
Lock(Lock const &) = delete;
Lock & operator=(Lock const &) = delete;
};
}
#endif //KIWIXLIB_TOOL_LOCK_H

View File

@@ -20,13 +20,10 @@
#ifndef KIWIX_NETWORKTOOLS_H
#define KIWIX_NETWORKTOOLS_H
#include <map>
#include <string>
namespace kiwix
{
std::map<std::string, std::string> getNetworkInterfaces();
std::string getBestPublicIp();
std::string download(const std::string& url);
}

View File

@@ -20,18 +20,16 @@
#ifndef KIWIX_OTHERTOOLS_H
#define KIWIX_OTHERTOOLS_H
#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif
#include <string>
#include <pugixml.hpp>
namespace pugi {
class xml_node;
}
namespace kiwix
{
void sleep(unsigned int milliseconds);
std::string nodeToString(pugi::xml_node node);
std::string nodeToString(const pugi::xml_node& node);
std::string converta2toa3(const std::string& a2code);
}

View File

@@ -20,45 +20,27 @@
#ifndef KIWIX_PATHTOOLS_H
#define KIWIX_PATHTOOLS_H
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fstream>
#include <ios>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#ifdef _WIN32
#include <direct.h>
#endif
#include "stringTools.h"
using namespace std;
bool isRelativePath(const string& path);
string computeAbsolutePath(const string path, const string relativePath);
string computeRelativePath(const string path, const string absolutePath);
string removeLastPathElement(const string path,
const bool removePreSeparator = false,
const bool removePostSeparator = false);
string appendToDirectory(const string& directoryPath, const string& filename);
unsigned int getFileSize(const string& path);
string getFileSizeAsString(const string& path);
string getFileContent(const string& path);
bool fileExists(const string& path);
bool makeDirectory(const string& path);
string makeTmpDirectory();
bool copyFile(const string& sourcePath, const string& destPath);
string getLastPathElement(const string& path);
string getExecutablePath();
string getCurrentDirectory();
string getDataDirectory();
bool writeTextFile(const string& path, const string& content);
bool isRelativePath(const std::string& path);
std::string computeAbsolutePath(const std::string& path, const std::string& relativePath);
std::string computeRelativePath(const std::string& path, const std::string& absolutePath);
std::string removeLastPathElement(const std::string& path,
const bool removePreSeparator = false,
const bool removePostSeparator = false);
std::string appendToDirectory(const std::string& directoryPath, const std::string& filename);
unsigned int getFileSize(const std::string& path);
std::string getFileSizeAsString(const std::string& path);
std::string getFileContent(const std::string& path);
bool fileExists(const std::string& path);
bool makeDirectory(const std::string& path);
std::string makeTmpDirectory();
bool copyFile(const std::string& sourcePath, const std::string& destPath);
std::string getLastPathElement(const std::string& path);
std::string getExecutablePath();
std::string getCurrentDirectory();
std::string getDataDirectory();
bool writeTextFile(const std::string& path, const std::string& content);
std::string getMimeTypeForFile(const std::string& filename);
#endif

View File

@@ -20,9 +20,6 @@
#ifndef KIWIX_REGEXTOOLS_H
#define KIWIX_REGEXTOOLS_H
#include <unicode/regex.h>
#include <unicode/ucnv.h>
#include <map>
#include <string>
bool matchRegex(const std::string& content, const std::string& regex);
@@ -30,7 +27,7 @@ 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& regex,
const std::string& replacement);
#endif

View File

@@ -22,14 +22,9 @@
#include <unicode/unistr.h>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include "pathTools.h"
#include <sstream>
namespace kiwix
{
@@ -75,5 +70,7 @@ T extractFromString(const std::string& str) {
iss >> ret;
return ret;
}
bool startsWith(const std::string& base, const std::string& start);
} //namespace kiwix
#endif

View File

@@ -1,5 +1,5 @@
project('kiwix-lib', 'cpp',
version : '4.0.0',
version : '6.0.2',
license : 'GPL',
default_options : ['c_std=c11', 'cpp_std=c++11', 'werror=true'])
@@ -14,9 +14,10 @@ endif
thread_dep = dependency('threads')
libicu_dep = dependency('icu-i18n', static:static_deps)
libzim_dep = dependency('libzim', version : '>=4.0.0', static:static_deps)
libzim_dep = dependency('libzim', version : '>=5.0.0', static:static_deps)
pugixml_dep = dependency('pugixml', static:static_deps)
libcurl_dep = dependency('libcurl', static:static_deps)
microhttpd_dep = dependency('libmicrohttpd', static:static_deps)
if not compiler.has_header('mustache.hpp')
error('Cannot found header mustache.hpp')
@@ -28,7 +29,7 @@ if target_machine.system() == 'windows' and static_deps
extra_cflags += '-DCURL_STATICLIB'
endif
all_deps = [thread_dep, libicu_dep, libzim_dep, pugixml_dep, libcurl_dep]
all_deps = [thread_dep, libicu_dep, libzim_dep, pugixml_dep, libcurl_dep, microhttpd_dep]
inc = include_directories('include')

View File

@@ -19,7 +19,7 @@
*/
#include <jni.h>
#include "org_kiwix_kiwixlib_JNIKiwix.h"
#include "org_kiwix_kiwixlib_JNIICU.h"
#include <iostream>
#include <string>
@@ -30,8 +30,8 @@
pthread_mutex_t globalLock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER;
JNIEXPORT void JNICALL Java_org_kiwix_kiwixlib_JNIKiwix_setDataDirectory(
JNIEnv* env, jobject obj, jstring dirStr)
JNIEXPORT void JNICALL Java_org_kiwix_kiwixlib_JNIICU_setDataDirectory(
JNIEnv* env, jclass kclass, jstring dirStr)
{
std::string cPath = jni2c(dirStr, env);

View File

@@ -0,0 +1,72 @@
/*
* Copyright (C) 2013 Emmanuel Engelhart <kelson@kiwix.org>
* Copyright (C) 2017 Matthieu Gautier <mgautier@kymeria.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#include <jni.h>
#include <android/log.h>
#include "org_kiwix_kiwixlib_JNIKiwixLibrary.h"
#include "library.h"
#include "reader.h"
#include "utils.h"
/* Kiwix Reader JNI functions */
JNIEXPORT jlong JNICALL Java_org_kiwix_kiwixlib_JNIKiwixLibrary_getNativeLibrary(
JNIEnv* env, jobject obj)
{
__android_log_print(ANDROID_LOG_INFO, "kiwix", "Attempting to create library");
Lock l;
try {
kiwix::Library* library = new kiwix::Library();
return reinterpret_cast<jlong>(new Handle<kiwix::Library>(library));
} catch (std::exception& e) {
__android_log_print(ANDROID_LOG_WARN, "kiwix", "Error creating ZIM library");
__android_log_print(ANDROID_LOG_WARN, "kiwix", e.what());
return 0;
}
}
JNIEXPORT void JNICALL
Java_org_kiwix_kiwixlib_JNIKiwixLibrary_dispose(JNIEnv* env, jobject obj)
{
Handle<kiwix::Library>::dispose(env, obj);
}
#define LIBRARY (Handle<kiwix::Library>::getHandle(env, obj))
/* Kiwix library functions */
JNIEXPORT jboolean JNICALL
Java_org_kiwix_kiwixlib_JNIKiwixLibrary_addBook(JNIEnv* env, jobject obj, jstring path)
{
std::string cPath = jni2c(path, env);
bool ret;
try {
kiwix::Reader reader(cPath);
kiwix::Book book;
book.update(reader);
ret = LIBRARY->addBook(book);
} catch (std::exception& e) {
__android_log_print(ANDROID_LOG_ERROR, "kiwix", "Unable to add the book");
__android_log_print(ANDROID_LOG_ERROR, "kiwix", e.what());
ret = false;
}
return ret;
}

View File

@@ -91,7 +91,7 @@ Java_org_kiwix_kiwixlib_JNIKiwixReader_getId(JNIEnv* env, jobject obj)
JNIEXPORT jint JNICALL
Java_org_kiwix_kiwixlib_JNIKiwixReader_getFileSize(JNIEnv* env, jobject obj)
{
jint size;
jint size = 0;
try {
int cSize = READER->getFileSize();
@@ -224,8 +224,23 @@ JNIEXPORT jstring JNICALL Java_org_kiwix_kiwixlib_JNIKiwixReader_getMimeType(
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("", env);
}
return finalUrl;
}
JNIEXPORT jbyteArray JNICALL Java_org_kiwix_kiwixlib_JNIKiwixReader_getContent(
JNIEnv* env, jobject obj, jstring url, jobject titleObj, jobject mimeTypeObj, jobject sizeObj)
JNIEnv* env, jobject obj, jobject url, jobject titleObj, jobject mimeTypeObj, jobject sizeObj)
{
/* Default values */
setStringObjValue("", titleObj, env);
@@ -234,21 +249,24 @@ JNIEXPORT jbyteArray JNICALL Java_org_kiwix_kiwixlib_JNIKiwixReader_getContent(
jbyteArray data = env->NewByteArray(0);
/* Retrieve the content */
std::string cUrl = jni2c(url, env);
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);
data = env->NewByteArray(cSize);
env->SetByteArrayRegion(
data, 0, cSize, reinterpret_cast<const jbyte*>(entry.getBlob().data()));
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) {
__android_log_print(ANDROID_LOG_ERROR, "kiwix", "Unable to get content for url: %s", cUrl.c_str());
__android_log_print(ANDROID_LOG_ERROR, "kiwix", e.what());

View File

@@ -52,7 +52,7 @@ JNIEXPORT void JNICALL Java_org_kiwix_kiwixlib_JNIKiwixSearcher_addReader(
{
auto searcher = SEARCHER;
searcher->add_reader(*(Handle<kiwix::Reader>::getHandle(env, reader)), "");
searcher->add_reader(*(Handle<kiwix::Reader>::getHandle(env, reader)));
}
JNIEXPORT void JNICALL Java_org_kiwix_kiwixlib_JNIKiwixSearcher_search(

View File

@@ -0,0 +1,99 @@
/*
* 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 <zim/file.h>
#include <android/log.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)
{
__android_log_print(ANDROID_LOG_INFO, "kiwix", "Attempting to create server");
Lock l;
try {
auto library = Handle<kiwix::Library>::getHandle(env, jLibrary);
kiwix::Server* server = new kiwix::Server(*library);
return reinterpret_cast<jlong>(new Handle<kiwix::Server>(server));
} catch (std::exception& e) {
__android_log_print(ANDROID_LOG_WARN, "kiwix", "Error creating the server");
__android_log_print(ANDROID_LOG_WARN, "kiwix", e.what());
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 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();
}

View File

@@ -1,8 +1,10 @@
kiwix_jni = custom_target('jni',
input: ['org/kiwix/kiwixlib/JNIKiwix.java',
input: ['org/kiwix/kiwixlib/JNIICU.java',
'org/kiwix/kiwixlib/JNIKiwixReader.java',
'org/kiwix/kiwixlib/JNIKiwixLibrary.java',
'org/kiwix/kiwixlib/JNIKiwixSearcher.java',
'org/kiwix/kiwixlib/JNIKiwixServer.java',
'org/kiwix/kiwixlib/JNIKiwixInt.java',
'org/kiwix/kiwixlib/JNIKiwixString.java',
'org/kiwix/kiwixlib/JNIKiwixBool.java',
@@ -10,15 +12,19 @@ kiwix_jni = custom_target('jni',
'org/kiwix/kiwixlib/Pair.java'],
output: ['org_kiwix_kiwixlib_JNIKiwix.h',
'org_kiwix_kiwixlib_JNIKiwixReader.h',
'org_kiwix_kiwixlib_JNIKiwixLibrary.h',
'org_kiwix_kiwixlib_JNIKiwixServer.h',
'org_kiwix_kiwixlib_JNIKiwixSearcher.h',
'org_kiwix_kiwixlib_JNIKiwixSearcher_Result.h'],
command:['javac', '-d', '@OUTDIR@', '-h', '@OUTDIR@', '@INPUT@']
)
kiwix_sources += [
'android/kiwix.cpp',
'android/kiwixicu.cpp',
'android/kiwixreader.cpp',
'android/kiwixlibrary.cpp',
'android/kiwixsearcher.cpp',
'android/kiwixserver.cpp',
kiwix_jni]
install_subdir('org', install_dir: 'kiwix-lib/java')

View File

@@ -0,0 +1,26 @@
/*
* 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);
}

View File

@@ -20,12 +20,17 @@
package org.kiwix.kiwixlib;
import org.kiwix.kiwixlib.JNIKiwixReader;
import org.kiwix.kiwixlib.JNIKiwixString;
import android.content.Context;
import com.getkeepsafe.relinker.ReLinker;
import org.kiwix.kiwixlib.JNIICU;
public class JNIKiwix
{
static { System.loadLibrary("kiwix"); }
public JNIKiwix(final Context context){
ReLinker.loadLibrary(context, "kiwix");
}
public native void setDataDirectory(String icuDataDir);
public void setDataDirectory(String icuDataDir) {
JNIICU.setDataDirectory(icuDataDir);
}
}

View File

@@ -0,0 +1,38 @@
/*
* 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;
public class JNIKiwixLibrary
{
public native boolean addBook(String path) throws JNIKiwixException;
public JNIKiwixLibrary()
{
nativeHandle = getNativeLibrary();
}
public native void dispose();
private native long getNativeLibrary();
private long nativeHandle;
}

View File

@@ -38,7 +38,31 @@ public class JNIKiwixReader
public native String getMimeType(String url);
public native byte[] getContent(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);

View File

@@ -0,0 +1,48 @@
/*
* 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.JNIKiwixLibrary;
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 boolean start();
public native void stop();
public JNIKiwixServer(JNIKiwixLibrary library)
{
nativeHandle = getNativeServer(library);
}
private native long getNativeServer(JNIKiwixLibrary library);
private long nativeHandle;
}

View File

@@ -22,4 +22,16 @@ 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;
}
}

View File

@@ -114,6 +114,13 @@ inline std::string jni2c(const jstring& val, JNIEnv* env)
inline int jni2c(const jint val) { return (int)val; }
/* 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)

View File

@@ -2,20 +2,24 @@
#include "aria2.h"
#include "xmlrpc.h"
#include <iostream>
#include <sstream>
#include <thread>
#include <chrono>
#include <tools/otherTools.h>
#include <tools/pathTools.h>
#include <tools/stringTools.h>
#include <downloader.h> // For AriaError
#ifdef _WIN32
# define ARIA2_CMD "aria2c.exe"
#else
# define ARIA2_CMD "aria2c"
# include <unistd.h>
#endif
namespace kiwix {
Aria2::Aria2():
@@ -82,7 +86,7 @@ Aria2::Aria2():
int watchdog = 50;
while(--watchdog) {
std::this_thread::sleep_for(std::chrono::microseconds(10000));
sleep(10);
auto res = curl_easy_perform(mp_curl);
if (res == CURLE_OK) {
break;
@@ -171,7 +175,6 @@ std::vector<std::string> Aria2::tellActive()
MethodCall methodCall("aria2.tellActive", m_secret);
auto statusArray = methodCall.newParamValue().getArray();
statusArray.addValue().set(std::string("gid"));
statusArray.addValue().set(std::string("following"));
auto responseContent = doRequest(methodCall);
MethodResponse response(responseContent);
std::vector<std::string> activeGID;
@@ -186,6 +189,27 @@ std::vector<std::string> Aria2::tellActive()
return activeGID;
}
std::vector<std::string> Aria2::tellWaiting()
{
MethodCall methodCall("aria2.tellWaiting", m_secret);
methodCall.newParamValue().set(0);
methodCall.newParamValue().set(99); // max number of downloads to be returned, don't know how to set this properly assumed that there will not be more than 99 paused downloads.
auto statusArray = methodCall.newParamValue().getArray();
statusArray.addValue().set(std::string("gid"));
auto responseContent = doRequest(methodCall);
MethodResponse response(responseContent);
std::vector<std::string> waitingGID;
int index = 0;
while(true) {
try {
auto structNode = response.getParamValue(0).getArray().getValue(index++).getStruct();
auto gidNode = structNode.getMember("gid");
waitingGID.push_back(gidNode.getValue().getAsS());
} catch (InvalidRPCNode& e) { break; }
}
return waitingGID;
}
void Aria2::saveSession()
{
MethodCall methodCall("aria2.saveSession", m_secret);
@@ -199,5 +223,25 @@ void Aria2::shutdown()
doRequest(methodCall);
}
void Aria2::pause(const std::string& gid)
{
MethodCall methodCall("aria2.pause", m_secret);
methodCall.newParamValue().set(gid);
doRequest(methodCall);
}
void Aria2::unpause(const std::string& gid)
{
MethodCall methodCall("aria2.unpause", m_secret);
methodCall.newParamValue().set(gid);
doRequest(methodCall);
}
void Aria2::remove(const std::string& gid)
{
MethodCall methodCall("aria2.remove", m_secret);
methodCall.newParamValue().set(gid);
doRequest(methodCall);
}
} // end namespace kiwix

View File

@@ -37,8 +37,12 @@ class Aria2
std::string addUri(const std::vector<std::string>& uri);
std::string tellStatus(const std::string& gid, const std::vector<std::string>& statusKey);
std::vector<std::string> tellActive();
std::vector<std::string> tellWaiting();
void saveSession();
void shutdown();
void pause(const std::string& gid);
void unpause(const std::string& gid);
void remove(const std::string& gid);
};
}; //end namespace kiwix

View File

@@ -29,7 +29,9 @@
namespace kiwix
{
/* Constructor */
Book::Book() : m_readOnly(false)
Book::Book() :
m_pathValid(false),
m_readOnly(false)
{
}
/* Destructor */
@@ -46,6 +48,7 @@ bool Book::update(const kiwix::Book& other)
if (m_path.empty()) {
m_path = other.m_path;
m_pathValid = other.m_pathValid;
}
if (m_url.empty()) {
@@ -83,6 +86,7 @@ void Book::update(const kiwix::Reader& reader)
m_articleCount = reader.getArticleCount();
m_mediaCount = reader.getMediaCount();
m_size = static_cast<uint64_t>(reader.getFileSize()) << 10;
m_pathValid = true;
reader.getFavicon(m_favicon, m_faviconMimeType);
}
@@ -138,6 +142,7 @@ void Book::updateFromOpds(const pugi::xml_node& node, const std::string& urlHost
m_language = VALUE("language");
m_date = fromOpdsDate(VALUE("updated"));
m_creator = node.child("author").child("name").child_value();
m_tags = VALUE("tags");
for(auto linkNode = node.child("link"); linkNode;
linkNode = linkNode.next_sibling("link")) {
std::string rel = linkNode.attribute("rel").value();
@@ -155,7 +160,7 @@ void Book::updateFromOpds(const pugi::xml_node& node, const std::string& urlHost
}
#undef VALUE
std::string Book::getHumanReadableIdFromPath()
std::string Book::getHumanReadableIdFromPath() const
{
std::string id = m_path;
if (!id.empty()) {

View File

@@ -19,6 +19,7 @@
#include "downloader.h"
#include "tools/pathTools.h"
#include "tools/stringTools.h"
#include <algorithm>
#include <thread>
@@ -36,6 +37,8 @@ namespace kiwix
void Download::updateStatus(bool follow)
{
if (m_status == Download::K_REMOVED)
return;
static std::vector<std::string> statusKey = {"status", "files", "totalLength",
"completedLength", "followedBy",
"downloadSpeed", "verifiedLength"};
@@ -93,6 +96,33 @@ void Download::updateStatus(bool follow)
}
}
void Download::resumeDownload()
{
if (!m_followedBy.empty())
mp_aria->unpause(m_followedBy);
else
mp_aria->unpause(m_did);
updateStatus(true);
}
void Download::pauseDownload()
{
if (!m_followedBy.empty())
mp_aria->pause(m_followedBy);
else
mp_aria->pause(m_did);
updateStatus(true);
}
void Download::cancelDownload()
{
if (!m_followedBy.empty())
mp_aria->remove(m_followedBy);
else
mp_aria->remove(m_did);
m_status = Download::K_REMOVED;
}
/* Constructor */
Downloader::Downloader() :
mp_aria(new Aria2())
@@ -101,6 +131,10 @@ Downloader::Downloader() :
m_knownDownloads[gid] = std::unique_ptr<Download>(new Download(mp_aria, gid));
m_knownDownloads[gid]->updateStatus();
}
for (auto gid : mp_aria->tellWaiting()) {
m_knownDownloads[gid] = std::unique_ptr<Download>(new Download(mp_aria, gid));
m_knownDownloads[gid]->updateStatus();
}
}
@@ -139,14 +173,23 @@ Download* Downloader::startDownload(const std::string& uri)
Download* Downloader::getDownload(const std::string& did)
{
try {
m_knownDownloads.at(did).get()->updateStatus(true);
return m_knownDownloads.at(did).get();
} catch(exception& e) {
} catch(std::exception& e) {
for (auto gid : mp_aria->tellActive()) {
if (gid == did) {
m_knownDownloads[gid] = std::unique_ptr<Download>(new Download(mp_aria, gid));
m_knownDownloads.at(gid).get()->updateStatus(true);
return m_knownDownloads[gid].get();
}
}
for (auto gid : mp_aria->tellWaiting()) {
if (gid == did) {
m_knownDownloads[gid] = std::unique_ptr<Download>(new Download(mp_aria, gid));
m_knownDownloads.at(gid).get()->updateStatus(true);
return m_knownDownloads[gid].get();
}
}
throw e;
}
}

View File

@@ -122,7 +122,6 @@ Entry Entry::getFinalEntry() const
if (final_article.good()) {
return final_article;
}
int loopCounter = 42;
final_article = article;
while (final_article.isRedirect() && loopCounter--) {
@@ -131,7 +130,10 @@ Entry Entry::getFinalEntry() const
throw NoEntry();
}
}
// Prevent infinite loops.
if (final_article.isRedirect()) {
throw NoEntry();
}
return final_article;
}

70
src/kiwixserve.cpp Normal file
View File

@@ -0,0 +1,70 @@
#include "kiwixserve.h"
#include "subprocess.h"
#ifdef _WIN32
# define KIWIXSERVE_CMD "kiwix-serve.exe"
# include <windows.h>
#else
# define KIWIXSERVE_CMD "kiwix-serve"
# include <unistd.h>
#endif
#include "tools/pathTools.h"
#include "tools/stringTools.h"
namespace kiwix {
KiwixServe::KiwixServe(int port) : m_port(port)
{
}
KiwixServe::~KiwixServe()
{
shutDown();
}
void KiwixServe::run()
{
#ifdef _WIN32
int pid = GetCurrentProcessId();
#else
pid_t pid = getpid();
#endif
std::vector<const char*> callCmd;
std::string kiwixServeCmd = appendToDirectory(
removeLastPathElement(getExecutablePath(), true, true),
KIWIXSERVE_CMD);
if (fileExists(kiwixServeCmd)) {
// A local kiwix-serve exe exists (packaged with kiwix-desktop), use it.
callCmd.push_back(kiwixServeCmd.c_str());
} else {
// Try to use a potential installed kiwix-serve.
callCmd.push_back(KIWIXSERVE_CMD);
}
std::string libraryPath = getDataDirectory() + "/library.xml";
std::string attachProcessOpt = "-a" + to_string(pid);
std::string portOpt = "-p" + to_string(m_port);
callCmd.push_back(attachProcessOpt.c_str());
callCmd.push_back(portOpt.c_str());
callCmd.push_back("-l");
callCmd.push_back(libraryPath.c_str());
mp_kiwixServe = Subprocess::run(callCmd);
}
void KiwixServe::shutDown()
{
if (mp_kiwixServe)
mp_kiwixServe->kill();
}
bool KiwixServe::isRunning()
{
if (mp_kiwixServe) {
return (mp_kiwixServe->isRunning());
}
return false;
}
}

View File

@@ -19,17 +19,21 @@
#include "library.h"
#include "book.h"
#include "reader.h"
#include "libxml_dumper.h"
#include "tools/base64.h"
#include "tools/regexTools.h"
#include "tools/pathTools.h"
#include "tools/stringTools.h"
#include <pugixml.hpp>
#include <algorithm>
#include <set>
namespace kiwix
{
/* Constructor */
Library::Library()
{
@@ -81,6 +85,20 @@ Book& Library::getBookById(const std::string& id)
return m_books.at(id);
}
std::shared_ptr<Reader> Library::getReaderById(const std::string& id)
{
try {
return m_readers.at(id);
} catch (std::out_of_range& e) {}
auto book = getBookById(id);
if (!book.isPathValid())
return nullptr;
auto sptr = make_shared<Reader>(book.getPath());
m_readers[id] = sptr;
return sptr;
}
unsigned int Library::getBookCount(const bool localBooks,
const bool remoteBooks)
{
@@ -95,14 +113,16 @@ unsigned int Library::getBookCount(const bool localBooks,
return result;
}
bool Library::writeToFile(const std::string& path) {
bool Library::writeToFile(const std::string& path)
{
auto baseDir = removeLastPathElement(path, true, false);
LibXMLDumper dumper(this);
dumper.setBaseDir(baseDir);
return writeTextFile(path, dumper.dumpLibXMLContent(getBooksIds()));
}
bool Library::writeBookmarksToFile(const std::string& path) {
bool Library::writeBookmarksToFile(const std::string& path)
{
LibXMLDumper dumper(this);
return writeTextFile(path, dumper.dumpLibXMLBookmark());
}
@@ -181,67 +201,104 @@ std::vector<std::string> Library::filter(const std::string& search)
return getBooksIds();
}
return filter(Filter().query(search));
}
std::vector<std::string> Library::filter(const Filter& filter)
{
std::vector<std::string> bookIds;
for(auto& pair:m_books) {
auto& book = pair.second;
if (matchRegex(book.getTitle(), "\\Q" + search + "\\E")
|| matchRegex(book.getDescription(), "\\Q" + search + "\\E")) {
bookIds.push_back(pair.first);
}
auto book = pair.second;
if(filter.accept(book)) {
bookIds.push_back(pair.first);
}
}
return bookIds;
}
template<supportedListSortBy sort>
struct Comparator {
Library* lib;
Comparator(Library* lib) : lib(lib) {}
bool operator() (const std::string& id1, const std::string& id2) {
return get_keys(id1) < get_keys(id2);
}
std::string get_keys(const std::string& id);
unsigned int get_keyi(const std::string& id);
template<supportedListSortBy SORT>
struct KEY_TYPE {
typedef std::string TYPE;
};
template<>
std::string Comparator<TITLE>::get_keys(const std::string& id)
struct KEY_TYPE<SIZE> {
typedef size_t TYPE;
};
template<supportedListSortBy sort>
class Comparator {
private:
Library* lib;
bool ascending;
inline typename KEY_TYPE<sort>::TYPE get_key(const std::string& id);
public:
Comparator(Library* lib, bool ascending) : lib(lib), ascending(ascending) {}
inline bool operator() (const std::string& id1, const std::string& id2) {
if (ascending) {
return get_key(id1) < get_key(id2);
} else {
return get_key(id2) < get_key(id1);
}
}
};
template<>
std::string Comparator<TITLE>::get_key(const std::string& id)
{
return lib->getBookById(id).getTitle();
}
template<>
unsigned int Comparator<SIZE>::get_keyi(const std::string& id)
size_t Comparator<SIZE>::get_key(const std::string& id)
{
return lib->getBookById(id).getSize();
}
template<>
bool Comparator<SIZE>::operator() (const std::string& id1, const std::string& id2)
{
return get_keyi(id1) < get_keyi(id2);
}
template<>
std::string Comparator<DATE>::get_keys(const std::string& id)
std::string Comparator<DATE>::get_key(const std::string& id)
{
return lib->getBookById(id).getDate();
}
template<>
std::string Comparator<CREATOR>::get_keys(const std::string& id)
std::string Comparator<CREATOR>::get_key(const std::string& id)
{
return lib->getBookById(id).getCreator();
}
template<>
std::string Comparator<PUBLISHER>::get_keys(const std::string& id)
std::string Comparator<PUBLISHER>::get_key(const std::string& id)
{
return lib->getBookById(id).getPublisher();
}
void Library::sort(std::vector<std::string>& bookIds, supportedListSortBy sort, bool ascending)
{
switch(sort) {
case TITLE:
std::sort(bookIds.begin(), bookIds.end(), Comparator<TITLE>(this, ascending));
break;
case SIZE:
std::sort(bookIds.begin(), bookIds.end(), Comparator<SIZE>(this, ascending));
break;
case DATE:
std::sort(bookIds.begin(), bookIds.end(), Comparator<DATE>(this, ascending));
break;
case CREATOR:
std::sort(bookIds.begin(), bookIds.end(), Comparator<CREATOR>(this, ascending));
break;
case PUBLISHER:
std::sort(bookIds.begin(), bookIds.end(), Comparator<PUBLISHER>(this, ascending));
break;
default:
break;
}
}
std::vector<std::string> Library::listBooksIds(
int mode,
@@ -250,59 +307,208 @@ std::vector<std::string> Library::listBooksIds(
const std::string& language,
const std::string& creator,
const std::string& publisher,
const std::vector<std::string>& tags,
size_t maxSize) {
std::vector<std::string> bookIds;
for(auto& pair:m_books) {
auto& book = pair.second;
auto local = !book.getPath().empty();
if (mode & LOCAL && !local)
continue;
if (mode & NOLOCAL && local)
continue;
auto valid = book.isPathValid();
if (mode & VALID && !valid)
continue;
if (mode & NOVALID && valid)
continue;
auto remote = !book.getUrl().empty();
if (mode & REMOTE && !remote)
continue;
if (mode & NOREMOTE && remote)
continue;
if (maxSize != 0 && book.getSize() > maxSize)
continue;
if (!language.empty() && book.getLanguage() != language)
continue;
if (!publisher.empty() && book.getPublisher() != publisher)
continue;
if (!creator.empty() && book.getCreator() != creator)
continue;
if (!search.empty() && !(matchRegex(book.getTitle(), "\\Q" + search + "\\E")
|| matchRegex(book.getDescription(), "\\Q" + search + "\\E")))
continue;
bookIds.push_back(pair.first);
}
Filter _filter;
if (mode & LOCAL)
_filter.local(true);
if (mode & NOLOCAL)
_filter.local(false);
if (mode & VALID)
_filter.valid(true);
if (mode & NOVALID)
_filter.valid(false);
if (mode & REMOTE)
_filter.remote(true);
if (mode & NOREMOTE)
_filter.remote(false);
if (!tags.empty())
_filter.acceptTags(tags);
if (maxSize != 0)
_filter.maxSize(maxSize);
if (!language.empty())
_filter.lang(language);
if (!publisher.empty())
_filter.publisher(publisher);
if (!creator.empty())
_filter.creator(creator);
if (!search.empty())
_filter.query(search);
switch(sortBy) {
case TITLE:
std::sort(bookIds.begin(), bookIds.end(), Comparator<TITLE>(this));
break;
case SIZE:
std::sort(bookIds.begin(), bookIds.end(), Comparator<SIZE>(this));
break;
case DATE:
std::sort(bookIds.begin(), bookIds.end(), Comparator<DATE>(this));
break;
case CREATOR:
std::sort(bookIds.begin(), bookIds.end(), Comparator<CREATOR>(this));
break;
case PUBLISHER:
std::sort(bookIds.begin(), bookIds.end(), Comparator<PUBLISHER>(this));
break;
default:
break;
}
auto bookIds = filter(_filter);
sort(bookIds, sortBy, true);
return bookIds;
}
Filter::Filter()
: activeFilters(0),
_maxSize(0)
{};
#define FLAG(x) (1 << x)
enum filterTypes {
NONE = 0,
_LOCAL = FLAG(0),
_REMOTE = FLAG(1),
_NOLOCAL = FLAG(2),
_NOREMOTE = FLAG(3),
_VALID = FLAG(4),
_NOVALID = FLAG(5),
ACCEPTTAGS = FLAG(6),
REJECTTAGS = FLAG(7),
LANG = FLAG(8),
_PUBLISHER = FLAG(9),
_CREATOR = FLAG(10),
MAXSIZE = FLAG(11),
QUERY = FLAG(12),
};
Filter& Filter::local(bool accept)
{
if (accept) {
activeFilters |= _LOCAL;
activeFilters &= ~_NOLOCAL;
} else {
activeFilters |= _NOLOCAL;
activeFilters &= ~_LOCAL;
}
return *this;
}
Filter& Filter::remote(bool accept)
{
if (accept) {
activeFilters |= _REMOTE;
activeFilters &= ~_NOREMOTE;
} else {
activeFilters |= _NOREMOTE;
activeFilters &= ~_REMOTE;
}
return *this;
}
Filter& Filter::valid(bool accept)
{
if (accept) {
activeFilters |= _VALID;
activeFilters &= ~_NOVALID;
} else {
activeFilters |= _NOVALID;
activeFilters &= ~_VALID;
}
return *this;
}
Filter& Filter::acceptTags(std::vector<std::string> tags)
{
_acceptTags = tags;
activeFilters |= ACCEPTTAGS;
return *this;
}
Filter& Filter::rejectTags(std::vector<std::string> tags)
{
_rejectTags = tags;
activeFilters |= REJECTTAGS;
return *this;
}
Filter& Filter::lang(std::string lang)
{
_lang = lang;
activeFilters |= LANG;
return *this;
}
Filter& Filter::publisher(std::string publisher)
{
_publisher = publisher;
activeFilters |= _PUBLISHER;
return *this;
}
Filter& Filter::creator(std::string creator)
{
_creator = creator;
activeFilters |= _CREATOR;
return *this;
}
Filter& Filter::maxSize(size_t maxSize)
{
_maxSize = maxSize;
activeFilters |= MAXSIZE;
return *this;
}
Filter& Filter::query(std::string query)
{
_query = query;
activeFilters |= QUERY;
return *this;
}
#define ACTIVE(X) (activeFilters & (X))
bool Filter::accept(const Book& book) const
{
auto local = !book.getPath().empty();
if (ACTIVE(_LOCAL) && !local)
return false;
if (ACTIVE(_NOLOCAL) && local)
return false;
auto valid = book.isPathValid();
if (ACTIVE(_VALID) && !valid)
return false;
if (ACTIVE(_NOVALID) && valid)
return false;
auto remote = !book.getUrl().empty();
if (ACTIVE(_REMOTE) && !remote)
return false;
if (ACTIVE(_NOREMOTE) && remote)
return false;
if (ACTIVE(ACCEPTTAGS)) {
if (!_acceptTags.empty()) {
auto vBookTags = split(book.getTags(), ";");
std::set<std::string> sBookTags(vBookTags.begin(), vBookTags.end());
for (auto& t: _acceptTags) {
if (sBookTags.find(t) == sBookTags.end()) {
return false;
}
}
}
}
if (ACTIVE(REJECTTAGS)) {
if (!_rejectTags.empty()) {
auto vBookTags = split(book.getTags(), ";");
std::set<std::string> sBookTags(vBookTags.begin(), vBookTags.end());
for (auto& t: _rejectTags) {
if (sBookTags.find(t) != sBookTags.end()) {
return false;
}
}
}
}
if (ACTIVE(MAXSIZE) && book.getSize() > _maxSize)
return false;
if (ACTIVE(LANG) && book.getLanguage() != _lang)
return false;
if (ACTIVE(_PUBLISHER) && book.getPublisher() != _publisher)
return false;
if (ACTIVE(_CREATOR) && book.getCreator() != _creator)
return false;
if ( ACTIVE(QUERY)
&& !(matchRegex(book.getTitle(), "\\Q" + _query + "\\E")
|| matchRegex(book.getDescription(), "\\Q" + _query + "\\E")))
return false;
return true;
}
}

View File

@@ -20,9 +20,10 @@
#include "libxml_dumper.h"
#include "book.h"
#include <tools/base64.h>
#include <tools/stringTools.h>
#include <tools/otherTools.h>
#include "tools/base64.h"
#include "tools/stringTools.h"
#include "tools/otherTools.h"
#include "tools/pathTools.h"
namespace kiwix
{

View File

@@ -208,12 +208,15 @@ bool Manager::addBookFromPath(const std::string& pathToOpen,
bool Manager::readBookFromPath(const std::string& path, kiwix::Book* book)
{
std::string tmp_path = path;
if (isRelativePath(path)) {
tmp_path = computeAbsolutePath(getCurrentDirectory(), path);
}
try {
kiwix::Reader reader(path);
kiwix::Reader reader(tmp_path);
book->update(reader);
book->setPathValid(true);
} catch (const std::exception& e) {
std::cerr << "Invalid " << path << " : " << e.what() << std::endl;
book->setPathValid(false);
return false;
}

View File

@@ -8,7 +8,9 @@ kiwix_sources = [
'downloader.cpp',
'reader.cpp',
'entry.cpp',
'server.cpp',
'searcher.cpp',
'search_renderer.cpp',
'subprocess.cpp',
'aria2.cpp',
'tools/base64.cpp',
@@ -17,6 +19,10 @@ kiwix_sources = [
'tools/stringTools.cpp',
'tools/networkTools.cpp',
'tools/otherTools.cpp',
'kiwixserve.cpp',
'name_mapper.cpp',
'server/request_context.cpp',
'server/response.cpp'
]
kiwix_sources += lib_resources
@@ -44,5 +50,4 @@ kiwixlib = library('kiwix',
dependencies : all_deps,
version: meson.project_version(),
install: true,
install_dir: install_dir,
install_rpath: '$ORIGIN')
install_dir: install_dir)

62
src/name_mapper.cpp Normal file
View File

@@ -0,0 +1,62 @@
/*
* Copyright 2019 Matthieu Gautier <mgautier@kymeria.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#include "name_mapper.h"
#include "library.h"
#include "tools/regexTools.h"
#include <iostream>
namespace kiwix {
HumanReadableNameMapper::HumanReadableNameMapper(kiwix::Library& library, bool withAlias) {
for (auto& bookId: library.filter(kiwix::Filter().local(true).valid(true))) {
auto& currentBook = library.getBookById(bookId);
auto bookName = currentBook.getHumanReadableIdFromPath();
m_idToName[bookId] = bookName;
m_nameToId[bookName] = bookId;
if (!withAlias)
continue;
auto aliasName = replaceRegex(bookName, "", "_[[:digit:]]{4}-[[:digit:]]{2}$");
if (aliasName == bookName) {
continue;
}
if (m_nameToId.find(aliasName) == m_nameToId.end()) {
m_nameToId[aliasName] = bookId;
} else {
auto alreadyPresentPath = library.getBookById(m_nameToId[aliasName]).getPath();
std::cerr << "Path collision: " << alreadyPresentPath
<< " and " << currentBook.getPath()
<< " can't share the same URL path '" << aliasName << "'."
<< " Therefore, only " << alreadyPresentPath
<< " will be served." << std::endl;
}
}
}
std::string HumanReadableNameMapper::getNameForId(const std::string& id) {
return m_idToName.at(id);
}
std::string HumanReadableNameMapper::getIdForName(const std::string& name) {
return m_nameToId.at(name);
}
}

View File

@@ -20,7 +20,8 @@
#include "opds_dumper.h"
#include "book.h"
#include <tools/otherTools.h>
#include "tools/otherTools.h"
#include <iomanip>
namespace kiwix
{
@@ -74,6 +75,7 @@ pugi::xml_node OPDSDumper::handleBook(Book book, pugi::xml_node root_node) {
ADD_TEXT_ENTRY(entry_node, "icon", rootLocation + "/meta?name=favicon&content=" + book.getHumanReadableIdFromPath());
ADD_TEXT_ENTRY(entry_node, "updated", gen_date_from_yyyy_mm_dd(book.getDate()));
ADD_TEXT_ENTRY(entry_node, "summary", book.getDescription());
ADD_TEXT_ENTRY(entry_node, "tags", book.getTags());
auto content_node = entry_node.append_child("link");
content_node.append_attribute("type") = "text/html";

View File

@@ -241,8 +241,6 @@ Entry Reader::getMainPage() const
throw NoEntry();
}
string url = "";
zim::Article article;
if (this->zimFileHandler->getFileheader().hasMainPage())
{
@@ -260,7 +258,7 @@ Entry Reader::getMainPage() const
bool Reader::getFavicon(string& content, string& mimeType) const
{
static const char* const paths[] = {"-/favicon.png", "I/favicon.png", "I/favicon", "-/favicon"};
static const char* const paths[] = {"-/favicon", "-/favicon.png", "I/favicon.png", "I/favicon"};
for (auto &path: paths) {
try {
@@ -767,13 +765,16 @@ bool Reader::searchSuggestionsSmart(const string& prefix,
this->suggestions.clear();
this->suggestionsOffset = this->suggestions.begin();
/* Try to search in the title using fulltext search database */
const zim::Search* suggestionSearch
const auto suggestionSearch
= this->getZimFileHandler()->suggestions(prefix, 0, suggestionsCount);
if (suggestionSearch->get_matches_estimated()) {
for (auto current = suggestionSearch->begin();
current != suggestionSearch->end();
current++) {
if (!current->good()) {
continue;
}
std::vector<std::string> suggestion;
suggestion.push_back(current->getTitle());
suggestion.push_back("/A/" + current->getUrl());

151
src/search_renderer.cpp Normal file
View File

@@ -0,0 +1,151 @@
/*
* Copyright 2011 Emmanuel Engelhart <kelson@kiwix.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#include <cmath>
#include "search_renderer.h"
#include "searcher.h"
#include "reader.h"
#include "library.h"
#include "name_mapper.h"
#include <zim/search.h>
#include <mustache.hpp>
#include "kiwixlib-resources.h"
namespace kiwix
{
/* Constructor */
SearchRenderer::SearchRenderer(Searcher* searcher, NameMapper* mapper)
: mp_searcher(searcher),
mp_nameMapper(mapper),
protocolPrefix("zim://"),
searchProtocolPrefix("search://?")
{}
/* Destructor */
SearchRenderer::~SearchRenderer() = default;
void SearchRenderer::setSearchPattern(const std::string& pattern)
{
this->searchPattern = pattern;
}
void SearchRenderer::setSearchContent(const std::string& name)
{
this->searchContent = name;
}
void SearchRenderer::setProtocolPrefix(const std::string& prefix)
{
this->protocolPrefix = prefix;
}
void SearchRenderer::setSearchProtocolPrefix(const std::string& prefix)
{
this->searchProtocolPrefix = prefix;
}
std::string SearchRenderer::getHtml()
{
kainjow::mustache::data results{kainjow::mustache::data::type::list};
mp_searcher->restart_search();
Result* p_result = NULL;
while ((p_result = mp_searcher->getNextResult())) {
kainjow::mustache::data result;
result.set("title", p_result->get_title());
result.set("url", p_result->get_url());
result.set("snippet", p_result->get_snippet());
auto readerIndex = p_result->get_readerIndex();
auto reader = mp_searcher->get_reader(readerIndex);
result.set("resultContentId", mp_nameMapper->getNameForId(reader->getId()));
if (p_result->get_wordCount() >= 0) {
result.set("wordCount", kiwix::beautifyInteger(p_result->get_wordCount()));
}
results.push_back(result);
delete p_result;
}
// pages
kainjow::mustache::data pages{kainjow::mustache::data::type::list};
auto resultStart = mp_searcher->getResultStart();
auto resultEnd = mp_searcher->getResultEnd();
auto resultCountPerPage = resultEnd - resultStart;
auto estimatedResultCount = mp_searcher->getEstimatedResultCount();
unsigned int pageStart
= resultStart / resultCountPerPage >= 5
? resultStart / resultCountPerPage - 4
: 0;
unsigned int pageCount
= estimatedResultCount / resultCountPerPage + 1 - pageStart;
if (pageCount > 10) {
pageCount = 10;
} else if (pageCount == 1) {
pageCount = 0;
}
for (unsigned int i = pageStart; i < pageStart + pageCount; i++) {
kainjow::mustache::data page;
page.set("label", to_string(i + 1));
page.set("start", to_string(i * resultCountPerPage));
page.set("end", to_string((i + 1) * resultCountPerPage));
if (i * resultCountPerPage == resultStart) {
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("results", results);
allData.set("pages", pages);
allData.set("hasResult", estimatedResultCount != 0);
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("resultRange", to_string(resultCountPerPage));
allData.set("resultLastPageStart", to_string(estimatedResultCount > resultCountPerPage
? round(estimatedResultCount / resultCountPerPage) * resultCountPerPage
: 0));
allData.set("lastResult", to_string(estimatedResultCount));
allData.set("protocolPrefix", this->protocolPrefix);
allData.set("searchProtocolPrefix", this->searchProtocolPrefix);
allData.set("contentId", this->searchContent);
std::stringstream ss;
tmpl.render(allData, [&ss](const std::string& str) { ss << str; });
return ss.str();
}
}

View File

@@ -65,16 +65,12 @@ struct SearcherInternal {
};
/* Constructor */
Searcher::Searcher(const std::string& humanReadableName)
Searcher::Searcher()
: internal(new SearcherInternal()),
searchPattern(""),
protocolPrefix("zim://"),
searchProtocolPrefix("search://?"),
resultCountPerPage(0),
estimatedResultCount(0),
resultStart(0),
resultEnd(0),
contentHumanReadableId(humanReadableName)
resultEnd(0)
{
loadICUExternalTables();
}
@@ -85,16 +81,21 @@ Searcher::~Searcher()
delete internal;
}
bool Searcher::add_reader(Reader* reader, const std::string& humanReadableName)
bool Searcher::add_reader(Reader* reader)
{
if (!reader->hasFulltextIndex()) {
return false;
}
this->readers.push_back(reader);
this->humanReaderNames.push_back(humanReadableName);
return true;
}
Reader* Searcher::get_reader(int readerIndex)
{
return readers.at(readerIndex);
}
/* Search strings in the database */
void Searcher::search(std::string& search,
unsigned int resultStart,
@@ -107,22 +108,8 @@ void Searcher::search(std::string& search,
cout << "Performing query `" << search << "'" << endl;
}
/* If resultEnd & resultStart inverted */
if (resultStart > resultEnd) {
resultEnd += resultStart;
resultStart = resultEnd - resultStart;
resultEnd -= resultStart;
}
/* Try to find results */
if (resultStart != resultEnd) {
/* Avoid big researches */
this->resultCountPerPage = resultEnd - resultStart;
if (this->resultCountPerPage > MAX_SEARCH_LEN) {
resultEnd = resultStart + MAX_SEARCH_LEN;
this->resultCountPerPage = MAX_SEARCH_LEN;
}
/* Perform the search */
this->searchPattern = search;
this->resultStart = resultStart;
@@ -136,6 +123,7 @@ void Searcher::search(std::string& search,
}
}
zim::Search* search = new zim::Search(zims);
search->set_verbose(verbose);
search->set_query(unaccentedSearch);
search->set_range(resultStart, resultEnd);
internal->_search = search;
@@ -158,25 +146,11 @@ void Searcher::geo_search(float latitude, float longitude, float distance,
cout << "Performing geo query `" << distance << "&(" << latitude << ";" << longitude << ")'" << endl;
}
/* If resultEnd & resultStart inverted */
if (resultStart > resultEnd) {
resultEnd += resultStart;
resultStart = resultEnd - resultStart;
resultEnd -= resultStart;
}
/* Try to find results */
if (resultStart == resultEnd) {
return;
}
/* Avoid big researches */
this->resultCountPerPage = resultEnd - resultStart;
if (this->resultCountPerPage > MAX_SEARCH_LEN) {
resultEnd = resultStart + MAX_SEARCH_LEN;
this->resultCountPerPage = MAX_SEARCH_LEN;
}
/* Perform the search */
std::ostringstream oss;
oss << "Articles located less than " << distance << " meters of " << latitude << ";" << longitude;
@@ -190,6 +164,7 @@ void Searcher::geo_search(float latitude, float longitude, float distance,
zims.push_back((*current)->getZimFileHandler());
}
zim::Search* search = new zim::Search(zims);
search->set_verbose(verbose);
search->set_query("");
search->set_georange(latitude, longitude, distance);
search->set_range(resultStart, resultEnd);
@@ -244,6 +219,7 @@ void Searcher::suggestions(std::string& searchPattern, const bool verbose)
zims.push_back((*current)->getZimFileHandler());
}
zim::Search* search = new zim::Search(zims);
search->set_verbose(verbose);
search->set_query(unaccentedSearch);
search->set_range(resultStart, resultEnd);
search->set_suggestion_mode(true);
@@ -258,18 +234,6 @@ unsigned int Searcher::getEstimatedResultCount()
return this->estimatedResultCount;
}
bool Searcher::setProtocolPrefix(const std::string prefix)
{
this->protocolPrefix = prefix;
return true;
}
bool Searcher::setSearchProtocolPrefix(const std::string prefix)
{
this->searchProtocolPrefix = prefix;
return true;
}
_Result::_Result(zim::Search::iterator& iterator)
: iterator(iterator)
{
@@ -311,79 +275,5 @@ int _Result::get_readerIndex()
return iterator.get_fileIndex();
}
string Searcher::getHtml()
{
kainjow::mustache::data results{kainjow::mustache::data::type::list};
this->restart_search();
Result* p_result = NULL;
while ((p_result = this->getNextResult())) {
kainjow::mustache::data result;
result.set("title", p_result->get_title());
result.set("url", p_result->get_url());
result.set("snippet", p_result->get_snippet());
result.set("resultContentId", humanReaderNames[p_result->get_readerIndex()]);
if (p_result->get_wordCount() >= 0) {
result.set("wordCount", kiwix::beautifyInteger(p_result->get_wordCount()));
}
results.push_back(result);
delete p_result;
}
// pages
kainjow::mustache::data pages{kainjow::mustache::data::type::list};
unsigned int pageStart
= this->resultStart / this->resultCountPerPage >= 5
? this->resultStart / this->resultCountPerPage - 4
: 0;
unsigned int pageCount
= this->estimatedResultCount / this->resultCountPerPage + 1 - pageStart;
if (pageCount > 10) {
pageCount = 10;
} else if (pageCount == 1) {
pageCount = 0;
}
for (unsigned int i = pageStart; i < pageStart + pageCount; i++) {
kainjow::mustache::data page;
page.set("label", to_string(i + 1));
page.set("start", to_string(i * this->resultCountPerPage));
page.set("end", to_string((i + 1) * this->resultCountPerPage));
if (i * this->resultCountPerPage == this->resultStart) {
page.set("selected", true);
}
pages.push_back(page);
}
std::string template_str = RESOURCE::search_result_tmpl;
kainjow::mustache::mustache tmpl(template_str);
kainjow::mustache::data allData;
allData.set("results", results);
allData.set("pages", pages);
allData.set("hasResult", this->estimatedResultCount != 0);
allData.set("count", kiwix::beautifyInteger(this->estimatedResultCount));
allData.set("searchPattern", kiwix::encodeDiples(this->searchPattern));
allData.set("searchPatternEncoded", urlEncode(this->searchPattern));
allData.set("resultStart", to_string(this->resultStart + 1));
allData.set("resultEnd", to_string(min(this->resultEnd, this->estimatedResultCount)));
allData.set("resultRange", to_string(this->resultCountPerPage));
allData.set("resultLastPageStart", to_string(this->estimatedResultCount > this->resultCountPerPage
? round(this->estimatedResultCount / this->resultCountPerPage) * this->resultCountPerPage
: 0));
allData.set("lastResult", to_string(this->estimatedResultCount));
allData.set("protocolPrefix", this->protocolPrefix);
allData.set("searchProtocolPrefix", this->searchProtocolPrefix);
allData.set("contentId", this->contentHumanReadableId);
std::stringstream ss;
tmpl.render(allData, [&ss](const std::string& str) { ss << str; });
return ss.str();
}
}

911
src/server.cpp Normal file
View File

@@ -0,0 +1,911 @@
/*
* Copyright 2019 Matthieu Gautier <mgautier@kymeria.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#include "server.h"
#ifdef _WIN32
# if !defined(__MINGW32__) && (_MSC_VER < 1600)
# include "stdint4win.h"
# endif
# include <winsock2.h>
# include <ws2tcpip.h>
# ifdef __GNUC__
// inet_pton is not declared in mingw, even if the function exists.
extern "C" {
WINSOCK_API_LINKAGE INT WSAAPI inet_pton( INT Family, PCSTR pszAddrString, PVOID pAddrBuf);
}
# endif
typedef UINT64 uint64_t;
typedef UINT16 uint16_t;
#endif
extern "C" {
#include <microhttpd.h>
}
#include "tools/otherTools.h"
#include "tools/pathTools.h"
#include "tools/regexTools.h"
#include "tools/stringTools.h"
#include "library.h"
#include "name_mapper.h"
#include "entry.h"
#include "searcher.h"
#include "search_renderer.h"
#include "opds_dumper.h"
#include <zim/uuid.h>
#include <mustache.hpp>
#include <pthread.h>
#include <atomic>
#include <string>
#include <vector>
#include <chrono>
#include "kiwixlib-resources.h"
#ifndef _WIN32
# include <arpa/inet.h>
#endif
#include "server/request_context.h"
#include "server/response.h"
#define MAX_SEARCH_LEN 140
#define KIWIX_MIN_CONTENT_SIZE_TO_DEFLATE 100
namespace kiwix {
static IdNameMapper defaultNameMapper;
static int staticHandlerCallback(void* cls,
struct MHD_Connection* connection,
const char* url,
const char* method,
const char* version,
const char* upload_data,
size_t* upload_data_size,
void** cont_cls);
class InternalServer {
public:
InternalServer(Library* library,
NameMapper* nameMapper,
std::string addr,
int port,
std::string root,
int nbThreads,
bool verbose,
bool withTaskbar,
bool withLibraryButton);
virtual ~InternalServer() = default;
int handlerCallback(struct MHD_Connection* connection,
const char* url,
const char* method,
const char* version,
const char* upload_data,
size_t* upload_data_size,
void** cont_cls);
bool start();
void stop();
private:
Response handle_request(const RequestContext& request);
Response build_500(const std::string& msg);
Response build_404(const RequestContext& request, const std::string& zimName);
Response build_homepage(const RequestContext& request);
Response handle_skin(const RequestContext& request);
Response handle_catalog(const RequestContext& request);
Response handle_meta(const RequestContext& request);
Response handle_search(const RequestContext& request);
Response handle_suggest(const RequestContext& request);
Response handle_random(const RequestContext& request);
Response handle_content(const RequestContext& request);
kainjow::mustache::data get_default_data();
Response get_default_response();
std::string m_addr;
int m_port;
std::string m_root;
int m_nbThreads;
std::atomic_bool m_verbose;
bool m_withTaskbar;
bool m_withLibraryButton;
struct MHD_Daemon* mp_daemon;
Library* mp_library;
NameMapper* mp_nameMapper;
};
Server::Server(Library* library, NameMapper* nameMapper) :
mp_library(library),
mp_nameMapper(nameMapper),
mp_server(nullptr)
{
}
Server::~Server() = default;
bool Server::start() {
mp_server.reset(new InternalServer(
mp_library,
mp_nameMapper,
m_addr,
m_port,
m_root,
m_nbThreads,
m_verbose,
m_withTaskbar,
m_withLibraryButton));
return mp_server->start();
}
void Server::stop() {
mp_server->stop();
mp_server.reset(nullptr);
}
void Server::setRoot(const std::string& root)
{
m_root = root;
if (m_root[0] != '/') {
m_root = "/" + m_root;
}
if (m_root.back() == '/') {
m_root.erase(m_root.size() - 1);
}
}
InternalServer::InternalServer(Library* library,
NameMapper* nameMapper,
std::string addr,
int port,
std::string root,
int nbThreads,
bool verbose,
bool withTaskbar,
bool withLibraryButton) :
m_addr(addr),
m_port(port),
m_root(root),
m_nbThreads(nbThreads),
m_verbose(verbose),
m_withTaskbar(withTaskbar),
m_withLibraryButton(withLibraryButton),
mp_daemon(nullptr),
mp_library(library),
mp_nameMapper(nameMapper ? nameMapper : &defaultNameMapper)
{}
bool InternalServer::start() {
#ifdef _WIN32
int flags = MHD_USE_SELECT_INTERNALLY;
#else
int flags = MHD_USE_POLL_INTERNALLY;
#endif
if (m_verbose.load())
flags |= MHD_USE_DEBUG;
struct sockaddr_in sockAddr;
memset(&sockAddr, 0, sizeof(sockAddr));
sockAddr.sin_family = AF_INET;
sockAddr.sin_port = htons(m_port);
if (m_addr.empty()) {
if (0 != INADDR_ANY)
sockAddr.sin_addr.s_addr = htonl(INADDR_ANY);
} else {
if (inet_pton(AF_INET, m_addr.c_str(), &(sockAddr.sin_addr.s_addr)) == 0) {
std::cerr << "Ip address " << m_addr << " is not a valid ip address" << std::endl;
return false;
}
}
mp_daemon = MHD_start_daemon(flags,
m_port,
NULL,
NULL,
&staticHandlerCallback,
this,
MHD_OPTION_SOCK_ADDR, &sockAddr,
MHD_OPTION_THREAD_POOL_SIZE, m_nbThreads,
MHD_OPTION_END);
if (mp_daemon == nullptr) {
std::cerr << "Unable to instantiate the HTTP daemon. The port " << m_port
<< " is maybe already occupied or need more permissions to be open. "
"Please try as root or with a port number higher or equal to 1024."
<< std::endl;
return false;
}
return true;
}
void InternalServer::stop()
{
MHD_stop_daemon(mp_daemon);
}
static int staticHandlerCallback(void* cls,
struct MHD_Connection* connection,
const char* url,
const char* method,
const char* version,
const char* upload_data,
size_t* upload_data_size,
void** cont_cls)
{
InternalServer* _this = static_cast<InternalServer*>(cls);
return _this->handlerCallback(connection,
url,
method,
version,
upload_data,
upload_data_size,
cont_cls);
}
int InternalServer::handlerCallback(struct MHD_Connection* connection,
const char* url,
const char* method,
const char* version,
const char* upload_data,
size_t* upload_data_size,
void** cont_cls)
{
auto start_time = std::chrono::steady_clock::now();
if (m_verbose.load() ) {
printf("======================\n");
printf("Requesting : \n");
printf("full_url : %s\n", url);
}
RequestContext request(connection, m_root, url, method, version);
if (m_verbose.load() ) {
request.print_debug_info();
}
/* Unexpected method */
if (request.get_method() != RequestMethod::GET
&& request.get_method() != RequestMethod::POST) {
printf("Reject request because of unhandled request method.\n");
printf("----------------------\n");
return MHD_NO;
}
auto response = handle_request(request);
if (response.getReturnCode() == MHD_HTTP_INTERNAL_SERVER_ERROR) {
printf("========== INTERNAL ERROR !! ============\n");
if (!m_verbose.load()) {
printf("Requesting : \n");
printf("full_url : %s\n", url);
request.print_debug_info();
}
}
auto ret = response.send(request, connection);
auto end_time = std::chrono::steady_clock::now();
auto time_span = std::chrono::duration_cast<std::chrono::duration<double>>(end_time - start_time);
if (m_verbose.load()) {
printf("Request time : %fs\n", time_span.count());
printf("----------------------\n");
}
return ret;
}
Response InternalServer::handle_request(const RequestContext& request)
{
try {
if (! request.is_valid_url())
return build_404(request, "");
if (kiwix::startsWith(request.get_url(), "/skin/"))
return handle_skin(request);
if (startsWith(request.get_url(), "/catalog"))
return handle_catalog(request);
if (request.get_url() == "/meta")
return handle_meta(request);
if (request.get_url() == "/search")
return handle_search(request);
if (request.get_url() == "/suggest")
return handle_suggest(request);
if (request.get_url() == "/random")
return handle_random(request);
return handle_content(request);
} catch (std::exception& e) {
fprintf(stderr, "===== Unhandled error : %s\n", e.what());
return build_500(e.what());
} catch (...) {
fprintf(stderr, "===== Unhandled unknown error\n");
return build_500("Unknown error");
}
}
kainjow::mustache::data InternalServer::get_default_data()
{
kainjow::mustache::data data;
data.set("root", m_root);
return data;
}
Response InternalServer::get_default_response()
{
return Response(m_root, m_verbose.load(), m_withTaskbar, m_withLibraryButton);
}
Response InternalServer::build_404(const RequestContext& request,
const std::string& bookName)
{
kainjow::mustache::data results;
results.set("url", request.get_full_url());
auto response = get_default_response();
response.set_template(RESOURCE::templates::_404_html, results);
response.set_mimeType("text/html");
response.set_code(MHD_HTTP_NOT_FOUND);
response.set_compress(true);
response.set_taskbar(bookName, "");
return response;
}
Response InternalServer::build_500(const std::string& msg)
{
kainjow::mustache::data data;
data.set("error", msg);
Response response(m_root, true, false, false);
response.set_template(RESOURCE::templates::_500_html, data);
response.set_mimeType("text/html");
response.set_code(MHD_HTTP_INTERNAL_SERVER_ERROR);
return response;
}
Response InternalServer::build_homepage(const RequestContext& request)
{
auto data = get_default_data();
kainjow::mustache::data books{kainjow::mustache::data::type::list};
for (auto& bookId: mp_library->filter(kiwix::Filter().local(true).valid(true))) {
auto& currentBook = mp_library->getBookById(bookId);
kainjow::mustache::data book;
book.set("name", mp_nameMapper->getNameForId(bookId));
book.set("title", currentBook.getTitle());
book.set("description", currentBook.getDescription());
book.set("articleCount", beautifyInteger(currentBook.getArticleCount()));
book.set("mediaCount", beautifyInteger(currentBook.getMediaCount()));
books.push_back(book);
}
data.set("books", books);
auto response = get_default_response();
response.set_template(RESOURCE::templates::index_html, data);
response.set_mimeType("text/html; charset=utf-8");
response.set_compress(true);
response.set_taskbar("", "");
return response;
}
Response InternalServer::handle_meta(const RequestContext& request)
{
std::string bookName;
std::string bookId;
std::string meta_name;
std::shared_ptr<Reader> reader;
try {
bookName = request.get_argument("content");
bookId = mp_nameMapper->getIdForName(bookName);
meta_name = request.get_argument("name");
reader = mp_library->getReaderById(bookId);
} catch (const std::out_of_range& e) {
return build_404(request, bookName);
}
if (reader == nullptr) {
return build_404(request, bookName);
}
std::string content;
std::string mimeType = "text";
if (meta_name == "title") {
content = reader->getTitle();
} else if (meta_name == "description") {
content = reader->getDescription();
} else if (meta_name == "language") {
content = reader->getLanguage();
} else if (meta_name == "name") {
content = reader->getName();
} else if (meta_name == "tags") {
content = reader->getTags();
} else if (meta_name == "date") {
content = reader->getDate();
} else if (meta_name == "creator") {
content = reader->getCreator();
} else if (meta_name == "publisher") {
content = reader->getPublisher();
} else if (meta_name == "favicon") {
reader->getFavicon(content, mimeType);
} else {
return build_404(request, bookName);
}
auto response = get_default_response();
response.set_content(content);
response.set_mimeType(mimeType);
response.set_compress(false);
response.set_cache(true);
return response;
}
Response InternalServer::handle_suggest(const RequestContext& request)
{
if (m_verbose.load()) {
printf("** running handle_suggest\n");
}
std::string content;
std::string mimeType;
unsigned int maxSuggestionCount = 10;
unsigned int suggestionCount = 0;
std::string suggestion;
std::string bookName;
std::string bookId;
std::string term;
std::shared_ptr<Reader> reader;
try {
bookName = request.get_argument("content");
bookId = mp_nameMapper->getIdForName(bookName);
term = request.get_argument("term");
reader = mp_library->getReaderById(bookId);
} catch (const std::out_of_range&) {
return build_404(request, bookName);
}
if (m_verbose.load()) {
printf("Searching suggestions for: \"%s\"\n", term.c_str());
}
kainjow::mustache::data results{kainjow::mustache::data::type::list};
bool first = true;
if (reader != nullptr) {
/* Get the suggestions */
reader->searchSuggestionsSmart(term, maxSuggestionCount);
while (reader->getNextSuggestion(suggestion)) {
kainjow::mustache::data result;
result.set("label", suggestion);
result.set("value", suggestion);
result.set("first", first);
first = false;
results.push_back(result);
suggestionCount++;
}
}
/* Propose the fulltext search if possible */
if (reader->hasFulltextIndex()) {
kainjow::mustache::data result;
result.set("label", "containing '" + term + "'...");
result.set("value", term);
result.set("first", first);
results.push_back(result);
}
auto data = get_default_data();
data.set("suggestions", results);
auto response = get_default_response();
response.set_template(RESOURCE::templates::suggestion_json, data);
response.set_mimeType("application/json; charset=utf-8");
response.set_compress(true);
return response;
}
Response InternalServer::handle_skin(const RequestContext& request)
{
if (m_verbose.load()) {
printf("** running handle_skin\n");
}
auto response = get_default_response();
auto resourceName = request.get_url().substr(1);
try {
response.set_content(getResource(resourceName));
} catch (const ResourceNotFound& e) {
return build_404(request, "");
}
response.set_mimeType(getMimeTypeForFile(resourceName));
response.set_compress(true);
response.set_cache(true);
return response;
}
Response InternalServer::handle_search(const RequestContext& request)
{
if (m_verbose.load()) {
printf("** running handle_search\n");
}
std::string bookName;
std::string bookId;
try {
bookName = request.get_argument("content");
bookId = mp_nameMapper->getIdForName(bookName);
} catch (const std::out_of_range&) {}
std::string patternString;
try {
patternString = request.get_argument("pattern");
} catch (const std::out_of_range&) {}
/* Retrive geo search */
bool has_geo_query = false;
float latitude = 0;
float longitude = 0;
float distance = 0;
try {
latitude = request.get_argument<float>("latitude");
longitude = request.get_argument<float>("longitude");
distance = request.get_argument<float>("distance");
has_geo_query = true;
} catch(const std::out_of_range&) {}
catch(const std::invalid_argument&) {}
std::shared_ptr<Reader> reader(nullptr);
try {
reader = mp_library->getReaderById(bookId);
} catch (const std::out_of_range&) {}
/* Try first to load directly the article */
if (reader != nullptr && !patternString.empty()) {
std::string patternCorrespondingUrl;
auto variants = reader->getTitleVariants(patternString);
auto variantsItr = variants.begin();
while (patternCorrespondingUrl.empty() && variantsItr != variants.end()) {
try {
auto entry = reader->getEntryFromTitle(*variantsItr);
entry = entry.getFinalEntry();
patternCorrespondingUrl = entry.getPath();
break;
} catch(kiwix::NoEntry& e) {
variantsItr++;
}
}
/* If article found then redirect directly to it */
if (!patternCorrespondingUrl.empty()) {
auto response = get_default_response();
response.set_redirection(m_root + "/" + bookName + "/" + patternCorrespondingUrl);
return response;
}
}
/* Make the search */
auto response = get_default_response();
response.set_mimeType("text/html; charset=utf-8");
response.set_taskbar(bookName, reader ? reader->getTitle() : "");
response.set_compress(true);
if ( (!reader && !bookName.empty())
|| (patternString.empty() && ! has_geo_query) ) {
auto data = get_default_data();
data.set("pattern", encodeDiples(patternString));
response.set_template(RESOURCE::templates::no_search_result_html, data);
response.set_code(MHD_HTTP_NOT_FOUND);
return response;
}
Searcher searcher;
if (reader) {
searcher.add_reader(reader.get());
} else {
for (auto& bookId: mp_library->filter(kiwix::Filter().local(true).valid(true))) {
auto currentReader = mp_library->getReaderById(bookId);
if (currentReader) {
searcher.add_reader(currentReader.get());
}
}
}
auto start = 0;
try {
start = request.get_argument<unsigned int>("start");
} catch (const std::exception&) {}
auto end = 25;
try {
end = request.get_argument<unsigned int>("end");
} catch (const std::exception&) {}
if (start>end) {
auto tmp = start;
start = end;
end = tmp;
}
if (end > start + MAX_SEARCH_LEN) {
end = start + MAX_SEARCH_LEN;
}
/* Get the results */
try {
if (patternString.empty()) {
searcher.geo_search(latitude, longitude, distance,
start, end, m_verbose.load());
} else {
searcher.search(patternString,
start, end, m_verbose.load());
}
SearchRenderer renderer(&searcher, mp_nameMapper);
renderer.setSearchPattern(patternString);
renderer.setSearchContent(bookName);
renderer.setProtocolPrefix(m_root + "/");
renderer.setSearchProtocolPrefix(m_root + "/search?");
response.set_content(renderer.getHtml());
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
}
return response;
}
Response InternalServer::handle_random(const RequestContext& request)
{
if (m_verbose.load()) {
printf("** running handle_random\n");
}
std::string bookName;
std::string bookId;
std::shared_ptr<Reader> reader;
try {
bookName = request.get_argument("content");
bookId = mp_nameMapper->getIdForName(bookName);
reader = mp_library->getReaderById(bookId);
} catch (const std::out_of_range&) {
return build_404(request, bookName);
}
if (reader == nullptr) {
return build_404(request, bookName);
}
try {
auto entry = reader->getRandomPage();
entry = entry.getFinalEntry();
auto response = get_default_response();
response.set_redirection(m_root + "/" + bookName + "/" + kiwix::urlEncode(entry.getPath()));
return response;
} catch(kiwix::NoEntry& e) {
return build_404(request, bookName);
}
}
Response InternalServer::handle_catalog(const RequestContext& request)
{
if (m_verbose.load()) {
printf("** running handle_catalog");
}
std::string host;
std::string url;
try {
host = request.get_header("Host");
url = request.get_url_part(1);
} catch (const std::out_of_range&) {
return build_404(request, "");
}
if (url != "searchdescription.xml" && url != "root.xml" && url != "search") {
return build_404(request, "");
}
auto response = get_default_response();
response.set_compress(true);
if (url == "searchdescription.xml") {
response.set_template(RESOURCE::opensearchdescription_xml, get_default_data());
response.set_mimeType("application/opensearchdescription+xml");
return response;
}
zim::Uuid uuid;
kiwix::OPDSDumper opdsDumper;
opdsDumper.setRootLocation(m_root);
opdsDumper.setSearchDescriptionUrl("catalog/searchdescription.xml");
opdsDumper.setId(kiwix::to_string(uuid));
opdsDumper.setLibrary(mp_library);
response.set_mimeType("application/atom+xml;profile=opds-catalog;kind=acquisition; charset=utf-8");
std::vector<std::string> bookIdsToDump;
if (url == "root.xml") {
opdsDumper.setTitle("All zims");
uuid = zim::Uuid::generate(host);
bookIdsToDump = mp_library->filter(kiwix::Filter().valid(true).local(true).remote(true));
} else if (url == "search") {
std::string query;
std::string language;
std::vector<std::string> tags;
std::vector<std::string> noTags;
size_t count(10);
size_t startIndex(0);
try {
query = request.get_argument("q");
} catch (const std::out_of_range&) {}
try {
language = request.get_argument("lang");
} catch (const std::out_of_range&) {}
try {
count = extractFromString<unsigned long>(request.get_argument("count"));
} catch (...) {}
try {
startIndex = extractFromString<unsigned long>(request.get_argument("start"));
} catch (...) {}
try {
tags = kiwix::split(request.get_argument("notag"), ";");
} catch (...) {}
try {
noTags = kiwix::split(request.get_argument("notag"), ";");
} catch (...) {}
opdsDumper.setTitle("Search result for " + query);
uuid = zim::Uuid::generate();
bookIdsToDump = mp_library->filter(
kiwix::Filter().valid(true).local(true).remote(true)
.query(query)
.lang(language)
.acceptTags(tags)
.rejectTags(noTags)
);
auto totalResults = bookIdsToDump.size();
bookIdsToDump.erase(bookIdsToDump.begin(), bookIdsToDump.begin()+startIndex);
if (count>0 && bookIdsToDump.size() > count) {
bookIdsToDump.resize(count);
}
opdsDumper.setOpenSearchInfo(totalResults, startIndex, bookIdsToDump.size());
}
response.set_content(opdsDumper.dumpOPDSFeed(bookIdsToDump));
return response;
}
Response InternalServer::handle_content(const RequestContext& request)
{
if (m_verbose.load()) {
printf("** running handle_content\n");
}
std::string baseUrl;
std::string content;
std::string mimeType;
kiwix::Entry entry;
std::string bookName;
try {
bookName = request.get_url_part(0);
} catch (const std::out_of_range& e) {
return build_homepage(request);
}
if (bookName.empty())
return build_homepage(request);
std::string bookId;
std::shared_ptr<Reader> reader;
try {
bookId = mp_nameMapper->getIdForName(bookName);
reader = mp_library->getReaderById(bookId);
} catch (const std::out_of_range& e) {
return build_404(request, bookName);
}
if (reader == nullptr) {
return build_404(request, bookName);
}
auto urlStr = request.get_url().substr(bookName.size()+1);
if (urlStr[0] == '/') {
urlStr = urlStr.substr(1);
}
try {
entry = reader->getEntryFromPath(urlStr);
if (entry.isRedirect() || urlStr.empty()) {
// If urlStr is empty, we want to mainPage.
// We must do a redirection to the real page.
entry = entry.getFinalEntry();
auto response = get_default_response();
response.set_redirection(m_root + "/" + bookName + "/" +
kiwix::urlEncode(entry.getPath()));
return response;
}
} catch(kiwix::NoEntry& e) {
if (m_verbose.load())
printf("Failed to find %s\n", urlStr.c_str());
return build_404(request, bookName);
}
try {
mimeType = entry.getMimetype();
} catch (exception& e) {
mimeType = "application/octet-stream";
}
if (m_verbose.load()) {
printf("Found %s\n", urlStr.c_str());
printf("mimeType: %s\n", mimeType.c_str());
}
if (mimeType.find("text/") != string::npos
|| mimeType.find("application/javascript") != string::npos
|| mimeType.find("application/json") != string::npos) {
zim::Blob raw_content = entry.getBlob();
content = string(raw_content.data(), raw_content.size());
auto response = get_default_response();
response.set_mimeType(mimeType);
/* Special rewrite URL in case of ZIM file use intern *asbolute* url like
* /A/Kiwix */
if (mimeType.find("text/html") != string::npos) {
content = replaceRegex(content,
"$1$2" + m_root + "/" + bookName + "/$3/",
"(href|src)(=[\"|\']{0,1})/([A-Z|\\-])/");
content = replaceRegex(content,
"$1$2" + m_root + "/" + bookName + "/$3/",
"(@import[ ]+)([\"|\']{0,1})/([A-Z|\\-])/");
response.set_taskbar(bookName, reader->getTitle());
} else if (mimeType.find("text/css") != string::npos) {
content = replaceRegex(content,
"$1$2" + m_root + "/" + bookName + "/$3/",
"(url|URL)(\\([\"|\']{0,1})/([A-Z|\\-])/");
}
response.set_content(content);
response.set_compress(true);
response.set_cache(true);
return response;
} else {
int range_len;
if (request.get_range().second == -1) {
range_len = entry.getSize() - request.get_range().first;
} else {
range_len = request.get_range().second - request.get_range().first;
}
auto response = get_default_response();
response.set_entry(entry);
response.set_mimeType(mimeType);
response.set_range_first(request.get_range().first);
response.set_range_len(range_len);
response.set_cache(true);
return response;
}
}
}

View File

@@ -0,0 +1,211 @@
/*
* Copyright 2009-2016 Emmanuel Engelhart <kelson@kiwix.org>
* Copyright 2017 Matthieu Gautier<mgautier@kymeria.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#include "request_context.h"
#include <string.h>
#include <stdexcept>
#include <sstream>
#include <cstdio>
#include <atomic>
namespace kiwix {
static std::atomic_ullong s_requestIndex(0);
RequestContext::RequestContext(struct MHD_Connection* connection,
std::string rootLocation,
const std::string& _url,
const std::string& method,
const std::string& version) :
full_url(_url),
url(_url),
valid_url(true),
version(version),
requestIndex(s_requestIndex++),
acceptEncodingDeflate(false),
accept_range(false),
range_pair(0, -1)
{
if (method == "GET") {
this->method = RequestMethod::GET;
} else if (method == "HEAD") {
this->method = RequestMethod::HEAD;
} else if (method == "POST") {
this->method = RequestMethod::POST;
} else if (method == "PUT") {
this->method = RequestMethod::PUT;
} else if (method == "DELETE") {
this->method = RequestMethod::DELETE_;
} else if (method == "CONNECT") {
this->method = RequestMethod::CONNECT;
} else if (method == "OPTIONS") {
this->method = RequestMethod::OPTIONS;
} else if (method == "TRACE") {
this->method = RequestMethod::TRACE;
} else if (method == "PATCH") {
this->method = RequestMethod::PATCH;
} else {
this->method = RequestMethod::OTHER;
}
MHD_get_connection_values(connection, MHD_HEADER_KIND, &RequestContext::fill_header, this);
MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, &RequestContext::fill_argument, this);
valid_url = true;
if (rootLocation.empty()) {
// nothing special to handle.
url = full_url;
} else {
if (full_url == rootLocation) {
url = "/";
} else if (full_url.size() > rootLocation.size() &&
full_url.substr(0, rootLocation.size()+1) == rootLocation + "/") {
url = full_url.substr(rootLocation.size());
} else {
valid_url = false;
}
}
try {
acceptEncodingDeflate =
(get_header(MHD_HTTP_HEADER_ACCEPT_ENCODING).find("deflate") != std::string::npos);
} catch (const std::out_of_range&) {}
/*Check if range is requested. */
try {
auto range = get_header(MHD_HTTP_HEADER_RANGE);
int start = 0;
int end = -1;
std::istringstream iss(range);
char c;
iss >> start >> c;
if (iss.good() && c=='-') {
iss >> end;
if (iss.fail()) {
// Something went wrong will extracting.
end = -1;
}
if (iss.eof()) {
accept_range = true;
range_pair = std::pair<int, int>(start, end);
}
}
} catch (const std::out_of_range&) {}
}
RequestContext::~RequestContext()
{}
int RequestContext::fill_header(void *__this, enum MHD_ValueKind kind,
const char *key, const char *value)
{
RequestContext *_this = static_cast<RequestContext*>(__this);
_this->headers[key] = value;
return MHD_YES;
}
int RequestContext::fill_argument(void *__this, enum MHD_ValueKind kind,
const char *key, const char* value)
{
RequestContext *_this = static_cast<RequestContext*>(__this);
_this->arguments[key] = value == nullptr ? "" : value;
return MHD_YES;
}
void RequestContext::print_debug_info() const {
printf("method : %s (%d)\n", method==RequestMethod::GET ? "GET" :
method==RequestMethod::POST ? "POST" :
"OTHER", (int)method);
printf("version : %s\n", version.c_str());
printf("request# : %lld\n", requestIndex);
printf("headers :\n");
for (auto it=headers.begin(); it!=headers.end(); it++) {
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());
}
printf("Parsed : \n");
printf("url : %s\n", url.c_str());
printf("acceptEncodingDeflate : %d\n", acceptEncodingDeflate);
printf("has_range : %d\n", accept_range);
printf("is_valid_url : %d\n", valid_url);
printf(".............\n");
}
RequestMethod RequestContext::get_method() const {
return method;
}
std::string RequestContext::get_url() const {
return url;
}
std::string RequestContext::get_url_part(int number) const {
size_t start = 1;
while(true) {
auto found = url.find('/', start);
if (number == 0) {
if (found == std::string::npos) {
return url.substr(start);
} else {
return url.substr(start, found-start);
}
} else {
if (found == std::string::npos) {
throw std::out_of_range("No parts");
}
start = found + 1;
number -= 1;
}
}
}
std::string RequestContext::get_full_url() const {
return full_url;
}
bool RequestContext::is_valid_url() const {
return valid_url;
}
bool RequestContext::has_range() const {
return accept_range;
}
std::pair<int, int> RequestContext::get_range() const {
return range_pair;
}
template<>
std::string RequestContext::get_argument(const std::string& name) const {
return arguments.at(name);
}
std::string RequestContext::get_header(const std::string& name) const {
return headers.at(name);
}
}

View File

@@ -0,0 +1,109 @@
/*
* Copyright 2009-2016 Emmanuel Engelhart <kelson@kiwix.org>
* Copyright 2017 Matthieu Gautier<mgautier@kymeria.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#ifndef REQUEST_CONTEXT_H
#define REQUEST_CONTEXT_H
#include <string>
#include <sstream>
#include <map>
#include <stdexcept>
extern "C" {
#include <microhttpd.h>
}
namespace kiwix {
enum class RequestMethod {
GET,
HEAD,
POST,
PUT,
DELETE_,
CONNECT,
OPTIONS,
TRACE,
PATCH,
OTHER
};
class KeyError : public std::runtime_error {};
class IndexError: public std::runtime_error {};
class RequestContext {
public:
RequestContext(struct MHD_Connection* connection,
std::string rootLocation,
const std::string& url,
const std::string& method,
const std::string& version);
~RequestContext();
void print_debug_info() const;
bool is_valid_url() const;
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;
}
RequestMethod get_method() const;
std::string get_url() const;
std::string get_url_part(int part) const;
std::string get_full_url() const;
bool has_range() const;
std::pair<int, int> get_range() const;
bool can_compress() const { return acceptEncodingDeflate; }
private:
std::string full_url;
std::string url;
bool valid_url;
RequestMethod method;
std::string version;
unsigned long long requestIndex;
bool acceptEncodingDeflate;
bool accept_range;
std::pair<int, int> range_pair;
std::map<std::string, std::string> headers;
std::map<std::string, std::string> arguments;
static int fill_header(void *, enum MHD_ValueKind, const char*, const char*);
static int fill_argument(void *, enum MHD_ValueKind, const char*, const char*);
};
template<> std::string RequestContext::get_argument(const std::string& name) const;
}
#endif //REQUEST_CONTEXT_H

250
src/server/response.cpp Normal file
View File

@@ -0,0 +1,250 @@
#include "response.h"
#include "request_context.h"
#include "kiwixlib-resources.h"
#include "tools/regexTools.h"
#include "tools/stringTools.h"
#include "string.h"
#include <mustache.hpp>
#include <zlib.h>
#define KIWIX_MIN_CONTENT_SIZE_TO_DEFLATE 100
namespace kiwix {
Response::Response(const std::string& root, bool verbose, bool withTaskbar, bool withLibraryButton)
: m_verbose(verbose),
m_root(root),
m_content(""),
m_mimeType(""),
m_returnCode(MHD_HTTP_OK),
m_withTaskbar(withTaskbar),
m_withLibraryButton(withLibraryButton),
m_useCache(false),
m_addTaskbar(false),
m_bookName(""),
m_startRange(0),
m_lenRange(0)
{
}
static int print_key_value (void *cls, enum MHD_ValueKind kind,
const char *key, const char *value)
{
printf (" - %s: '%s'\n", key, value);
return MHD_YES;
}
struct RunningResponse {
kiwix::Entry entry;
int range_start;
RunningResponse(kiwix::Entry entry,
int range_start) :
entry(entry),
range_start(range_start)
{}
};
static ssize_t callback_reader_from_entry(void* cls,
uint64_t pos,
char* buf,
size_t max)
{
RunningResponse* response = static_cast<RunningResponse*>(cls);
size_t max_size_to_set = min<size_t>(
max,
response->entry.getSize() - pos - response->range_start);
if (max_size_to_set <= 0) {
return MHD_CONTENT_READER_END_WITH_ERROR;
}
zim::Blob blob = response->entry.getBlob(response->range_start+pos, max_size_to_set);
memcpy(buf, blob.data(), max_size_to_set);
return max_size_to_set;
}
static void callback_free_response(void* cls)
{
RunningResponse* response = static_cast<RunningResponse*>(cls);
delete response;
}
void print_response_info(int retCode, MHD_Response* response)
{
printf("Response :\n");
printf("httpResponseCode : %d\n", retCode);
printf("headers :\n");
MHD_get_response_headers(response, print_key_value, nullptr);
}
std::string 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();
}
void Response::introduce_taskbar()
{
if (! m_withTaskbar)
// Taskbar is globally disabled.
return;
kainjow::mustache::data data;
data.set("root", m_root);
data.set("content", m_bookName);
data.set("hascontent", !m_bookName.empty());
data.set("title", m_bookTitle);
data.set("withlibrarybutton", m_withLibraryButton);
auto head_content = render_template(RESOURCE::templates::head_part_html, data);
m_content = appendToFirstOccurence(
m_content,
"<head>",
head_content);
auto taskbar_part = render_template(RESOURCE::templates::taskbar_part_html, data);
m_content = appendToFirstOccurence(
m_content,
"<body[^>]*>",
taskbar_part);
}
int Response::send(const RequestContext& request, MHD_Connection* connection)
{
MHD_Response* response = nullptr;
switch (m_mode) {
case ResponseMode::RAW_CONTENT : {
if (m_addTaskbar) {
introduce_taskbar();
}
bool shouldCompress = m_compress && request.can_compress();
shouldCompress &= m_mimeType.find("text/") != string::npos
|| m_mimeType.find("application/javascript") != string::npos
|| m_mimeType.find("application/json") != string::npos;
shouldCompress &= (m_content.size() > KIWIX_MIN_CONTENT_SIZE_TO_DEFLATE);
if (shouldCompress) {
std::vector<Bytef> compr_buffer(compressBound(m_content.size()));
uLongf comprLen = compr_buffer.capacity();
int err = compress(&compr_buffer[0],
&comprLen,
(const Bytef*)(m_content.data()),
m_content.size());
if (err == Z_OK && comprLen > 2 && comprLen < (m_content.size() + 2)) {
/* /!\ Internet Explorer has a bug with deflate compression.
It can not handle the first two bytes (compression headers)
We need to chunk them off (move the content 2bytes)
It has no incidence on other browsers
See http://www.subbu.org/blog/2008/03/ie7-deflate-or-not and comments */
m_content = string((char*)&compr_buffer[2], comprLen - 2);
} else {
shouldCompress = false;
}
}
response = MHD_create_response_from_buffer(
m_content.size(), const_cast<char*>(m_content.data()), MHD_RESPMEM_MUST_COPY);
if (shouldCompress) {
MHD_add_response_header(
response, MHD_HTTP_HEADER_VARY, "Accept-Encoding");
MHD_add_response_header(
response, MHD_HTTP_HEADER_CONTENT_ENCODING, "deflate");
}
MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, m_mimeType.c_str());
break;
}
case ResponseMode::REDIRECTION : {
response = MHD_create_response_from_buffer(0, nullptr, MHD_RESPMEM_MUST_COPY);
MHD_add_response_header(response, MHD_HTTP_HEADER_LOCATION, m_content.c_str());
break;
}
case ResponseMode::ENTRY : {
response = MHD_create_response_from_callback(m_entry.getSize(),
16384,
callback_reader_from_entry,
new RunningResponse(m_entry, m_startRange),
callback_free_response);
MHD_add_response_header(response,
MHD_HTTP_HEADER_CONTENT_TYPE, m_mimeType.c_str());
MHD_add_response_header(response, MHD_HTTP_HEADER_ACCEPT_RANGES, "bytes");
std::ostringstream oss;
oss << "bytes " << m_startRange << "-" << m_startRange + m_lenRange - 1
<< "/" << m_entry.getSize();
MHD_add_response_header(response,
MHD_HTTP_HEADER_CONTENT_RANGE, oss.str().c_str());
MHD_add_response_header(response,
MHD_HTTP_HEADER_CONTENT_LENGTH, kiwix::to_string(m_lenRange).c_str());
break;
}
}
MHD_add_response_header(response, "Access-Control-Allow-Origin", "*");
MHD_add_response_header(response, MHD_HTTP_HEADER_CACHE_CONTROL,
m_useCache ? "max-age=2723040, public" : "no-cache, no-store, must-revalidate");
if (m_returnCode == MHD_HTTP_OK && request.has_range())
m_returnCode = MHD_HTTP_PARTIAL_CONTENT;
if (m_verbose)
print_response_info(m_returnCode, response);
auto ret = MHD_queue_response(connection, m_returnCode, response);
MHD_destroy_response(response);
return ret;
}
void Response::set_template(const std::string& template_str, kainjow::mustache::data data) {
set_content(render_template(template_str, data));
}
void Response::set_content(const std::string& content) {
m_content = content;
m_mode = ResponseMode::RAW_CONTENT;
}
void Response::set_redirection(const std::string& url) {
m_content = url;
m_mode = ResponseMode::REDIRECTION;
m_returnCode = MHD_HTTP_FOUND;
}
void Response::set_entry(const Entry& entry) {
m_entry = entry;
m_mode = ResponseMode::ENTRY;
}
void Response::set_taskbar(const std::string& bookName, const std::string& bookTitle)
{
m_addTaskbar = true;
m_bookName = bookName;
m_bookTitle = bookTitle;
}
}

89
src/server/response.h Normal file
View File

@@ -0,0 +1,89 @@
/*
* Copyright 2019 Matthieu Gautier<mgautier@kymeria.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#ifndef KIWIXLIB_SERVER_RESPONSE_H
#define KIWIXLIB_SERVER_RESPONSE_H
#include <string>
#include <mustache.hpp>
#include "entry.h"
extern "C" {
#include <microhttpd.h>
}
namespace kiwix {
enum class ResponseMode {
RAW_CONTENT,
REDIRECTION,
ENTRY
};
class RequestContext;
class Response {
public:
Response(const std::string& root, bool verbose, bool withTaskbar, bool withLibraryButton);
~Response() = default;
int send(const RequestContext& request, MHD_Connection* connection);
void set_template(const std::string& template_str, kainjow::mustache::data data);
void set_content(const std::string& content);
void set_redirection(const std::string& url);
void set_entry(const Entry& entry);
void set_mimeType(const std::string& mimeType) { m_mimeType = mimeType; }
void set_code(int code) { m_returnCode = code; }
void set_cache(bool cache) { m_useCache = cache; }
void set_compress(bool compress) { m_compress = compress; }
void set_taskbar(const std::string& bookName, const std::string& bookTitle);
void set_range_first(uint64_t start) { m_startRange = start; }
void set_range_len(uint64_t len) { m_lenRange = len; }
int getReturnCode() { return m_returnCode; }
void introduce_taskbar();
private:
bool m_verbose;
ResponseMode m_mode;
std::string m_root;
std::string m_content;
Entry m_entry;
std::string m_mimeType;
int m_returnCode;
bool m_withTaskbar;
bool m_withLibraryButton;
bool m_useCache;
bool m_compress;
bool m_addTaskbar;
std::string m_bookName;
std::string m_bookTitle;
uint64_t m_startRange;
uint64_t m_lenRange;
};
}
#endif //KIWIXLIB_SERVER_RESPONSE_H

View File

@@ -46,7 +46,7 @@ void* UnixImpl::waitForPID(void* _self)
#endif
UnixImpl* self = static_cast<UnixImpl*>(_self);
waitpid(self->m_pid, NULL, WEXITED);
waitpid(self->m_pid, NULL, 0);
pthread_mutex_lock(&self->m_mutex);
self->m_running = false;
@@ -67,7 +67,7 @@ void UnixImpl::run(commandLine_t& commandLine)
commandLine.push_back(NULL);
if (execvp(binary, const_cast<char* const*>(commandLine.data()))) {
perror("Cannot launch\n");
exit(-1);
_exit(-1);
}
break;

View File

@@ -19,167 +19,17 @@
#include <tools/networkTools.h>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <net/if.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#endif
#include <curl/curl.h>
#include <sstream>
#include <iostream>
#include <stdexcept>
std::map<std::string, std::string> kiwix::getNetworkInterfaces()
{
std::map<std::string, std::string> interfaces;
#ifdef _WIN32
SOCKET sd = WSASocket(AF_INET, SOCK_DGRAM, 0, 0, 0, 0);
if (sd == (SOCKET)SOCKET_ERROR) {
std::cerr << "Failed to get a socket. Error " << WSAGetLastError()
<< std::endl;
return interfaces;
}
INTERFACE_INFO InterfaceList[20];
unsigned long nBytesReturned;
if (WSAIoctl(sd,
SIO_GET_INTERFACE_LIST,
0,
0,
&InterfaceList,
sizeof(InterfaceList),
&nBytesReturned,
0,
0)
== SOCKET_ERROR) {
std::cerr << "Failed calling WSAIoctl: error " << WSAGetLastError()
<< std::endl;
return interfaces;
}
int nNumInterfaces = nBytesReturned / sizeof(INTERFACE_INFO);
for (int i = 0; i < nNumInterfaces; ++i) {
sockaddr_in* pAddress;
pAddress = (sockaddr_in*)&(InterfaceList[i].iiAddress);
/* Add to the map */
std::string interfaceName = std::string(inet_ntoa(pAddress->sin_addr));
std::string interfaceIp = std::string(inet_ntoa(pAddress->sin_addr));
interfaces.insert(
std::pair<std::string, std::string>(interfaceName, interfaceIp));
}
#else
/* Get Network interfaces information */
char buf[16384];
struct ifconf ifconf;
int fd = socket(PF_INET, SOCK_DGRAM, 0); /* Only IPV4 */
ifconf.ifc_len = sizeof buf;
ifconf.ifc_buf = buf;
if (ioctl(fd, SIOCGIFCONF, &ifconf) != 0) {
perror("ioctl(SIOCGIFCONF)");
exit(EXIT_FAILURE);
}
/* Go through each interface */
int i;
size_t len;
struct ifreq* ifreq;
ifreq = ifconf.ifc_req;
for (i = 0; i < ifconf.ifc_len;) {
if (ifreq->ifr_addr.sa_family == AF_INET) {
/* Get the network interface ip */
char host[128] = {0};
const int error = getnameinfo(&(ifreq->ifr_addr),
sizeof ifreq->ifr_addr,
host,
sizeof host,
0,
0,
NI_NUMERICHOST);
if (!error) {
std::string interfaceName = std::string(ifreq->ifr_name);
std::string interfaceIp = std::string(host);
/* Add to the map */
interfaces.insert(
std::pair<std::string, std::string>(interfaceName, interfaceIp));
} else {
perror("getnameinfo()");
}
}
/* some systems have ifr_addr.sa_len and adjust the length that
* way, but not mine. weird */
#ifndef __linux__
len = IFNAMSIZ + ifreq->ifr_addr.sa_len;
#else
len = sizeof *ifreq;
#endif
ifreq = (struct ifreq*)((char*)ifreq + len);
i += len;
}
#endif
return interfaces;
}
std::string kiwix::getBestPublicIp()
{
std::map<std::string, std::string> interfaces = kiwix::getNetworkInterfaces();
#ifndef _WIN32
const char* const prioritizedNames[]
= {"eth0", "eth1", "wlan0", "wlan1", "en0", "en1"};
const int count = (sizeof prioritizedNames) / (sizeof prioritizedNames[0]);
for (int i = 0; i < count; ++i) {
std::map<std::string, std::string>::const_iterator it
= interfaces.find(prioritizedNames[i]);
if (it != interfaces.end()) {
return it->second;
}
}
#endif
for (std::map<std::string, std::string>::iterator iter = interfaces.begin();
iter != interfaces.end();
++iter) {
std::string interfaceIp = iter->second;
if (interfaceIp.length() >= 7 && interfaceIp.substr(0, 7) == "192.168") {
return interfaceIp;
}
}
for (std::map<std::string, std::string>::iterator iter = interfaces.begin();
iter != interfaces.end();
++iter) {
std::string interfaceIp = iter->second;
if (interfaceIp.length() >= 7 && interfaceIp.substr(0, 7) == "172.16.") {
return interfaceIp;
}
}
for (std::map<std::string, std::string>::iterator iter = interfaces.begin();
iter != interfaces.end();
++iter) {
std::string interfaceIp = iter->second;
if (interfaceIp.length() >= 3 && interfaceIp.substr(0, 3) == "10.") {
return interfaceIp;
}
}
return "127.0.0.1";
}
size_t write_callback_to_iss(char* ptr, size_t size, size_t nmemb, void* userdata)
{
auto str = static_cast<std::stringstream*>(userdata);

View File

@@ -17,283 +17,163 @@
* MA 02110-1301, USA.
*/
#include <tools/otherTools.h>
#include "tools/otherTools.h"
#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif
#include <map>
#include <pugixml.hpp>
static std::map<std::string, std::string> codeisomapping {
//a
{ "ad", "and" },
{ "ae", "are" },
{ "af", "afg" },
{ "ag", "atg" },
{ "ai", "aia" },
{ "al", "alb" },
{ "am", "arm" },
{ "an", "ant" },
{ "ao", "ago" },
{ "aq", "ata" },
{ "ar", "arg" },
{ "as", "asm" },
{ "at", "aut" },
{ "au", "aus" },
{ "aw", "abw" },
{ "ax", "ala" },
{ "az", "aze" },
//b
{ "ba", "bih" },
{ "bb", "brb" },
{ "bd", "bgd" },
{ "be", "bel" },
{ "bf", "bfa" },
{ "bg", "bgr" },
{ "bh", "bhr" },
{ "bi", "bdi" },
{ "bj", "ben" },
{ "bl", "blm" },
{ "bn", "brn" },
{ "bm", "bmu" },
{ "bo", "bol" },
{ "br", "bra" },
{ "bs", "bhs" },
{ "bt", "btn" },
{ "bv", "bvt" },
{ "bw", "bwa" },
{ "by", "blr" },
{ "bz", "blz" },
//c
{ "ca", "can" },
{ "cc", "cck" },
{ "cd", "cod" },
{ "cf", "caf" },
{ "cg", "cog" },
{ "ch", "che" },
{ "ci", "civ" },
{ "ck", "cok" },
{ "cl", "chl" },
{ "cm", "cmr" },
{ "cn", "chn" },
{ "co", "col" },
{ "cr", "cri" },
{ "cu", "cub" },
{ "cv", "cpv" },
{ "cx", "cxr" },
{ "cy", "cyp" },
{ "cz", "cze" },
//d
{ "de", "deu" },
{ "dj", "dji" },
{ "dk", "dnk" },
{ "dm", "dma" },
{ "do", "dom" },
{ "dz", "dza" },
//e
{ "ec", "ecu" },
{ "ee", "est" },
{ "eg", "egy" },
{ "eh", "esh" },
{ "en", "eng" },
{ "er", "eri" },
{ "es", "esp" },
{ "et", "eth" },
//f
{ "fi", "fin" },
{ "fj", "fji" },
{ "fk", "flk" },
{ "fm", "fsm" },
{ "fo", "fro" },
{ "fr", "fra" },
//g
{ "ga", "gab" },
{ "gb", "gbr" },
{ "gd", "grd" },
{ "ge", "geo" },
{ "gf", "guf" },
{ "gg", "ggy" },
{ "gh", "gha" },
{ "gi", "gib" },
{ "gl", "grl" },
{ "gm", "gmb" },
{ "gn", "gin" },
{ "gp", "glp" },
{ "gq", "gnq" },
{ "gr", "grc" },
{ "gs", "sgs" },
{ "gt", "gtm" },
{ "gu", "gum" },
{ "gw", "gnb" },
{ "gy", "guy" },
//h
{ "hk", "hkg" },
{ "hm", "hmd" },
{ "hn", "hnd" },
{ "hr", "hrv" },
{ "ht", "hti" },
{ "hu", "hun" },
//i
{ "id", "idn" },
{ "ie", "irl" },
{ "il", "isr" },
{ "im", "imn" },
{ "in", "ind" },
{ "io", "iot" },
{ "iq", "irq" },
{ "ir", "irn" },
{ "is", "isl" },
{ "it", "ita" },
//j
{ "je", "jey" },
{ "jm", "jam" },
{ "jo", "jor" },
{ "jp", "jpn" },
//k
{ "ke", "ken" },
{ "kg", "kgz" },
{ "kh", "khm" },
{ "ki", "kir" },
{ "km", "com" },
{ "kn", "kna" },
{ "kp", "prk" },
{ "kr", "kor" },
{ "kw", "kwt" },
{ "ky", "cym" },
{ "kz", "kaz" },
//l
{ "la", "lao" },
{ "lb", "lbn" },
{ "lc", "lca" },
{ "li", "lie" },
{ "lk", "lka" },
{ "lr", "lbr" },
{ "ls", "lso" },
{ "lt", "ltu" },
{ "lu", "lux" },
{ "lv", "lva" },
{ "ly", "lby" },
//m
{ "ma", "mar" },
{ "mc", "mco" },
{ "md", "mda" },
{ "me", "mne" },
{ "mf", "maf" },
{ "mg", "mdg" },
{ "mh", "mhl" },
{ "mk", "mkd" },
{ "ml", "mli" },
{ "mm", "mmr" },
{ "mn", "mng" },
{ "mo", "mac" },
{ "mp", "mnp" },
{ "mq", "mtq" },
{ "mr", "mrt" },
{ "ms", "msr" },
{ "mt", "mlt" },
{ "mu", "mus" },
{ "mv", "mdv" },
{ "mw", "mwi" },
{ "mx", "mex" },
{ "my", "mys" },
{ "mz", "moz" },
//n
{ "na", "nam" },
{ "nc", "ncl" },
{ "ne", "ner" },
{ "nf", "nfk" },
{ "ng", "nga" },
{ "ni", "nic" },
{ "nl", "nld" },
{ "no", "nor" },
{ "np", "npl" },
{ "nr", "nru" },
{ "nu", "niu" },
{ "nz", "nzl" },
//o
{ "om", "omn" },
//p
{ "pa", "pan" },
{ "pe", "per" },
{ "pf", "pyf" },
{ "pg", "png" },
{ "ph", "phl" },
{ "pk", "pak" },
{ "pl", "pol" },
{ "pm", "spm" },
{ "pn", "pcn" },
{ "pr", "pri" },
{ "ps", "pse" },
{ "pt", "prt" },
{ "pw", "plw" },
{ "py", "pry" },
//q
{ "qa", "qat" },
//r
{ "re", "reu" },
{ "ro", "rou" },
{ "rs", "srb" },
{ "ru", "rus" },
{ "rw", "rwa" },
//s
{ "sa", "sau" },
{ "sb", "slb" },
{ "sc", "syc" },
{ "sd", "sdn" },
{ "se", "swe" },
{ "sg", "sgp" },
{ "sh", "shn" },
{ "si", "svn" },
{ "sj", "sjm" },
{ "sk", "svk" },
{ "sl", "sle" },
{ "sm", "smr" },
{ "sn", "sen" },
{ "so", "som" },
{ "sr", "sur" },
{ "ss", "ssd" },
{ "st", "stp" },
{ "sv", "slv" },
{ "sy", "syr" },
{ "sz", "swz" },
//t
{ "tc", "tca" },
{ "td", "tcd" },
{ "tf", "atf" },
{ "tg", "tgo" },
{ "th", "tha" },
{ "tj", "tjk" },
{ "tk", "tkl" },
{ "tl", "tls" },
{ "tm", "tkm" },
{ "tn", "tun" },
{ "to", "ton" },
{ "tr", "tur" },
{ "tt", "tto" },
{ "tv", "tuv" },
{ "tw", "twn" },
{ "tz", "tza" },
//u
{ "ua", "ukr" },
{ "ug", "uga" },
{ "um", "umi" },
{ "us", "usa" },
{ "uy", "ury" },
{ "uz", "uzb" },
//v
{ "va", "vat" },
{ "vc", "vct" },
{ "ve", "ven" },
{ "vg", "vgb" },
{ "vi", "vir" },
{ "vn", "vnm" },
{ "vu", "vut" },
//w
{ "wf", "wlf" },
{ "ws", "wsm" },
//y
{ "ye", "yem" },
{ "yt", "myt" },
// z
{ "za", "zaf" },
{ "zm", "zmb" },
{ "zw", "zwe" }
{ "aa", "aar" },
{ "af", "afr" },
{ "ak", "aka" },
{ "am", "amh" },
{ "ar", "ara" },
{ "as", "asm" },
{ "az", "aze" },
{ "ba", "bak" },
{ "be", "bel" },
{ "bg", "bul" },
{ "bm", "bam" },
{ "bn", "ben" },
{ "bo", "bod" },
{ "br", "bre" },
{ "bs", "bos" },
{ "ca", "cat" },
{ "ce", "che" },
{ "co", "cos" },
{ "cs", "ces" },
{ "cu", "chu" },
{ "cv", "chv" },
{ "cy", "cym" },
{ "da", "dan" },
{ "de", "deu" },
{ "dv", "div" },
{ "dz", "dzo" },
{ "ee", "ewe" },
{ "el", "ell" },
{ "en", "eng" },
{ "es", "spa" },
{ "et", "est" },
{ "eu", "eus" },
{ "fa", "fas" },
{ "ff", "ful" },
{ "fi", "fin" },
{ "fo", "fao" },
{ "fr", "fra" },
{ "fy", "fry" },
{ "ga", "gle" },
{ "gd", "gla" },
{ "gl", "glg" },
{ "gn", "grn" },
{ "gu", "guj" },
{ "gv", "glv" },
{ "ha", "hau" },
{ "he", "heb" },
{ "hi", "hin" },
{ "hr", "hrv" },
{ "hu", "hun" },
{ "hy", "hye" },
{ "ia", "ina" },
{ "id", "ind" },
{ "ig", "ibo" },
{ "is", "isl" },
{ "it", "ita" },
{ "iu", "iku" },
{ "ja", "jpn" },
{ "jv", "jav" },
{ "ka", "kat" },
{ "ki", "kik" },
{ "kk", "kaz" },
{ "kl", "kal" },
{ "km", "khm" },
{ "kn", "kan" },
{ "ko", "kor" },
{ "ks", "kas" },
{ "ku", "kur" },
{ "kw", "cor" },
{ "ky", "kir" },
{ "lb", "ltz" },
{ "lg", "lug" },
{ "ln", "lin" },
{ "lo", "lao" },
{ "lt", "lit" },
{ "lv", "lav" },
{ "mg", "mlg" },
{ "mi", "mri" },
{ "mi", "mri" },
{ "mk", "mkd" },
{ "ml", "mal" },
{ "mn", "mon" },
{ "mr", "mar" },
{ "ms", "msa" },
{ "mt", "mlt" },
{ "my", "mya" },
{ "nb", "nob" },
{ "ne", "nep" },
{ "nl", "nld" },
{ "nn", "nno" },
{ "no", "nor" },
{ "ny", "nya" },
{ "oc", "oci" },
{ "om", "orm" },
{ "or", "ori" },
{ "os", "oss" },
{ "pa", "pan" },
{ "pl", "pol" },
{ "ps", "pus" },
{ "pt", "por" },
{ "qu", "que" },
{ "rm", "roh" },
{ "rn", "run" },
{ "ro", "ron" },
{ "ru", "rus" },
{ "rw", "kin" },
{ "sa", "san" },
{ "sd", "snd" },
{ "se", "sme" },
{ "sg", "sag" },
{ "si", "sin" },
{ "sk", "slk" },
{ "sl", "slv" },
{ "sn", "sna" },
{ "so", "som" },
{ "sq", "sqi" },
{ "sr", "srp" },
{ "ss", "ssw" },
{ "st", "sot" },
{ "sv", "swe" },
{ "sw", "swa" },
{ "ta", "tam" },
{ "te", "tel" },
{ "tg", "tgk" },
{ "th", "tha" },
{ "ti", "tir" },
{ "tk", "tuk" },
{ "tl", "tgl" },
{ "tn", "tsn" },
{ "to", "ton" },
{ "tr", "tur" },
{ "ts", "tso" },
{ "tt", "tat" },
{ "ug", "uig" },
{ "uk", "ukr" },
{ "ur", "urd" },
{ "uz", "uzb" },
{ "ve", "ven" },
{ "vi", "vie" },
{ "wa", "wln" },
{ "wo", "wol" },
{ "xh", "xho" },
{ "yo", "yor" },
{ "zh", "zho" },
{ "zu", "zul" }
};
void kiwix::sleep(unsigned int milliseconds)
@@ -314,7 +194,7 @@ struct XmlStringWriter: pugi::xml_writer
}
};
std::string kiwix::nodeToString(pugi::xml_node node)
std::string kiwix::nodeToString(const pugi::xml_node& node)
{
XmlStringWriter writer;
node.print(writer, " ");

View File

@@ -17,7 +17,7 @@
* MA 02110-1301, USA.
*/
#include <tools/pathTools.h>
#include "tools/pathTools.h"
#ifdef __APPLE__
#include <limits.h>
@@ -29,10 +29,16 @@
#define getcwd _getcwd // stupid MSFT "deprecation" warning
#endif
#ifdef _WIN32
#else
#include <unistd.h>
#endif
#include "tools/stringTools.h"
#include <string.h>
#include <map>
#include <vector>
#include <sys/stat.h>
#include <sstream>
#include <fstream>
#include <iomanip>
#include <iostream>
#ifdef _WIN32
const std::string SEPARATOR("\\");
@@ -47,7 +53,7 @@ const std::string SEPARATOR("/");
#define PATH_MAX 1024
#endif
bool isRelativePath(const string& path)
bool isRelativePath(const std::string& path)
{
#ifdef _WIN32
return path.empty() || path.substr(1, 2) == ":\\" ? false : true;
@@ -56,7 +62,7 @@ bool isRelativePath(const string& path)
#endif
}
string computeRelativePath(const string path, const string absolutePath)
std::string computeRelativePath(const std::string& path, const std::string& absolutePath)
{
std::vector<std::string> pathParts = kiwix::split(path, SEPARATOR);
std::vector<std::string> absolutePathParts
@@ -69,7 +75,7 @@ string computeRelativePath(const string path, const string absolutePath)
commonCount++;
}
string relativePath;
std::string relativePath;
#ifdef _WIN32
/* On Windows you have a token more because the root is represented
by a letter */
@@ -88,10 +94,16 @@ string computeRelativePath(const string path, const string absolutePath)
return relativePath;
}
#ifdef _WIN32
# define STRTOK strtok_s
#else
# define STRTOK strtok_r
#endif
/* Warning: the relative path must be with slashes */
string computeAbsolutePath(const string path, const string relativePath)
std::string computeAbsolutePath(const std::string& path, const std::string& relativePath)
{
string absolutePath;
std::string absolutePath;
if (path.empty()) {
char* path = NULL;
@@ -103,7 +115,7 @@ string computeAbsolutePath(const string path, const string relativePath)
path = getcwd(path, size);
#endif
absolutePath = string(path) + SEPARATOR;
absolutePath = std::string(path) + SEPARATOR;
} else {
absolutePath = path.substr(path.length() - 1, 1) == SEPARATOR
? path
@@ -115,31 +127,33 @@ string computeAbsolutePath(const string path, const string relativePath)
#else
char* cRelativePath = strdup(relativePath.c_str());
#endif
char* token = strtok(cRelativePath, "/");
char* saveptr = nullptr;
char* token = STRTOK(cRelativePath, "/", &saveptr);
while (token != NULL) {
if (string(token) == "..") {
if (std::string(token) == "..") {
absolutePath = removeLastPathElement(absolutePath, true, false);
token = strtok(NULL, "/");
token = STRTOK(NULL, "/", &saveptr);
} else if (strcmp(token, ".") && strcmp(token, "")) {
absolutePath += string(token);
token = strtok(NULL, "/");
absolutePath += std::string(token);
token = STRTOK(NULL, "/", &saveptr);
if (token != NULL) {
absolutePath += SEPARATOR;
}
} else {
token = strtok(NULL, "/");
token = STRTOK(NULL, "/", &saveptr);
}
}
free(cRelativePath);
return absolutePath;
}
string removeLastPathElement(const string path,
const bool removePreSeparator,
const bool removePostSeparator)
std::string removeLastPathElement(const std::string& path,
const bool removePreSeparator,
const bool removePostSeparator)
{
string newPath = path;
std::string newPath = path;
size_t offset = newPath.find_last_of(SEPARATOR);
if (removePreSeparator &&
#ifndef _WIN32
@@ -154,18 +168,18 @@ string removeLastPathElement(const string path,
return newPath;
}
string appendToDirectory(const string& directoryPath, const string& filename)
std::string appendToDirectory(const std::string& directoryPath, const std::string& filename)
{
string newPath = directoryPath + SEPARATOR + filename;
std::string newPath = directoryPath + SEPARATOR + filename;
return newPath;
}
string getLastPathElement(const string& path)
std::string getLastPathElement(const std::string& path)
{
return path.substr(path.find_last_of(SEPARATOR) + 1);
}
unsigned int getFileSize(const string& path)
unsigned int getFileSize(const std::string& path)
{
#ifdef _WIN32
struct _stat filestatus;
@@ -178,14 +192,14 @@ unsigned int getFileSize(const string& path)
return filestatus.st_size / 1024;
}
string getFileSizeAsString(const string& path)
std::string getFileSizeAsString(const std::string& path)
{
ostringstream convert;
std::ostringstream convert;
convert << getFileSize(path);
return convert.str();
}
string getFileContent(const string& path)
std::string getFileContent(const std::string& path)
{
std::ifstream f(path, std::ios::in|std::ios::ate);
std::string content;
@@ -199,14 +213,14 @@ string getFileContent(const string& path)
return content;
}
bool fileExists(const string& path)
bool fileExists(const std::string& path)
{
#ifdef _WIN32
return PathFileExists(path.c_str());
#else
bool flag = false;
fstream fin;
fin.open(path.c_str(), ios::in);
std::fstream fin;
fin.open(path.c_str(), std::ios::in);
if (fin.is_open()) {
flag = true;
}
@@ -215,7 +229,7 @@ bool fileExists(const string& path)
#endif
}
bool makeDirectory(const string& path)
bool makeDirectory(const std::string& path)
{
#ifdef _WIN32
int status = _mkdir(path.c_str());
@@ -225,32 +239,27 @@ bool makeDirectory(const string& path)
return status == 0;
}
string makeTmpDirectory()
std::string makeTmpDirectory()
{
#ifdef _WIN32
char cbase[MAX_PATH+1];
int base_len = GetTempPath(MAX_PATH+1, cbase);
UUID uuid;
UuidCreate(&uuid);
char* dir_name;
UuidToString(&uuid, reinterpret_cast<unsigned char**>(&dir_name));
string dir(cbase, base_len);
dir += dir_name;
_mkdir(dir.c_str());
RpcStringFree(reinterpret_cast<unsigned char**>(&dir_name));
char cbase[MAX_PATH];
char ctmp[MAX_PATH];
GetTempPath(MAX_PATH-14, cbase);
// This create a file for us, ensure it is unique.
// So we need to delete it and create the directory using the same name.
GetTempFileName(cbase, "kiwix", 0, ctmp);
DeleteFile(ctmp);
_mkdir(ctmp);
return std::string(ctmp);
#else
string base = "/tmp";
auto _template = base + "/kiwix-lib_XXXXXX";
char* _template_array = new char[_template.size()+1];
memcpy(_template_array, _template.c_str(), _template.size());
string dir = mkdtemp(_template_array);
delete[] _template_array;
#endif
char _template_array[] = {"/tmp/kiwix-lib_XXXXXX"};
std::string dir = mkdtemp(_template_array);
return dir;
#endif
}
/* Try to create a link and if does not work then make a copy */
bool copyFile(const string& sourcePath, const string& destPath)
bool copyFile(const std::string& sourcePath, const std::string& destPath)
{
try {
#ifndef _WIN32
@@ -262,15 +271,15 @@ bool copyFile(const string& sourcePath, const string& destPath)
#ifndef _WIN32
}
#endif
} catch (exception& e) {
cerr << e.what() << endl;
} catch (std::exception& e) {
std::cerr << e.what() << std::endl;
return false;
}
return true;
}
string getExecutablePath()
std::string getExecutablePath()
{
char binRootPath[PATH_MAX];
@@ -291,7 +300,7 @@ string getExecutablePath()
return "";
}
bool writeTextFile(const string& path, const string& content)
bool writeTextFile(const std::string& path, const std::string& content)
{
std::ofstream file;
file.open(path.c_str());
@@ -300,15 +309,15 @@ bool writeTextFile(const string& path, const string& content)
return true;
}
string getCurrentDirectory()
std::string getCurrentDirectory()
{
char* a_cwd = getcwd(NULL, 0);
string s_cwd(a_cwd);
std::string s_cwd(a_cwd);
free(a_cwd);
return s_cwd;
}
string getDataDirectory()
std::string getDataDirectory()
{
#ifdef _WIN32
char* cDataDir = ::getenv("APPDATA");
@@ -333,3 +342,49 @@ string getDataDirectory()
#endif
return appendToDirectory(dataDir, "kiwix");
}
static std::map<std::string, std::string> extMimeTypes = {
{ "html", "text/html"},
{ "htm", "text/html"},
{ "png", "image/png"},
{ "tiff", "image/tiff"},
{ "tif", "image/tiff"},
{ "jpeg", "image/jpeg"},
{ "jpg", "image/jpeg"},
{ "gif", "image/gif"},
{ "svg", "image/svg+xml"},
{ "txt", "text/plain"},
{ "xml", "text/xml"},
{ "pdf", "application/pdf"},
{ "ogg", "application/ogg"},
{ "js", "application/javascript"},
{ "css", "text/css"},
{ "otf", "application/vnd.ms-opentype"},
{ "ttf", "application/font-ttf"},
{ "woff", "application/font-woff"},
{ "vtt", "text/vtt"}
};
/* Try to get the mimeType from the file extension */
std::string getMimeTypeForFile(const std::string& filename)
{
std::string mimeType = "text/plain";
auto pos = filename.find_last_of(".");
if (pos != std::string::npos) {
std::string extension = filename.substr(pos + 1);
auto it = extMimeTypes.find(extension);
if (it != extMimeTypes.end()) {
mimeType = it->second;
} else {
it = extMimeTypes.find(kiwix::lcAll(extension));
if (it != extMimeTypes.end()) {
mimeType = it->second;
}
}
}
return mimeType;
}

View File

@@ -18,40 +18,46 @@
*/
#include <tools/regexTools.h>
#include <tools/lock.h>
std::map<std::string, icu::RegexMatcher*> regexCache;
#include <unicode/regex.h>
#include <unicode/ucnv.h>
icu::RegexMatcher* buildRegex(const std::string& regex)
#include <memory>
#include <map>
#include <pthread.h>
std::map<std::string, std::shared_ptr<icu::RegexPattern>> regexCache;
static pthread_mutex_t regexLock = PTHREAD_MUTEX_INITIALIZER;
std::unique_ptr<icu::RegexMatcher> buildMatcher(const std::string& regex, icu::UnicodeString& content)
{
icu::RegexMatcher* matcher;
auto itr = regexCache.find(regex);
std::shared_ptr<icu::RegexPattern> pattern;
/* Regex is in cache */
if (itr != regexCache.end()) {
matcher = itr->second;
try {
pattern = regexCache.at(regex);
} catch (std::out_of_range&) {
// Redo the search with a lock to avoid race condition.
kiwix::Lock l(&regexLock);
try {
pattern = regexCache.at(regex);
} catch (std::out_of_range&) {
UErrorCode status = U_ZERO_ERROR;
UParseError pe;
icu::UnicodeString uregex(regex.c_str());
pattern.reset(icu::RegexPattern::compile(uregex, UREGEX_CASE_INSENSITIVE, pe, status));
regexCache[regex] = pattern;
}
}
/* Regex needs to be parsed (and cached) */
else {
UErrorCode status = U_ZERO_ERROR;
icu::UnicodeString uregex(regex.c_str());
matcher = new icu::RegexMatcher(uregex, UREGEX_CASE_INSENSITIVE, status);
regexCache[regex] = matcher;
}
return matcher;
UErrorCode status = U_ZERO_ERROR;
return std::unique_ptr<icu::RegexMatcher>(pattern->matcher(content, status));
}
/* todo */
void freeRegexCache()
{
}
bool matchRegex(const std::string& content, const std::string& regex)
{
ucnv_setDefaultName("UTF-8");
icu::UnicodeString ucontent(content.c_str());
auto matcher = buildRegex(regex);
matcher->reset(ucontent);
auto matcher = buildMatcher(regex, ucontent);
return matcher->find();
}
@@ -60,10 +66,9 @@ std::string replaceRegex(const std::string& content,
const std::string& regex)
{
ucnv_setDefaultName("UTF-8");
icu::UnicodeString ucontent(content.c_str());
icu::UnicodeString ureplacement(replacement.c_str());
auto matcher = buildRegex(regex);
matcher->reset(ucontent);
icu::UnicodeString ucontent(content.c_str());
auto matcher = buildMatcher(regex, ucontent);
UErrorCode status = U_ZERO_ERROR;
auto uresult = matcher->replaceAll(ureplacement, status);
std::string tmp;
@@ -72,15 +77,13 @@ std::string replaceRegex(const std::string& content,
}
std::string appendToFirstOccurence(const std::string& content,
const std::string regex,
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 = buildRegex(regex);
matcher->reset(ucontent);
auto matcher = buildMatcher(regex, ucontent);
if (matcher->find()) {
UErrorCode status = U_ZERO_ERROR;
ucontent.insert(matcher->end(status), ureplacement);

View File

@@ -19,6 +19,7 @@
#include <tools/stringTools.h>
#include <tools/pathTools.h>
#include <unicode/normlzr.h>
#include <unicode/rep.h>
#include <unicode/translit.h>
@@ -26,6 +27,10 @@
#include <unicode/uniset.h>
#include <unicode/ustring.h>
#include <iostream>
#include <iomanip>
/* tell ICU where to find its dat file (tables) */
void kiwix::loadICUExternalTables()
{
@@ -36,7 +41,7 @@ void kiwix::loadICUExternalTables()
= computeAbsolutePath(executableDirectory, "icudt58l.dat");
try {
u_setDataDirectory(datPath.c_str());
} catch (exception& e) {
} catch (std::exception& e) {
std::cerr << e.what() << std::endl;
}
#endif
@@ -372,3 +377,11 @@ std::string kiwix::normalize(const std::string& word)
{
return kiwix::lcAll(word);
}
bool kiwix::startsWith(const std::string& base, const std::string& start)
{
return start.length() <= base.length()
&& std::equal(start.begin(), start.end(), base.begin());
}

View File

@@ -4,6 +4,7 @@
#define KIWIX_XMLRPC_H_
#include <tools/otherTools.h>
#include <pugixml.hpp>
namespace kiwix {

View File

@@ -7,5 +7,5 @@ lib_resources = custom_target('resources',
'--hfile', '@OUTPUT1@',
'--source_dir', '@OUTDIR@',
'@INPUT@'],
depend_files: files('search_result.tmpl')
build_always_stale: true
)

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<id>{{id}}</id>
<link rel="self"
href="{{self_url}}"
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
<link rel="start"
href="{{start_url}}"
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
<title>{{title}}</title>
<updated>{{updated}}</updated>
<author>
<name>{{author_name}}</name>
<uri>{{author_uri}}</uri>
</author>
{{#entries}}
<entry>
<title>{{title}}</title>
<link rel="subsection"
href="{{href}}"
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
<updated>{{updated}}</updated>
<id>{{id}}</id>
</entry>
{{/entries}}
</feed>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
<ShortName>Zim catalog search</ShortName>
<Description>Search zim files in the catalog.</Description>
<Url type="application/atom+xml;profile=opds-catalog"
xmlns:atom="http://www.w3.org/2005/Atom"
indexOffset="0"
template="/{{root}}/catalog/search?q={searchTerms}&lang={language}&count={count}&start={startIndex}"/>
</OpenSearchDescription>

View File

@@ -1 +1,31 @@
search_result.tmpl
skin/jquery-ui/jquery-ui.structure.min.css
skin/jquery-ui/jquery-ui.min.js
skin/jquery-ui/external/jquery/jquery.js
skin/jquery-ui/images/ui-bg_flat_0_aaaaaa_40x100.png
skin/jquery-ui/images/ui-bg_flat_75_ffffff_40x100.png
skin/jquery-ui/images/ui-icons_222222_256x240.png
skin/jquery-ui/images/ui-bg_glass_55_fbf9ee_1x400.png
skin/jquery-ui/images/ui-bg_highlight-soft_75_cccccc_1x100.png
skin/jquery-ui/images/ui-bg_glass_65_ffffff_1x400.png
skin/jquery-ui/images/ui-icons_2e83ff_256x240.png
skin/jquery-ui/images/ui-icons_cd0a0a_256x240.png
skin/jquery-ui/images/ui-icons_888888_256x240.png
skin/jquery-ui/images/ui-bg_glass_75_e6e6e6_1x400.png
skin/jquery-ui/images/animated-overlay.gif
skin/jquery-ui/images/ui-bg_glass_75_dadada_1x400.png
skin/jquery-ui/images/ui-icons_454545_256x240.png
skin/jquery-ui/images/ui-bg_glass_95_fef1ec_1x400.png
skin/jquery-ui/jquery-ui.theme.min.css
skin/jquery-ui/jquery-ui.min.css
skin/caret.png
skin/taskbar.js
skin/taskbar.css
templates/search_result.html
templates/no_search_result.html
templates/404.html
templates/500.html
templates/index.html
templates/suggestion.json
templates/head_part.html
templates/taskbar_part.html
opensearchdescription.xml

BIN
static/skin/caret.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

File diff suppressed because it is too large Load Diff

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

File diff suppressed because one or more lines are too long

13
static/skin/jquery-ui/jquery-ui.min.js vendored Normal file
View File

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

186
static/skin/taskbar.css Normal file
View File

@@ -0,0 +1,186 @@
#kiwixtoolbar {
position: fixed;
padding: .5em;
left: 0;
right: 0;
top: 0;
z-index: 100;
background-position-y: 0;
transition: 0.3s;
width: 100%;
box-sizing: border-box;
}
#kiwixtoolbar>a {
float: left;
}
#kiwixfooter {
text-align: center;
margin-top: 1em;
}
.height_separator {
height: 3em;
}
.kiwixsearch {
position: relative;
height: 26px;
width: 100%;
left: 0;
margin-bottom: 0;
}
.kiwix_searchform {
width: 20em;
}
#kiwix_serve_taskbar_home_button button {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 160px;
}
.kiwix .kiwix_centered {
max-width: 720px;
margin: 0 auto;
}
#kiwix_button_show_toggle {
display: none;
}
#kiwix_button_show_toggle:checked~label~.kiwix_button_cont,
#kiwix_button_show_toggle:checked~label~.kiwix_button_cont>a {
display: block;
}
#kiwix_button_show_toggle:not(:checked)~label~.kiwix_button_cont {
display: none;
}
label[for="kiwix_button_show_toggle"] {
display: inline-block;
height: 26px;
}
label[for="kiwix_button_show_toggle"] img {
transition: 0.1s;
height: 26px;
}
#kiwix_button_show_toggle:checked~label img {
transform: rotate(-180deg);
}
label[for="kiwix_button_show_toggle"],
.kiwix_button_cont {
display: block;
}
.kiwix .kiwix_searchform {
float: right;
}
.kiwix #kiwixtoolbar button,
.kiwix #kiwixtoolbar input[type="submit"] {
box-sizing: border-box !important;
height: 26px !important;
line-height: 20px !important;
margin-right: 5px !important;
padding: 2px 6px !important;
border: 1px solid #999 !important;
border-radius: 3px !important;
background-color: #ededed !important;
font-weight: normal !important;
cursor: pointer !important;
font-size: 16px !important;
}
.kiwix #kiwixtoolbar #kiwixsearchform input[type='text'] {
position: absolute;
left: 0;
box-sizing: border-box !important;
width: 100%;
height: 26px !important;
line-height: 20px !important;
border: 1px solid #999 !important;
border-radius: 3px !important;
background-color: #fff !important;
padding: 2px 2px 2px 27px !important;
font-size: 16px !important;
}
label[for=kiwixsearchbox] {
z-index: 1;
position: absolute;
height: 100%;
left: 5px;
font-size: 90%;
line-height: 26px;
vertical-align: middle;
}
body {
padding-top: 40px !important;
}
/* Try to fix buggy stuff in jquery-ui autocomplete */
#ui-id-1,
.ui-autocomplete {
background: white !important;
border: solid 1px grey !important;
}
li.ui-state-focus {
font-weight: bold;
}
@media(min-width:420px) {
.kiwix_button_cont {
display: inline-block !important;
}
.kiwix_button_cont>a {
display: inline-block !important;
}
label[for="kiwix_button_show_toggle"] {
display: none;
}
}
@media (max-width: 645px) {
#kiwix_button_show_toggle~label~.kiwix_button_cont.searching {
display: none !important;
}
label[for="kiwix_button_show_toggle"].searching {
display: none !important;
}
.kiwix_searchform.full_width {
width: 100%;
}
.kiwixsearch {
float: none;
}
.kiwix_searchform {
width: 36%;
}
.height_separator {
height: 6em;
}
}
@media(max-width:415px) {
.kiwix_searchform {
width: 80%;
}
}

49
static/skin/taskbar.js Normal file
View File

@@ -0,0 +1,49 @@
(function ($) {
if ($(window).width() < 520) {
var didScroll;
var lastScrollTop = 0;
var delta = 5;
// on scroll, let the interval function know the user has scrolled
$(window).scroll(function (event) {
didScroll = true;
});
// run hasScrolled() and reset didScroll status
setInterval(function () {
if (didScroll) {
hasScrolled();
didScroll = false;
}
}, 250);
function hasScrolled() {
var st = $(this).scrollTop();
// Make sure they scroll more than delta
if (Math.abs(lastScrollTop - st) <= delta)
return;
// If they scrolled down and are past the navbar, add class .nav-up.
// This is necessary so you never see what is "behind" the navbar.
if (st > lastScrollTop) {
// Scroll Down
$('#kiwixtoolbar').css({ top: '-100%' });
} else {
// Scroll Up
$('#kiwixtoolbar').css({ top: '0' });
}
lastScrollTop = st;
}
}
$('#kiwixsearchbox').on({
focus: function () {
$('.kiwix_searchform').addClass('full_width');
$('label[for="kiwix_button_show_toggle"], .kiwix_button_cont').addClass('searching');
},
blur: function () {
$('.kiwix_searchform').removeClass('full_width');
$('label[for="kiwix_button_show_toggle"], .kiwix_button_cont').removeClass('searching');
}
});
})(jQuery);

Some files were not shown because too many files have changed in this diff Show More