Compare commits
92 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
651cb9165c | ||
|
|
49046248fd | ||
|
|
e42e061d45 | ||
|
|
d90774450d | ||
|
|
9e36c876f5 | ||
|
|
1a4c434e3c | ||
|
|
3294508d87 | ||
|
|
351e573bce | ||
|
|
a32363e6a2 | ||
|
|
56f8b7a876 | ||
|
|
87dc145dc7 | ||
|
|
a13244dc0e | ||
|
|
78dbd66522 | ||
|
|
fdc291b7c2 | ||
|
|
d372cea146 | ||
|
|
e1fcd12e48 | ||
|
|
828bd032c4 | ||
|
|
26d32a36ad | ||
|
|
c031547461 | ||
|
|
d0833bdcd4 | ||
|
|
1bb5e278ed | ||
|
|
0a331f8ba9 | ||
|
|
b1a4bbd345 | ||
|
|
14dbe843b9 | ||
|
|
e111316636 | ||
|
|
bbb346b685 | ||
|
|
56a08f49b2 | ||
|
|
fc2ad81185 | ||
|
|
af78aa5fd0 | ||
|
|
2ea0e5bab0 | ||
|
|
b92a4b2e04 | ||
|
|
ef758aa0a6 | ||
|
|
b8e5de6a47 | ||
|
|
aed808ae5e | ||
|
|
e25b27b354 | ||
|
|
12a93c3e29 | ||
|
|
b3abb3a35b | ||
|
|
8c3b51b81c | ||
|
|
5f81fab1e2 | ||
|
|
cea201b394 | ||
|
|
d0f6fadda2 | ||
|
|
8672aede97 | ||
|
|
8edac07fcc | ||
|
|
6ab52e2b9e | ||
|
|
858c2fecbe | ||
|
|
d90a27af11 | ||
|
|
a8668db2fe | ||
|
|
3e3e1683f5 | ||
|
|
1e0c9a120a | ||
|
|
ca27ddf41a | ||
|
|
99dcdd849a | ||
|
|
a65e192f0f | ||
|
|
513cc9c90f | ||
|
|
231ae095f6 | ||
|
|
9a0c6da018 | ||
|
|
52299ef767 | ||
|
|
44bec86f31 | ||
|
|
c4963268ba | ||
|
|
fee2da57e6 | ||
|
|
0154131d74 | ||
|
|
3fa503efb1 | ||
|
|
73a29ccb24 | ||
|
|
1e94665c07 | ||
|
|
7060afae66 | ||
|
|
de819dff25 | ||
|
|
4d3df4e889 | ||
|
|
cd050ddcc8 | ||
|
|
635d4438e5 | ||
|
|
ce09375c6c | ||
|
|
fae0918f49 | ||
|
|
d90f8b0f05 | ||
|
|
abb5db0193 | ||
|
|
e452f5cf36 | ||
|
|
c890e1c87e | ||
|
|
e5ef3780db | ||
|
|
2aeed65205 | ||
|
|
c1faf55ae8 | ||
|
|
64dfea2547 | ||
|
|
cca5980b27 | ||
|
|
68a768f58a | ||
|
|
ce8fff0b42 | ||
|
|
e56335109c | ||
|
|
656bf183b7 | ||
|
|
cbe8e20118 | ||
|
|
e013d38cc6 | ||
|
|
6234457920 | ||
|
|
72223d69fe | ||
|
|
ddeb862395 | ||
|
|
61c28f0e3d | ||
|
|
c8e719101e | ||
|
|
1b8e7849bd | ||
|
|
efce01aec4 |
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
.idea/
|
||||
*.swp
|
||||
|
||||
43
ChangeLog
@@ -1,3 +1,46 @@
|
||||
kiwix-lib 7.0.0
|
||||
===============
|
||||
|
||||
* [API break] Add a argument to kiwix-serve to specify the library to use.
|
||||
|
||||
kiwix-lib 6.0.4
|
||||
===============
|
||||
|
||||
* Fix HTML rendering of the search result if there is no result.
|
||||
* Do not crash at html rendering if request ask for 0 results (start == end)
|
||||
* Correctly find the executable path if we are using AppImage
|
||||
|
||||
kiwix-lib 6.0.3
|
||||
===============
|
||||
|
||||
* force one column suggestion in kiwix-serve suggestions
|
||||
* fix fulltext search link in suggestions
|
||||
* UI fixes in kiwix-serve rendering
|
||||
|
||||
kiwix-lib 6.0.2
|
||||
===============
|
||||
|
||||
* Correctly set the groupId in the pom file.
|
||||
|
||||
kiwix-lib 6.0.1
|
||||
===============
|
||||
|
||||
* Generate the pom file for android/maven
|
||||
|
||||
kiwix-lib 6.0.0
|
||||
===============
|
||||
|
||||
* Move the server code in kiwix-lib (from kiwix-serve).
|
||||
* Add unit test on regex functions.
|
||||
* Fix computerAbsolutePath (thread safe, memory leak).
|
||||
* Correctly set the book's path as valid if we construct the book from a
|
||||
reader.
|
||||
* [JNI] Add a method to know if a article is a redirection.
|
||||
* Do not embed the gtest dependency.
|
||||
* [JNI] Add a constructor to JNIKiwixString.
|
||||
* Change order of search of the favicon urls.
|
||||
* Clean a lot of unecessary includes in headers. (potential "API break")
|
||||
|
||||
kiwix-lib 5.2.0
|
||||
===============
|
||||
|
||||
|
||||
29
README.md
@@ -1,8 +1,15 @@
|
||||
Kiwix library
|
||||
=============
|
||||
|
||||
The Kiwix library provides the Kiwix software core. It contains the
|
||||
code shared by all Kiwix ports (Windows, Linux, OSX, Android, ...).
|
||||
The Kiwix library provides the [Kiwix](https://kiwix.org) software
|
||||
suite core. It contains the code shared by all Kiwix ports (Windows,
|
||||
Linux, OSX, Android, ...).
|
||||
|
||||
[](https://bintray.com/kiwix/kiwix/kiwixlib/_latestVersion)
|
||||
[](https://aur.archlinux.org/packages/kiwix-lib/)
|
||||
[](https://travis-ci.org/kiwix/kiwix-lib)
|
||||
[](https://www.codefactor.io/repository/github/kiwix/kiwix-lib)
|
||||
[](https://www.gnu.org/licenses/gpl-3.0)
|
||||
|
||||
Disclaimer
|
||||
----------
|
||||
@@ -52,9 +59,9 @@ The Kiwix library builds using [Meson](https://mesonbuild.com/) version
|
||||
compilation tools.
|
||||
|
||||
Install first the few common compilation tools:
|
||||
* Meson
|
||||
* Ninja
|
||||
* Pkg-config
|
||||
* [Meson](https://mesonbuild.com/)
|
||||
* [Ninja](https://ninja-build.org/)
|
||||
* [pkg-config](https://www.freedesktop.org/wiki/Software/pkg-config/)
|
||||
|
||||
These tools should be packaged if you use a cutting edge operating
|
||||
system. If not, have a look to the [Troubleshooting](#Troubleshooting)
|
||||
@@ -65,7 +72,6 @@ Compilation
|
||||
|
||||
Once all dependencies are installed, you can compile the Kiwix library
|
||||
with:
|
||||
|
||||
```bash
|
||||
meson . build
|
||||
ninja -C build
|
||||
@@ -83,32 +89,29 @@ 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
|
||||
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
|
||||
Like for the installation, you might need to run the command as `root`
|
||||
(or using `sudo`).
|
||||
|
||||
Troubleshooting
|
||||
---------------
|
||||
|
||||
If you need to install Meson "manually":
|
||||
|
||||
```bash
|
||||
virtualenv -p python3 ./ # Create virtualenv
|
||||
source bin/activate # Activate the virtualenv
|
||||
@@ -117,7 +120,6 @@ hash -r # Refresh bash paths
|
||||
```
|
||||
|
||||
If you need to install Ninja "manually":
|
||||
|
||||
```bash
|
||||
git clone git://github.com/ninja-build/ninja.git
|
||||
cd ninja
|
||||
@@ -137,4 +139,5 @@ repository.
|
||||
License
|
||||
-------
|
||||
|
||||
GPLv3 or later, see COPYING for more details.
|
||||
[GPLv3](https://www.gnu.org/licenses/gpl-3.0) or later, see
|
||||
[COPYING](COPYING) for more details.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'maven'
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
@@ -19,3 +20,45 @@ android {
|
||||
dependencies {
|
||||
implementation 'com.getkeepsafe.relinker:relinker:1.3.1'
|
||||
}
|
||||
|
||||
task writePom {
|
||||
pom {
|
||||
project {
|
||||
groupId 'org.kiwix.kiwixlib'
|
||||
artifactId 'kiwixlib'
|
||||
version '7.0.0' + (System.env.KIWIXLIB_BUILDVERSION == null ? '' : '-'+System.env.KIWIXLIB_BUILDVERSION)
|
||||
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")
|
||||
}
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
#include <exception>
|
||||
#include <string>
|
||||
|
||||
#include "common.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace kiwix
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define KIWIXLIB_KIWIX_SERVE_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
class Subprocess;
|
||||
namespace kiwix {
|
||||
@@ -9,7 +10,7 @@ namespace kiwix {
|
||||
class KiwixServe
|
||||
{
|
||||
public:
|
||||
KiwixServe(int port = 8181);
|
||||
KiwixServe(const std::string& libraryPath, int port = 8181);
|
||||
~KiwixServe();
|
||||
|
||||
void run();
|
||||
@@ -20,6 +21,7 @@ class KiwixServe
|
||||
private:
|
||||
std::unique_ptr<Subprocess> mp_kiwixServe;
|
||||
int m_port;
|
||||
std::string m_libraryPath;
|
||||
};
|
||||
|
||||
}; //end namespace kiwix
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
#include "book.h"
|
||||
#include "bookmark.h"
|
||||
@@ -110,6 +111,7 @@ class Filter {
|
||||
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:
|
||||
@@ -145,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.
|
||||
|
||||
@@ -10,7 +10,10 @@ headers = [
|
||||
'reader.h',
|
||||
'entry.h',
|
||||
'searcher.h',
|
||||
'kiwixserve.h'
|
||||
'search_renderer.h',
|
||||
'server.h',
|
||||
'kiwixserve.h',
|
||||
'name_mapper.h'
|
||||
]
|
||||
|
||||
install_headers(headers, subdir:'kiwix')
|
||||
@@ -22,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
@@ -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
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
project('kiwix-lib', 'cpp',
|
||||
version : '5.2.0',
|
||||
version : '7.0.0', # Also change this in android-kiwix-lib-publisher/kiwixLibAndroid/build.gradle
|
||||
license : 'GPL',
|
||||
default_options : ['c_std=c11', 'cpp_std=c++11', 'werror=true'])
|
||||
|
||||
@@ -17,6 +17,7 @@ libicu_dep = dependency('icu-i18n', 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')
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
pthread_mutex_t globalLock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER;
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_kiwix_kiwixlib_JNIICU_setDataDirectory(
|
||||
JNIEnv* env, jobject obj, jstring dirStr)
|
||||
JNIEnv* env, jclass kclass, jstring dirStr)
|
||||
{
|
||||
std::string cPath = jni2c(dirStr, env);
|
||||
|
||||
|
||||
72
src/android/kiwixlibrary.cpp
Normal 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;
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
@@ -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(
|
||||
|
||||
99
src/android/kiwixserver.cpp
Normal 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();
|
||||
}
|
||||
@@ -2,7 +2,9 @@
|
||||
kiwix_jni = custom_target('jni',
|
||||
input: ['org/kiwix/kiwixlib/JNIICU.java',
|
||||
'org/kiwix/kiwixlib/JNIKiwixReader.java',
|
||||
'org/kiwix/kiwixlib/JNIKiwixLibrary.java',
|
||||
'org/kiwix/kiwixlib/JNIKiwixSearcher.java',
|
||||
'org/kiwix/kiwixlib/JNIKiwixServer.java',
|
||||
'org/kiwix/kiwixlib/JNIKiwixInt.java',
|
||||
'org/kiwix/kiwixlib/JNIKiwixString.java',
|
||||
'org/kiwix/kiwixlib/JNIKiwixBool.java',
|
||||
@@ -10,6 +12,8 @@ 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@']
|
||||
@@ -18,7 +22,9 @@ kiwix_jni = custom_target('jni',
|
||||
kiwix_sources += [
|
||||
'android/kiwixicu.cpp',
|
||||
'android/kiwixreader.cpp',
|
||||
'android/kiwixlibrary.cpp',
|
||||
'android/kiwixsearcher.cpp',
|
||||
'android/kiwixserver.cpp',
|
||||
kiwix_jni]
|
||||
|
||||
install_subdir('org', install_dir: 'kiwix-lib/java')
|
||||
|
||||
38
src/android/org/kiwix/kiwixlib/JNIKiwixLibrary.java
Normal 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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
48
src/android/org/kiwix/kiwixlib/JNIKiwixServer.java
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -156,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()) {
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "downloader.h"
|
||||
#include "tools/pathTools.h"
|
||||
#include "tools/stringTools.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <thread>
|
||||
@@ -174,7 +175,7 @@ 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));
|
||||
|
||||
@@ -10,10 +10,13 @@
|
||||
#endif
|
||||
|
||||
#include "tools/pathTools.h"
|
||||
#include "tools/stringTools.h"
|
||||
|
||||
namespace kiwix {
|
||||
|
||||
KiwixServe::KiwixServe(int port) : m_port(port)
|
||||
KiwixServe::KiwixServe(const std::string& libraryPath, int port)
|
||||
: m_port(port),
|
||||
m_libraryPath(libraryPath)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -42,13 +45,12 @@ void KiwixServe::run()
|
||||
// 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());
|
||||
callCmd.push_back(m_libraryPath.c_str());
|
||||
mp_kiwixServe = Subprocess::run(callCmd);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,11 +19,13 @@
|
||||
|
||||
#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>
|
||||
@@ -31,6 +33,7 @@
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
/* Constructor */
|
||||
Library::Library()
|
||||
{
|
||||
@@ -82,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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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',
|
||||
@@ -18,6 +20,9 @@ kiwix_sources = [
|
||||
'tools/networkTools.cpp',
|
||||
'tools/otherTools.cpp',
|
||||
'kiwixserve.cpp',
|
||||
'name_mapper.cpp',
|
||||
'server/request_context.cpp',
|
||||
'server/response.cpp'
|
||||
]
|
||||
kiwix_sources += lib_resources
|
||||
|
||||
|
||||
62
src/name_mapper.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,7 +20,8 @@
|
||||
#include "opds_dumper.h"
|
||||
#include "book.h"
|
||||
|
||||
#include <tools/otherTools.h>
|
||||
#include "tools/otherTools.h"
|
||||
#include <iomanip>
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
@@ -258,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 {
|
||||
|
||||
152
src/search_renderer.cpp
Normal file
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* 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();
|
||||
auto currentPage = 0U;
|
||||
auto pageStart = 0U;
|
||||
auto pageEnd = 0U;
|
||||
auto lastPageStart = 0U;
|
||||
if (resultCountPerPage) {
|
||||
currentPage = resultStart/resultCountPerPage;
|
||||
pageStart = currentPage > 4 ? currentPage-4 : 0;
|
||||
pageEnd = currentPage + 5;
|
||||
if (pageEnd > estimatedResultCount / resultCountPerPage) {
|
||||
pageEnd = estimatedResultCount / resultCountPerPage;
|
||||
}
|
||||
if (estimatedResultCount > resultCountPerPage) {
|
||||
lastPageStart = static_cast<int>(round(estimatedResultCount/resultCountPerPage)) * resultCountPerPage;
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned int i = pageStart; i < pageEnd; i++) {
|
||||
kainjow::mustache::data page;
|
||||
page.set("label", to_string(i + 1));
|
||||
page.set("start", to_string(i * resultCountPerPage));
|
||||
page.set("end", to_string((i + 1) * resultCountPerPage));
|
||||
|
||||
if (i == currentPage) {
|
||||
page.set("selected", true);
|
||||
}
|
||||
pages.push_back(page);
|
||||
}
|
||||
|
||||
std::string template_str = RESOURCE::templates::search_result_html;
|
||||
kainjow::mustache::mustache tmpl(template_str);
|
||||
|
||||
kainjow::mustache::data allData;
|
||||
allData.set("results", results);
|
||||
allData.set("pages", pages);
|
||||
allData.set("hasResults", estimatedResultCount != 0);
|
||||
allData.set("hasPages", pageStart != pageEnd);
|
||||
allData.set("count", kiwix::beautifyInteger(estimatedResultCount));
|
||||
allData.set("searchPattern", kiwix::encodeDiples(this->searchPattern));
|
||||
allData.set("searchPatternEncoded", urlEncode(this->searchPattern));
|
||||
allData.set("resultStart", to_string(resultStart + 1));
|
||||
allData.set("resultEnd", to_string(min(resultEnd, estimatedResultCount)));
|
||||
allData.set("resultRange", to_string(resultCountPerPage));
|
||||
allData.set("resultLastPageStart", to_string(lastPageStart));
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
147
src/searcher.cpp
@@ -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,26 +108,12 @@ 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;
|
||||
}
|
||||
|
||||
this->searchPattern = search;
|
||||
this->resultStart = resultStart;
|
||||
this->resultEnd = resultEnd;
|
||||
/* 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;
|
||||
this->resultEnd = resultEnd;
|
||||
string unaccentedSearch = removeAccents(search);
|
||||
std::vector<const zim::File*> zims;
|
||||
for (auto current = this->readers.begin(); current != this->readers.end();
|
||||
@@ -159,25 +146,6 @@ 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;
|
||||
@@ -185,6 +153,11 @@ void Searcher::geo_search(float latitude, float longitude, float distance,
|
||||
this->resultStart = resultStart;
|
||||
this->resultEnd = resultEnd;
|
||||
|
||||
/* Try to find results */
|
||||
if (resultStart == resultEnd) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<const zim::File*> zims;
|
||||
for (auto current = this->readers.begin(); current != this->readers.end();
|
||||
current++) {
|
||||
@@ -261,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)
|
||||
{
|
||||
@@ -314,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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
211
src/server/request_context.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
109
src/server/request_context.h
Normal 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
@@ -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
@@ -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
|
||||
@@ -17,8 +17,17 @@
|
||||
* 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 {
|
||||
{ "aa", "aar" },
|
||||
@@ -185,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, " ");
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <tools/pathTools.h>
|
||||
#include "tools/pathTools.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <limits.h>
|
||||
@@ -29,6 +29,17 @@
|
||||
#define getcwd _getcwd // stupid MSFT "deprecation" warning
|
||||
#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("\\");
|
||||
#else
|
||||
@@ -42,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;
|
||||
@@ -51,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
|
||||
@@ -64,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 */
|
||||
@@ -83,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;
|
||||
@@ -98,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
|
||||
@@ -110,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
|
||||
@@ -149,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;
|
||||
@@ -173,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;
|
||||
@@ -194,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;
|
||||
}
|
||||
@@ -210,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());
|
||||
@@ -220,7 +239,7 @@ bool makeDirectory(const string& path)
|
||||
return status == 0;
|
||||
}
|
||||
|
||||
string makeTmpDirectory()
|
||||
std::string makeTmpDirectory()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
char cbase[MAX_PATH];
|
||||
@@ -231,16 +250,16 @@ string makeTmpDirectory()
|
||||
GetTempFileName(cbase, "kiwix", 0, ctmp);
|
||||
DeleteFile(ctmp);
|
||||
_mkdir(ctmp);
|
||||
return string(ctmp);
|
||||
return std::string(ctmp);
|
||||
#else
|
||||
char _template_array[] = {"/tmp/kiwix-lib_XXXXXX"};
|
||||
string dir = mkdtemp(_template_array);
|
||||
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
|
||||
@@ -252,18 +271,33 @@ 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];
|
||||
|
||||
char* cAppImage = ::getenv("APPIMAGE");
|
||||
if (cAppImage) {
|
||||
char* cArgv0 = ::getenv("ARGV0");
|
||||
char* cOwd = ::getenv("OWD");
|
||||
if (!cArgv0 && !cOwd) {
|
||||
// Nothing to clean, goto in the same function...
|
||||
// This is silly but .. ok..
|
||||
// And we should pass here, if APPIMAGE is set ARGV0 and OWD should also
|
||||
// (except if there is a bug in appimage)
|
||||
goto normal;
|
||||
}
|
||||
return appendToDirectory(cOwd, cArgv0);
|
||||
}
|
||||
normal:
|
||||
|
||||
#ifdef _WIN32
|
||||
GetModuleFileName(NULL, binRootPath, PATH_MAX);
|
||||
return std::string(binRootPath);
|
||||
@@ -281,7 +315,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());
|
||||
@@ -290,15 +324,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");
|
||||
@@ -323,3 +357,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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(®exLock);
|
||||
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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#define KIWIX_XMLRPC_H_
|
||||
|
||||
#include <tools/otherTools.h>
|
||||
#include <pugixml.hpp>
|
||||
|
||||
namespace kiwix {
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
27
static/navigation_feed.tmpl
Normal 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>
|
||||
9
static/opensearchdescription.xml
Normal 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>
|
||||
@@ -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
|
After Width: | Height: | Size: 1.4 KiB |
9789
static/skin/jquery-ui/external/jquery/jquery.js
vendored
Normal file
BIN
static/skin/jquery-ui/images/animated-overlay.gif
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
static/skin/jquery-ui/images/ui-bg_flat_0_aaaaaa_40x100.png
Normal file
|
After Width: | Height: | Size: 212 B |
BIN
static/skin/jquery-ui/images/ui-bg_flat_75_ffffff_40x100.png
Normal file
|
After Width: | Height: | Size: 208 B |
BIN
static/skin/jquery-ui/images/ui-bg_glass_55_fbf9ee_1x400.png
Normal file
|
After Width: | Height: | Size: 335 B |
BIN
static/skin/jquery-ui/images/ui-bg_glass_65_ffffff_1x400.png
Normal file
|
After Width: | Height: | Size: 207 B |
BIN
static/skin/jquery-ui/images/ui-bg_glass_75_dadada_1x400.png
Normal file
|
After Width: | Height: | Size: 262 B |
BIN
static/skin/jquery-ui/images/ui-bg_glass_75_e6e6e6_1x400.png
Normal file
|
After Width: | Height: | Size: 262 B |
BIN
static/skin/jquery-ui/images/ui-bg_glass_95_fef1ec_1x400.png
Normal file
|
After Width: | Height: | Size: 332 B |
|
After Width: | Height: | Size: 280 B |
BIN
static/skin/jquery-ui/images/ui-icons_222222_256x240.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
static/skin/jquery-ui/images/ui-icons_2e83ff_256x240.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
static/skin/jquery-ui/images/ui-icons_454545_256x240.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
static/skin/jquery-ui/images/ui-icons_888888_256x240.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
static/skin/jquery-ui/images/ui-icons_cd0a0a_256x240.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
7
static/skin/jquery-ui/jquery-ui.min.css
vendored
Normal file
13
static/skin/jquery-ui/jquery-ui.min.js
vendored
Normal file
5
static/skin/jquery-ui/jquery-ui.structure.min.css
vendored
Normal file
5
static/skin/jquery-ui/jquery-ui.theme.min.css
vendored
Normal file
187
static/skin/taskbar.css
Normal file
@@ -0,0 +1,187 @@
|
||||
#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: 3em !important;
|
||||
}
|
||||
|
||||
/* Try to fix buggy stuff in jquery-ui autocomplete */
|
||||
#ui-id-1,
|
||||
.ui-autocomplete {
|
||||
background: white !important;
|
||||
border: solid 1px grey !important;
|
||||
column-count: 1 !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
@@ -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);
|
||||
13
static/templates/404.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta content="text/html;charset=UTF-8" http-equiv="content-type" />
|
||||
<title>Content not found</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Not Found</h1>
|
||||
<p>
|
||||
The requested URL "{{url}}" was not found on this server.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
16
static/templates/500.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta content="text/html;charset=UTF-8" http-equiv="content-type" />
|
||||
<title>Internal Server Error</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Internal Server Error</h1>
|
||||
<p>
|
||||
An internal server error occured. We are sorry about that :/
|
||||
</p>
|
||||
<p>
|
||||
{{ error }}
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
28
static/templates/head_part.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<link type="text/css" href="{{root}}/skin/jquery-ui/jquery-ui.min.css" rel="Stylesheet" />
|
||||
<link type="text/css" href="{{root}}/skin/jquery-ui/jquery-ui.theme.min.css" rel="Stylesheet" />
|
||||
<link type="text/css" href="{{root}}/skin/taskbar.css" rel="Stylesheet" />
|
||||
<script type="text/javascript" src="{{root}}/skin/jquery-ui/external/jquery/jquery.js"></script>
|
||||
<script type="text/javascript" src="{{root}}/skin/jquery-ui/jquery-ui.min.js"></script>
|
||||
<script>
|
||||
var jk = jQuery.noConflict();
|
||||
jk(function() {
|
||||
jk( "#kiwixsearchbox" ).autocomplete({
|
||||
|
||||
source: "{{root}}/suggest?content={{#urlencoded}}{{{content}}}{{/urlencoded}}",
|
||||
dataType: "json",
|
||||
cache: false,
|
||||
|
||||
select: function(event, ui) {
|
||||
jk( "#kiwixsearchbox" ).val(ui.item.value);
|
||||
jk( "#kiwixsearchform" ).submit();
|
||||
},
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
/* cybook hack */
|
||||
if (navigator.userAgent.indexOf("bookeen/cybook") != -1) {
|
||||
jk("html").addClass("cybook");
|
||||
}
|
||||
</script>
|
||||
<script type="text/javascript" src="{{root}}/skin/taskbar.js" async></script>
|
||||
66
static/templates/index.html
Normal file
@@ -0,0 +1,66 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Welcome to Kiwix Server</title>
|
||||
<script type="text/javascript" src="{{root}}/skin/jquery-ui/external/jquery/jquery.js"></script>
|
||||
<script type="text/javascript" src="{{root}}/skin/jquery-ui/jquery-ui.min.js"></script>
|
||||
<link type="text/css" href="{{root}}/skin/jquery-ui/jquery-ui.min.css" rel="Stylesheet" />
|
||||
<link type="text/css" href="{{root}}/skin/jquery-ui/jquery-ui.theme.min.css" rel="Stylesheet" />
|
||||
<style>
|
||||
body {
|
||||
background:
|
||||
radial-gradient(#EEEEEE 15%, transparent 16%) 0 0,
|
||||
radial-gradient(#EEEEEE 15%, transparent 16%) 8px 8px,
|
||||
radial-gradient(rgba(255,255,255,.1) 15%, transparent 20%) 0 1px,
|
||||
radial-gradient(rgba(255,255,255,.1) 15%, transparent 20%) 8px 9px;
|
||||
background-color:#E8E8E8;
|
||||
background-size:16px 16px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 1100px;
|
||||
}
|
||||
.book__list { text-align: center; }
|
||||
.book {
|
||||
display: inline-block; vertical-align: bottom; margin: 8px; padding: 12px 15px; width: 300px;
|
||||
border: 1px solid #ccc; border-radius: 8px;
|
||||
text-align: left; color: #000; font-family: sans-serif; font-size: 13px;
|
||||
background-color:#F1F1F1;
|
||||
box-shadow: 2px 2px 5px 0px #ccc;
|
||||
}
|
||||
.book:hover { background-color: #F9F9F9; box-shadow: none;}
|
||||
.book__background { background-repeat: no-repeat; background-size: auto; background-position: top right; }
|
||||
.book__title {
|
||||
padding: 0 55px 0 0;overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
||||
font-size: 18px; color: #0645ad; line-height: 1em;
|
||||
}
|
||||
.book__description {
|
||||
padding: 5px 55px 5px 0px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
||||
font-size: 15px; line-height: 1em;
|
||||
}
|
||||
.book__info { color: #777; font-weight: bold; font-size: 13px; line-height: 1em; }
|
||||
</style>
|
||||
<script type="text/javascript" src="{{root}}/skin/taskbar.js" async></script>
|
||||
</head>
|
||||
<body class="kiwix">
|
||||
|
||||
<div class="kiwix">
|
||||
<div class='book__list'>
|
||||
{{#books}}
|
||||
<a href="{{root}}/{{name}}"><div class='book'>
|
||||
<div class='book__background' style="background-image: url('{{root}}/meta?content={{#urlencoded}}{{{name}}}{{/urlencoded}}&name=favicon');">
|
||||
<div class='book__title' title='{{title}}'>{{title}}</div>
|
||||
<div class='book__description' title='{{description}}'>{{description}}</div>
|
||||
<div class='book__info'>{{articleCount}} articles, {{mediaCount}} medias</div>
|
||||
</div>
|
||||
</div></a>
|
||||
{{/books}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="kiwixfooter">
|
||||
Powered by <a href="https://kiwix.org">Kiwix</a>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
103
static/templates/no_search_result.html
Normal file
@@ -0,0 +1,103 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type" />
|
||||
<style type="text/css">
|
||||
body{
|
||||
color: #000000;
|
||||
font: small/normal Arial,Helvetica,Sans-Serif;
|
||||
margin-top: 0.5em;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
a{
|
||||
color: #04c;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: #639
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline
|
||||
}
|
||||
|
||||
.header {
|
||||
font-size: 120%;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin:0;
|
||||
padding:0
|
||||
}
|
||||
|
||||
.results {
|
||||
font-size: 110%;
|
||||
}
|
||||
|
||||
.results li {
|
||||
list-style-type:none;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
.results a {
|
||||
font-size: 110%;
|
||||
text-decoration: underline
|
||||
}
|
||||
|
||||
cite {
|
||||
font-style:normal;
|
||||
word-wrap:break-word;
|
||||
display: block;
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
.informations {
|
||||
color: #388222;
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 0;
|
||||
margin-top: 1em;
|
||||
width: 100%;
|
||||
float: left
|
||||
}
|
||||
|
||||
.footer a, .footer span {
|
||||
display: block;
|
||||
padding: .3em .7em;
|
||||
margin: 0 .38em 0 0;
|
||||
text-align:center;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.footer a:hover {
|
||||
background: #ededed;
|
||||
}
|
||||
|
||||
.footer ul, .footer li {
|
||||
list-style:none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.footer li {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.selected {
|
||||
background: #ededed;
|
||||
}
|
||||
|
||||
</style>
|
||||
<title>Fulltext search unavailable</title>
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
<div class="header">Not found</div>
|
||||
<p>
|
||||
There is no article with the title <b> "{{pattern}}"</b>
|
||||
and the fulltext search engine is not available for this content.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type" />
|
||||
<style type="text/css">
|
||||
body{
|
||||
color: #00000;
|
||||
color: #000000;
|
||||
font: small/normal Arial,Helvetica,Sans-Serif;
|
||||
margin-top: 0.5em;
|
||||
font-size: 90%;
|
||||
@@ -95,7 +95,7 @@
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
<div class="header">
|
||||
{{#hasResult}}
|
||||
{{#hasResults}}
|
||||
Results
|
||||
<b>
|
||||
{{resultStart}}-{{resultEnd}}
|
||||
@@ -104,10 +104,10 @@
|
||||
</b> for <b>
|
||||
{{searchPattern}}
|
||||
</b>
|
||||
{{/hasResult}}
|
||||
{{^hasResult}}
|
||||
{{/hasResults}}
|
||||
{{^hasResults}}
|
||||
No results were found for <b>{{searchPattern}}</b>
|
||||
{{/hasResult}}
|
||||
{{/hasResults}}
|
||||
</div>
|
||||
|
||||
<div class="results">
|
||||
@@ -129,30 +129,32 @@
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<ul>
|
||||
{{#resultLastPageStart}}
|
||||
<li>
|
||||
<a href="{{searchProtocolPrefix}}pattern={{searchPatternEncoded}}{{#contentId}}&content={{.}}{{/contentId}}&start=0&end={{resultRange}}">
|
||||
◀
|
||||
</a>
|
||||
</li>
|
||||
{{/resultLastPageStart}}
|
||||
{{#pages}}
|
||||
<li>
|
||||
<a {{#selected}}class="selected"{{/selected}}
|
||||
href="{{searchProtocolPrefix}}pattern={{searchPatternEncoded}}{{#contentId}}&content={{.}}{{/contentId}}&start={{start}}&end={{end}}">
|
||||
{{label}}
|
||||
</a>
|
||||
</li>
|
||||
{{/pages}}
|
||||
{{#resultLastPageStart}}
|
||||
<li>
|
||||
<a href="{{searchProtocolPrefix}}pattern={{searchPatternEncoded}}{{#contentId}}&content={{.}}{{/contentId}}&start={{resultLastPageStart}}&end={{lastResult}}">
|
||||
▶
|
||||
</a>
|
||||
</li>
|
||||
{{/resultLastPageStart}}
|
||||
</ul>
|
||||
{{#hasPages}}
|
||||
<ul>
|
||||
{{#resultLastPageStart}}
|
||||
<li>
|
||||
<a href="{{searchProtocolPrefix}}pattern={{searchPatternEncoded}}{{#contentId}}&content={{.}}{{/contentId}}&start=0&end={{resultRange}}">
|
||||
◀
|
||||
</a>
|
||||
</li>
|
||||
{{/resultLastPageStart}}
|
||||
{{#pages}}
|
||||
<li>
|
||||
<a {{#selected}}class="selected"{{/selected}}
|
||||
href="{{searchProtocolPrefix}}pattern={{searchPatternEncoded}}{{#contentId}}&content={{.}}{{/contentId}}&start={{start}}&end={{end}}">
|
||||
{{label}}
|
||||
</a>
|
||||
</li>
|
||||
{{/pages}}
|
||||
{{#resultLastPageStart}}
|
||||
<li>
|
||||
<a href="{{searchProtocolPrefix}}pattern={{searchPatternEncoded}}{{#contentId}}&content={{.}}{{/contentId}}&start={{resultLastPageStart}}&end={{lastResult}}">
|
||||
▶
|
||||
</a>
|
||||
</li>
|
||||
{{/resultLastPageStart}}
|
||||
</ul>
|
||||
{{/hasPages}}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
7
static/templates/suggestion.json
Normal file
@@ -0,0 +1,7 @@
|
||||
[
|
||||
{{#suggestions}}{{^first}},{{/first}}
|
||||
{
|
||||
"value" : "{{value}}",
|
||||
"label" : "{{& label}}"
|
||||
}{{/suggestions}}
|
||||
]
|
||||
25
static/templates/taskbar_part.html
Normal file
@@ -0,0 +1,25 @@
|
||||
<span class="kiwix">
|
||||
<span id="kiwixtoolbar" class="ui-widget-header">
|
||||
<div class="kiwix_centered">
|
||||
<div class="kiwix_searchform">
|
||||
<form class="kiwixsearch" method="GET" action="{{root}}/search" id="kiwixsearchform">
|
||||
{{#hascontent}}<input type="hidden" name="content" value="{{content}}" />{{/hascontent}}
|
||||
<label for="kiwixsearchbox">🔍</label>
|
||||
<input autocomplete="off" class="ui-autocomplete-input" id="kiwixsearchbox" name="pattern" type="text">
|
||||
</form>
|
||||
</div>
|
||||
{{#hascontent}}
|
||||
<input type="checkbox" id="kiwix_button_show_toggle">
|
||||
<label for="kiwix_button_show_toggle"><img src="{{root}}/skin/caret.png" alt=""></label>
|
||||
<div class="kiwix_button_cont">
|
||||
{{#withlibrarybutton}}
|
||||
<a id="kiwix_serve_taskbar_library_button" href="{{root}}/"><button>🏠</button></a>
|
||||
{{/withlibrarybutton}}
|
||||
<a id="kiwix_serve_taskbar_home_button" href="{{root}}/{{content}}/"><button>{{title}}</button></a>
|
||||
<a id="kiwix_serve_taskbar_random_button"
|
||||
href="{{root}}/random?content={{#urlencoded}}{{{content}}}{{/urlencoded}}"><button>🎲</button></a>
|
||||
</div>
|
||||
{{/hascontent}}
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
1
subprojects/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
gtest
|
||||
10
subprojects/gtest.wrap
Normal file
@@ -0,0 +1,10 @@
|
||||
[wrap-file]
|
||||
directory = googletest-release-1.8.1
|
||||
|
||||
source_url = https://github.com/google/googletest/archive/release-1.8.1.zip
|
||||
source_filename = gtest-1.8.1.zip
|
||||
source_hash = 927827c183d01734cc5cfef85e0ff3f5a92ffe6188e0d18e909c5efebf28a0c7
|
||||
|
||||
patch_url = https://wrapdb.mesonbuild.com/v1/projects/gtest/1.8.1/1/get_zip
|
||||
patch_filename = gtest-1.8.1-1-wrap.zip
|
||||
patch_hash = f79f5fd46e09507b3f2e09a51ea6eb20020effe543335f5aee59f30cc8d15805
|
||||
2
subprojects/gtest/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
# Ignore CI build directory
|
||||
build/
|
||||
@@ -1,46 +0,0 @@
|
||||
# Build matrix / environment variable are explained on:
|
||||
# http://about.travis-ci.org/docs/user/build-configuration/
|
||||
# This file can be validated on:
|
||||
# http://lint.travis-ci.org/
|
||||
|
||||
install:
|
||||
# /usr/bin/gcc is 4.6 always, but gcc-X.Y is available.
|
||||
- if [ "$CXX" = "g++" ]; then export CXX="g++-4.9" CC="gcc-4.9"; fi
|
||||
# /usr/bin/clang is 3.4, lets override with modern one.
|
||||
- if [ "$CXX" = "clang++" ] && [ "$TRAVIS_OS_NAME" = "linux" ]; then export CXX="clang++-3.7" CC="clang-3.7"; fi
|
||||
- echo ${PATH}
|
||||
- echo ${CXX}
|
||||
- ${CXX} --version
|
||||
- ${CXX} -v
|
||||
addons:
|
||||
apt:
|
||||
# List of whitelisted in travis packages for ubuntu-precise can be found here:
|
||||
# https://github.com/travis-ci/apt-package-whitelist/blob/master/ubuntu-precise
|
||||
# List of whitelisted in travis apt-sources:
|
||||
# https://github.com/travis-ci/apt-source-whitelist/blob/master/ubuntu.json
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
- llvm-toolchain-precise-3.7
|
||||
packages:
|
||||
- gcc-4.9
|
||||
- g++-4.9
|
||||
- clang-3.7
|
||||
- valgrind
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
language: cpp
|
||||
compiler:
|
||||
- gcc
|
||||
- clang
|
||||
script: ./travis.sh
|
||||
env:
|
||||
matrix:
|
||||
- GTEST_TARGET=googletest SHARED_LIB=OFF STATIC_LIB=ON CMAKE_PKG=OFF BUILD_TYPE=debug VERBOSE_MAKE=true VERBOSE
|
||||
- GTEST_TARGET=googlemock SHARED_LIB=OFF STATIC_LIB=ON CMAKE_PKG=OFF BUILD_TYPE=debug VERBOSE_MAKE=true VERBOSE
|
||||
- GTEST_TARGET=googlemock SHARED_LIB=OFF STATIC_LIB=ON CMAKE_PKG=OFF BUILD_TYPE=debug CXX_FLAGS=-std=c++11 VERBOSE_MAKE=true VERBOSE
|
||||
# - GTEST_TARGET=googletest SHARED_LIB=ON STATIC_LIB=ON CMAKE_PKG=ON BUILD_TYPE=release VERBOSE_MAKE=false
|
||||
# - GTEST_TARGET=googlemock SHARED_LIB=ON STATIC_LIB=ON CMAKE_PKG=ON BUILD_TYPE=release VERBOSE_MAKE=false
|
||||
notifications:
|
||||
email: false
|
||||
sudo: false
|
||||
@@ -1,16 +0,0 @@
|
||||
cmake_minimum_required(VERSION 2.6.2)
|
||||
|
||||
project( googletest-distribution )
|
||||
|
||||
enable_testing()
|
||||
|
||||
option(BUILD_GTEST "Builds the googletest subproject" OFF)
|
||||
|
||||
#Note that googlemock target already builds googletest
|
||||
option(BUILD_GMOCK "Builds the googlemock subproject" ON)
|
||||
|
||||
if(BUILD_GMOCK)
|
||||
add_subdirectory( googlemock )
|
||||
elseif(BUILD_GTEST)
|
||||
add_subdirectory( googletest )
|
||||
endif()
|
||||
@@ -1,142 +0,0 @@
|
||||
|
||||
# Google Test #
|
||||
|
||||
[](https://travis-ci.org/google/googletest)
|
||||
[](https://ci.appveyor.com/project/BillyDonahue/googletest/branch/master)
|
||||
|
||||
Welcome to **Google Test**, Google's C++ test framework!
|
||||
|
||||
This repository is a merger of the formerly separate GoogleTest and
|
||||
GoogleMock projects. These were so closely related that it makes sense to
|
||||
maintain and release them together.
|
||||
|
||||
Please see the project page above for more information as well as the
|
||||
mailing list for questions, discussions, and development. There is
|
||||
also an IRC channel on OFTC (irc.oftc.net) #gtest available. Please
|
||||
join us!
|
||||
|
||||
Getting started information for **Google Test** is available in the
|
||||
[Google Test Primer](googletest/docs/Primer.md) documentation.
|
||||
|
||||
**Google Mock** is an extension to Google Test for writing and using C++ mock
|
||||
classes. See the separate [Google Mock documentation](googlemock/README.md).
|
||||
|
||||
More detailed documentation for googletest (including build instructions) are
|
||||
in its interior [googletest/README.md](googletest/README.md) file.
|
||||
|
||||
## Features ##
|
||||
|
||||
* An [XUnit](https://en.wikipedia.org/wiki/XUnit) test framework.
|
||||
* Test discovery.
|
||||
* A rich set of assertions.
|
||||
* User-defined assertions.
|
||||
* Death tests.
|
||||
* Fatal and non-fatal failures.
|
||||
* Value-parameterized tests.
|
||||
* Type-parameterized tests.
|
||||
* Various options for running the tests.
|
||||
* XML test report generation.
|
||||
|
||||
## Platforms ##
|
||||
|
||||
Google test has been used on a variety of platforms:
|
||||
|
||||
* Linux
|
||||
* Mac OS X
|
||||
* Windows
|
||||
* Cygwin
|
||||
* MinGW
|
||||
* Windows Mobile
|
||||
* Symbian
|
||||
|
||||
## Who Is Using Google Test? ##
|
||||
|
||||
In addition to many internal projects at Google, Google Test is also used by
|
||||
the following notable projects:
|
||||
|
||||
* The [Chromium projects](http://www.chromium.org/) (behind the Chrome
|
||||
browser and Chrome OS).
|
||||
* The [LLVM](http://llvm.org/) compiler.
|
||||
* [Protocol Buffers](https://github.com/google/protobuf), Google's data
|
||||
interchange format.
|
||||
* The [OpenCV](http://opencv.org/) computer vision library.
|
||||
|
||||
## Related Open Source Projects ##
|
||||
|
||||
[Google Test UI](https://github.com/ospector/gtest-gbar) is test runner that runs
|
||||
your test binary, allows you to track its progress via a progress bar, and
|
||||
displays a list of test failures. Clicking on one shows failure text. Google
|
||||
Test UI is written in C#.
|
||||
|
||||
[GTest TAP Listener](https://github.com/kinow/gtest-tap-listener) is an event
|
||||
listener for Google Test that implements the
|
||||
[TAP protocol](https://en.wikipedia.org/wiki/Test_Anything_Protocol) for test
|
||||
result output. If your test runner understands TAP, you may find it useful.
|
||||
|
||||
## Requirements ##
|
||||
|
||||
Google Test is designed to have fairly minimal requirements to build
|
||||
and use with your projects, but there are some. Currently, we support
|
||||
Linux, Windows, Mac OS X, and Cygwin. We will also make our best
|
||||
effort to support other platforms (e.g. Solaris, AIX, and z/OS).
|
||||
However, since core members of the Google Test project have no access
|
||||
to these platforms, Google Test may have outstanding issues there. If
|
||||
you notice any problems on your platform, please notify
|
||||
<googletestframework@googlegroups.com>. Patches for fixing them are
|
||||
even more welcome!
|
||||
|
||||
### Linux Requirements ###
|
||||
|
||||
These are the base requirements to build and use Google Test from a source
|
||||
package (as described below):
|
||||
|
||||
* GNU-compatible Make or gmake
|
||||
* POSIX-standard shell
|
||||
* POSIX(-2) Regular Expressions (regex.h)
|
||||
* A C++98-standard-compliant compiler
|
||||
|
||||
### Windows Requirements ###
|
||||
|
||||
* Microsoft Visual C++ v7.1 or newer
|
||||
|
||||
### Cygwin Requirements ###
|
||||
|
||||
* Cygwin v1.5.25-14 or newer
|
||||
|
||||
### Mac OS X Requirements ###
|
||||
|
||||
* Mac OS X v10.4 Tiger or newer
|
||||
* Xcode Developer Tools
|
||||
|
||||
### Requirements for Contributors ###
|
||||
|
||||
We welcome patches. If you plan to contribute a patch, you need to
|
||||
build Google Test and its own tests from a git checkout (described
|
||||
below), which has further requirements:
|
||||
|
||||
* [Python](https://www.python.org/) v2.3 or newer (for running some of
|
||||
the tests and re-generating certain source files from templates)
|
||||
* [CMake](https://cmake.org/) v2.6.4 or newer
|
||||
|
||||
## Regenerating Source Files ##
|
||||
|
||||
Some of Google Test's source files are generated from templates (not
|
||||
in the C++ sense) using a script.
|
||||
For example, the
|
||||
file include/gtest/internal/gtest-type-util.h.pump is used to generate
|
||||
gtest-type-util.h in the same directory.
|
||||
|
||||
You don't need to worry about regenerating the source files
|
||||
unless you need to modify them. You would then modify the
|
||||
corresponding `.pump` files and run the '[pump.py](googletest/scripts/pump.py)'
|
||||
generator script. See the [Pump Manual](googletest/docs/PumpManual.md).
|
||||
|
||||
### Contributing Code ###
|
||||
|
||||
We welcome patches. Please read the
|
||||
[Developer's Guide](googletest/docs/DevGuide.md)
|
||||
for how you can contribute. In particular, make sure you have signed
|
||||
the Contributor License Agreement, or we won't be able to accept the
|
||||
patch.
|
||||
|
||||
Happy testing!
|
||||
@@ -1,71 +0,0 @@
|
||||
version: '{build}'
|
||||
|
||||
os: Visual Studio 2015
|
||||
|
||||
environment:
|
||||
matrix:
|
||||
- Toolset: v140
|
||||
- Toolset: v120
|
||||
- Toolset: v110
|
||||
- Toolset: v100
|
||||
|
||||
platform:
|
||||
- Win32
|
||||
- x64
|
||||
|
||||
configuration:
|
||||
# - Release
|
||||
- Debug
|
||||
|
||||
build:
|
||||
verbosity: minimal
|
||||
|
||||
artifacts:
|
||||
- path: '_build/Testing/Temporary/*'
|
||||
name: test_results
|
||||
|
||||
before_build:
|
||||
- ps: |
|
||||
Write-Output "Configuration: $env:CONFIGURATION"
|
||||
Write-Output "Platform: $env:PLATFORM"
|
||||
$generator = switch ($env:TOOLSET)
|
||||
{
|
||||
"v140" {"Visual Studio 14 2015"}
|
||||
"v120" {"Visual Studio 12 2013"}
|
||||
"v110" {"Visual Studio 11 2012"}
|
||||
"v100" {"Visual Studio 10 2010"}
|
||||
}
|
||||
if ($env:PLATFORM -eq "x64")
|
||||
{
|
||||
$generator = "$generator Win64"
|
||||
}
|
||||
|
||||
build_script:
|
||||
- ps: |
|
||||
if (($env:TOOLSET -eq "v100") -and ($env:PLATFORM -eq "x64"))
|
||||
{
|
||||
return
|
||||
}
|
||||
md _build -Force | Out-Null
|
||||
cd _build
|
||||
|
||||
& cmake -G "$generator" -DCMAKE_CONFIGURATION_TYPES="Debug;Release" -Dgtest_build_tests=ON -Dgtest_build_samples=ON -Dgmock_build_tests=ON ..
|
||||
if ($LastExitCode -ne 0) {
|
||||
throw "Exec: $ErrorMessage"
|
||||
}
|
||||
& cmake --build . --config $env:CONFIGURATION
|
||||
if ($LastExitCode -ne 0) {
|
||||
throw "Exec: $ErrorMessage"
|
||||
}
|
||||
|
||||
test_script:
|
||||
- ps: |
|
||||
if (($env:Toolset -eq "v100") -and ($env:PLATFORM -eq "x64"))
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
& ctest -C $env:CONFIGURATION --output-on-failure
|
||||
if ($LastExitCode -ne 0) {
|
||||
throw "Exec: $ErrorMessage"
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
Changes for 1.7.0:
|
||||
|
||||
* All new improvements in Google Test 1.7.0.
|
||||
* New feature: matchers DoubleNear(), FloatNear(),
|
||||
NanSensitiveDoubleNear(), NanSensitiveFloatNear(),
|
||||
UnorderedElementsAre(), UnorderedElementsAreArray(), WhenSorted(),
|
||||
WhenSortedBy(), IsEmpty(), and SizeIs().
|
||||
* Improvement: Google Mock can now be built as a DLL.
|
||||
* Improvement: when compiled by a C++11 compiler, matchers AllOf()
|
||||
and AnyOf() can accept an arbitrary number of matchers.
|
||||
* Improvement: when compiled by a C++11 compiler, matchers
|
||||
ElementsAreArray() can accept an initializer list.
|
||||
* Improvement: when exceptions are enabled, a mock method with no
|
||||
default action now throws instead crashing the test.
|
||||
* Improvement: added class testing::StringMatchResultListener to aid
|
||||
definition of composite matchers.
|
||||
* Improvement: function return types used in MOCK_METHOD*() macros can
|
||||
now contain unprotected commas.
|
||||
* Improvement (potentially breaking): EXPECT_THAT() and ASSERT_THAT()
|
||||
are now more strict in ensuring that the value type and the matcher
|
||||
type are compatible, catching potential bugs in tests.
|
||||
* Improvement: Pointee() now works on an optional<T>.
|
||||
* Improvement: the ElementsAreArray() matcher can now take a vector or
|
||||
iterator range as input, and makes a copy of its input elements
|
||||
before the conversion to a Matcher.
|
||||
* Improvement: the Google Mock Generator can now generate mocks for
|
||||
some class templates.
|
||||
* Bug fix: mock object destruction triggerred by another mock object's
|
||||
destruction no longer hangs.
|
||||
* Improvement: Google Mock Doctor works better with newer Clang and
|
||||
GCC now.
|
||||
* Compatibility fixes.
|
||||
* Bug/warning fixes.
|
||||
|
||||
Changes for 1.6.0:
|
||||
|
||||
* Compilation is much faster and uses much less memory, especially
|
||||
when the constructor and destructor of a mock class are moved out of
|
||||
the class body.
|
||||
* New matchers: Pointwise(), Each().
|
||||
* New actions: ReturnPointee() and ReturnRefOfCopy().
|
||||
* CMake support.
|
||||
* Project files for Visual Studio 2010.
|
||||
* AllOf() and AnyOf() can handle up-to 10 arguments now.
|
||||
* Google Mock doctor understands Clang error messages now.
|
||||
* SetArgPointee<> now accepts string literals.
|
||||
* gmock_gen.py handles storage specifier macros and template return
|
||||
types now.
|
||||
* Compatibility fixes.
|
||||
* Bug fixes and implementation clean-ups.
|
||||
* Potentially incompatible changes: disables the harmful 'make install'
|
||||
command in autotools.
|
||||
|
||||
Potentially breaking changes:
|
||||
|
||||
* The description string for MATCHER*() changes from Python-style
|
||||
interpolation to an ordinary C++ string expression.
|
||||
* SetArgumentPointee is deprecated in favor of SetArgPointee.
|
||||
* Some non-essential project files for Visual Studio 2005 are removed.
|
||||
|
||||
Changes for 1.5.0:
|
||||
|
||||
* New feature: Google Mock can be safely used in multi-threaded tests
|
||||
on platforms having pthreads.
|
||||
* New feature: function for printing a value of arbitrary type.
|
||||
* New feature: function ExplainMatchResult() for easy definition of
|
||||
composite matchers.
|
||||
* The new matcher API lets user-defined matchers generate custom
|
||||
explanations more directly and efficiently.
|
||||
* Better failure messages all around.
|
||||
* NotNull() and IsNull() now work with smart pointers.
|
||||
* Field() and Property() now work when the matcher argument is a pointer
|
||||
passed by reference.
|
||||
* Regular expression matchers on all platforms.
|
||||
* Added GCC 4.0 support for Google Mock Doctor.
|
||||
* Added gmock_all_test.cc for compiling most Google Mock tests
|
||||
in a single file.
|
||||
* Significantly cleaned up compiler warnings.
|
||||
* Bug fixes, better test coverage, and implementation clean-ups.
|
||||
|
||||
Potentially breaking changes:
|
||||
|
||||
* Custom matchers defined using MatcherInterface or MakePolymorphicMatcher()
|
||||
need to be updated after upgrading to Google Mock 1.5.0; matchers defined
|
||||
using MATCHER or MATCHER_P* aren't affected.
|
||||
* Dropped support for 'make install'.
|
||||
|
||||
Changes for 1.4.0 (we skipped 1.2.* and 1.3.* to match the version of
|
||||
Google Test):
|
||||
|
||||
* Works in more environments: Symbian and minGW, Visual C++ 7.1.
|
||||
* Lighter weight: comes with our own implementation of TR1 tuple (no
|
||||
more dependency on Boost!).
|
||||
* New feature: --gmock_catch_leaked_mocks for detecting leaked mocks.
|
||||
* New feature: ACTION_TEMPLATE for defining templatized actions.
|
||||
* New feature: the .After() clause for specifying expectation order.
|
||||
* New feature: the .With() clause for for specifying inter-argument
|
||||
constraints.
|
||||
* New feature: actions ReturnArg<k>(), ReturnNew<T>(...), and
|
||||
DeleteArg<k>().
|
||||
* New feature: matchers Key(), Pair(), Args<...>(), AllArgs(), IsNull(),
|
||||
and Contains().
|
||||
* New feature: utility class MockFunction<F>, useful for checkpoints, etc.
|
||||
* New feature: functions Value(x, m) and SafeMatcherCast<T>(m).
|
||||
* New feature: copying a mock object is rejected at compile time.
|
||||
* New feature: a script for fusing all Google Mock and Google Test
|
||||
source files for easy deployment.
|
||||
* Improved the Google Mock doctor to diagnose more diseases.
|
||||
* Improved the Google Mock generator script.
|
||||
* Compatibility fixes for Mac OS X and gcc.
|
||||
* Bug fixes and implementation clean-ups.
|
||||
|
||||
Changes for 1.1.0:
|
||||
|
||||
* New feature: ability to use Google Mock with any testing framework.
|
||||
* New feature: macros for easily defining new matchers
|
||||
* New feature: macros for easily defining new actions.
|
||||
* New feature: more container matchers.
|
||||
* New feature: actions for accessing function arguments and throwing
|
||||
exceptions.
|
||||
* Improved the Google Mock doctor script for diagnosing compiler errors.
|
||||
* Bug fixes and implementation clean-ups.
|
||||
|
||||
Changes for 1.0.0:
|
||||
|
||||
* Initial Open Source release of Google Mock
|
||||
@@ -1,202 +0,0 @@
|
||||
########################################################################
|
||||
# CMake build script for Google Mock.
|
||||
#
|
||||
# To run the tests for Google Mock itself on Linux, use 'make test' or
|
||||
# ctest. You can select which tests to run using 'ctest -R regex'.
|
||||
# For more options, run 'ctest --help'.
|
||||
|
||||
# BUILD_SHARED_LIBS is a standard CMake variable, but we declare it here to
|
||||
# make it prominent in the GUI.
|
||||
option(BUILD_SHARED_LIBS "Build shared libraries (DLLs)." OFF)
|
||||
|
||||
option(gmock_build_tests "Build all of Google Mock's own tests." OFF)
|
||||
|
||||
# A directory to find Google Test sources.
|
||||
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/gtest/CMakeLists.txt")
|
||||
set(gtest_dir gtest)
|
||||
else()
|
||||
set(gtest_dir ../googletest)
|
||||
endif()
|
||||
|
||||
# Defines pre_project_set_up_hermetic_build() and set_up_hermetic_build().
|
||||
include("${gtest_dir}/cmake/hermetic_build.cmake" OPTIONAL)
|
||||
|
||||
if (COMMAND pre_project_set_up_hermetic_build)
|
||||
# Google Test also calls hermetic setup functions from add_subdirectory,
|
||||
# although its changes will not affect things at the current scope.
|
||||
pre_project_set_up_hermetic_build()
|
||||
endif()
|
||||
|
||||
########################################################################
|
||||
#
|
||||
# Project-wide settings
|
||||
|
||||
# Name of the project.
|
||||
#
|
||||
# CMake files in this project can refer to the root source directory
|
||||
# as ${gmock_SOURCE_DIR} and to the root binary directory as
|
||||
# ${gmock_BINARY_DIR}.
|
||||
# Language "C" is required for find_package(Threads).
|
||||
project(gmock CXX C)
|
||||
cmake_minimum_required(VERSION 2.6.2)
|
||||
|
||||
if (COMMAND set_up_hermetic_build)
|
||||
set_up_hermetic_build()
|
||||
endif()
|
||||
|
||||
# Instructs CMake to process Google Test's CMakeLists.txt and add its
|
||||
# targets to the current scope. We are placing Google Test's binary
|
||||
# directory in a subdirectory of our own as VC compilation may break
|
||||
# if they are the same (the default).
|
||||
add_subdirectory("${gtest_dir}" "${gmock_BINARY_DIR}/gtest")
|
||||
|
||||
# Although Google Test's CMakeLists.txt calls this function, the
|
||||
# changes there don't affect the current scope. Therefore we have to
|
||||
# call it again here.
|
||||
config_compiler_and_linker() # from ${gtest_dir}/cmake/internal_utils.cmake
|
||||
|
||||
# Adds Google Mock's and Google Test's header directories to the search path.
|
||||
include_directories("${gmock_SOURCE_DIR}/include"
|
||||
"${gmock_SOURCE_DIR}"
|
||||
"${gtest_SOURCE_DIR}/include"
|
||||
# This directory is needed to build directly from Google
|
||||
# Test sources.
|
||||
"${gtest_SOURCE_DIR}")
|
||||
|
||||
# Summary of tuple support for Microsoft Visual Studio:
|
||||
# Compiler version(MS) version(cmake) Support
|
||||
# ---------- ----------- -------------- -----------------------------
|
||||
# <= VS 2010 <= 10 <= 1600 Use Google Tests's own tuple.
|
||||
# VS 2012 11 1700 std::tr1::tuple + _VARIADIC_MAX=10
|
||||
# VS 2013 12 1800 std::tr1::tuple
|
||||
if (MSVC AND MSVC_VERSION EQUAL 1700)
|
||||
add_definitions(/D _VARIADIC_MAX=10)
|
||||
endif()
|
||||
|
||||
########################################################################
|
||||
#
|
||||
# Defines the gmock & gmock_main libraries. User tests should link
|
||||
# with one of them.
|
||||
|
||||
# Google Mock libraries. We build them using more strict warnings than what
|
||||
# are used for other targets, to ensure that Google Mock can be compiled by
|
||||
# a user aggressive about warnings.
|
||||
cxx_library(gmock
|
||||
"${cxx_strict}"
|
||||
"${gtest_dir}/src/gtest-all.cc"
|
||||
src/gmock-all.cc)
|
||||
|
||||
cxx_library(gmock_main
|
||||
"${cxx_strict}"
|
||||
"${gtest_dir}/src/gtest-all.cc"
|
||||
src/gmock-all.cc
|
||||
src/gmock_main.cc)
|
||||
|
||||
# If the CMake version supports it, attach header directory information
|
||||
# to the targets for when we are part of a parent build (ie being pulled
|
||||
# in via add_subdirectory() rather than being a standalone build).
|
||||
if (DEFINED CMAKE_VERSION AND NOT "${CMAKE_VERSION}" VERSION_LESS "2.8.11")
|
||||
target_include_directories(gmock INTERFACE "${gmock_SOURCE_DIR}/include")
|
||||
target_include_directories(gmock_main INTERFACE "${gmock_SOURCE_DIR}/include")
|
||||
endif()
|
||||
|
||||
########################################################################
|
||||
#
|
||||
# Install rules
|
||||
install(TARGETS gmock gmock_main
|
||||
DESTINATION lib)
|
||||
install(DIRECTORY ${gmock_SOURCE_DIR}/include/gmock
|
||||
DESTINATION include)
|
||||
|
||||
########################################################################
|
||||
#
|
||||
# Google Mock's own tests.
|
||||
#
|
||||
# You can skip this section if you aren't interested in testing
|
||||
# Google Mock itself.
|
||||
#
|
||||
# The tests are not built by default. To build them, set the
|
||||
# gmock_build_tests option to ON. You can do it by running ccmake
|
||||
# or specifying the -Dgmock_build_tests=ON flag when running cmake.
|
||||
|
||||
if (gmock_build_tests)
|
||||
# This must be set in the root directory for the tests to be run by
|
||||
# 'make test' or ctest.
|
||||
enable_testing()
|
||||
|
||||
############################################################
|
||||
# C++ tests built with standard compiler flags.
|
||||
|
||||
cxx_test(gmock-actions_test gmock_main)
|
||||
cxx_test(gmock-cardinalities_test gmock_main)
|
||||
cxx_test(gmock_ex_test gmock_main)
|
||||
cxx_test(gmock-generated-actions_test gmock_main)
|
||||
cxx_test(gmock-generated-function-mockers_test gmock_main)
|
||||
cxx_test(gmock-generated-internal-utils_test gmock_main)
|
||||
cxx_test(gmock-generated-matchers_test gmock_main)
|
||||
cxx_test(gmock-internal-utils_test gmock_main)
|
||||
cxx_test(gmock-matchers_test gmock_main)
|
||||
cxx_test(gmock-more-actions_test gmock_main)
|
||||
cxx_test(gmock-nice-strict_test gmock_main)
|
||||
cxx_test(gmock-port_test gmock_main)
|
||||
cxx_test(gmock-spec-builders_test gmock_main)
|
||||
cxx_test(gmock_link_test gmock_main test/gmock_link2_test.cc)
|
||||
cxx_test(gmock_test gmock_main)
|
||||
|
||||
if (CMAKE_USE_PTHREADS_INIT)
|
||||
cxx_test(gmock_stress_test gmock)
|
||||
endif()
|
||||
|
||||
# gmock_all_test is commented to save time building and running tests.
|
||||
# Uncomment if necessary.
|
||||
# cxx_test(gmock_all_test gmock_main)
|
||||
|
||||
############################################################
|
||||
# C++ tests built with non-standard compiler flags.
|
||||
|
||||
cxx_library(gmock_main_no_exception "${cxx_no_exception}"
|
||||
"${gtest_dir}/src/gtest-all.cc" src/gmock-all.cc src/gmock_main.cc)
|
||||
|
||||
cxx_library(gmock_main_no_rtti "${cxx_no_rtti}"
|
||||
"${gtest_dir}/src/gtest-all.cc" src/gmock-all.cc src/gmock_main.cc)
|
||||
|
||||
if (NOT MSVC OR MSVC_VERSION LESS 1600) # 1600 is Visual Studio 2010.
|
||||
# Visual Studio 2010, 2012, and 2013 define symbols in std::tr1 that
|
||||
# conflict with our own definitions. Therefore using our own tuple does not
|
||||
# work on those compilers.
|
||||
cxx_library(gmock_main_use_own_tuple "${cxx_use_own_tuple}"
|
||||
"${gtest_dir}/src/gtest-all.cc" src/gmock-all.cc src/gmock_main.cc)
|
||||
|
||||
cxx_test_with_flags(gmock_use_own_tuple_test "${cxx_use_own_tuple}"
|
||||
gmock_main_use_own_tuple test/gmock-spec-builders_test.cc)
|
||||
endif()
|
||||
|
||||
cxx_test_with_flags(gmock-more-actions_no_exception_test "${cxx_no_exception}"
|
||||
gmock_main_no_exception test/gmock-more-actions_test.cc)
|
||||
|
||||
cxx_test_with_flags(gmock_no_rtti_test "${cxx_no_rtti}"
|
||||
gmock_main_no_rtti test/gmock-spec-builders_test.cc)
|
||||
|
||||
cxx_shared_library(shared_gmock_main "${cxx_default}"
|
||||
"${gtest_dir}/src/gtest-all.cc" src/gmock-all.cc src/gmock_main.cc)
|
||||
|
||||
# Tests that a binary can be built with Google Mock as a shared library. On
|
||||
# some system configurations, it may not possible to run the binary without
|
||||
# knowing more details about the system configurations. We do not try to run
|
||||
# this binary. To get a more robust shared library coverage, configure with
|
||||
# -DBUILD_SHARED_LIBS=ON.
|
||||
cxx_executable_with_flags(shared_gmock_test_ "${cxx_default}"
|
||||
shared_gmock_main test/gmock-spec-builders_test.cc)
|
||||
set_target_properties(shared_gmock_test_
|
||||
PROPERTIES
|
||||
COMPILE_DEFINITIONS "GTEST_LINKED_AS_SHARED_LIBRARY=1")
|
||||
|
||||
############################################################
|
||||
# Python tests.
|
||||
|
||||
cxx_executable(gmock_leak_test_ test gmock_main)
|
||||
py_test(gmock_leak_test)
|
||||
|
||||
cxx_executable(gmock_output_test_ test gmock)
|
||||
py_test(gmock_output_test)
|
||||
endif()
|
||||
@@ -1,40 +0,0 @@
|
||||
# This file contains a list of people who've made non-trivial
|
||||
# contribution to the Google C++ Mocking Framework project. People
|
||||
# who commit code to the project are encouraged to add their names
|
||||
# here. Please keep the list sorted by first names.
|
||||
|
||||
Benoit Sigoure <tsuna@google.com>
|
||||
Bogdan Piloca <boo@google.com>
|
||||
Chandler Carruth <chandlerc@google.com>
|
||||
Dave MacLachlan <dmaclach@gmail.com>
|
||||
David Anderson <danderson@google.com>
|
||||
Dean Sturtevant
|
||||
Gene Volovich <gv@cite.com>
|
||||
Hal Burch <gmock@hburch.com>
|
||||
Jeffrey Yasskin <jyasskin@google.com>
|
||||
Jim Keller <jimkeller@google.com>
|
||||
Joe Walnes <joe@truemesh.com>
|
||||
Jon Wray <jwray@google.com>
|
||||
Keir Mierle <mierle@gmail.com>
|
||||
Keith Ray <keith.ray@gmail.com>
|
||||
Kostya Serebryany <kcc@google.com>
|
||||
Lev Makhlis
|
||||
Manuel Klimek <klimek@google.com>
|
||||
Mario Tanev <radix@google.com>
|
||||
Mark Paskin
|
||||
Markus Heule <markus.heule@gmail.com>
|
||||
Matthew Simmons <simmonmt@acm.org>
|
||||
Mike Bland <mbland@google.com>
|
||||
Neal Norwitz <nnorwitz@gmail.com>
|
||||
Nermin Ozkiranartli <nermin@google.com>
|
||||
Owen Carlsen <ocarlsen@google.com>
|
||||
Paneendra Ba <paneendra@google.com>
|
||||
Paul Menage <menage@google.com>
|
||||
Piotr Kaminski <piotrk@google.com>
|
||||
Russ Rufer <russ@pentad.com>
|
||||
Sverre Sundsdal <sundsdal@gmail.com>
|
||||
Takeshi Yoshino <tyoshino@google.com>
|
||||
Vadim Berman <vadimb@google.com>
|
||||
Vlad Losev <vladl@google.com>
|
||||
Wolfgang Klier <wklier@google.com>
|
||||
Zhanyong Wan <wan@google.com>
|
||||
@@ -1,28 +0,0 @@
|
||||
Copyright 2008, Google Inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -1,224 +0,0 @@
|
||||
# Automake file
|
||||
|
||||
# Nonstandard package files for distribution.
|
||||
EXTRA_DIST = LICENSE
|
||||
|
||||
# We may need to build our internally packaged gtest. If so, it will be
|
||||
# included in the 'subdirs' variable.
|
||||
SUBDIRS = $(subdirs)
|
||||
|
||||
# This is generated by the configure script, so clean it for distribution.
|
||||
DISTCLEANFILES = scripts/gmock-config
|
||||
|
||||
# We define the global AM_CPPFLAGS as everything we compile includes from these
|
||||
# directories.
|
||||
AM_CPPFLAGS = $(GTEST_CPPFLAGS) -I$(srcdir)/include
|
||||
|
||||
# Modifies compiler and linker flags for pthreads compatibility.
|
||||
if HAVE_PTHREADS
|
||||
AM_CXXFLAGS = @PTHREAD_CFLAGS@ -DGTEST_HAS_PTHREAD=1
|
||||
AM_LIBS = @PTHREAD_LIBS@
|
||||
endif
|
||||
|
||||
# Build rules for libraries.
|
||||
lib_LTLIBRARIES = lib/libgmock.la lib/libgmock_main.la
|
||||
|
||||
lib_libgmock_la_SOURCES = src/gmock-all.cc
|
||||
|
||||
pkginclude_HEADERS = \
|
||||
include/gmock/gmock-actions.h \
|
||||
include/gmock/gmock-cardinalities.h \
|
||||
include/gmock/gmock-generated-actions.h \
|
||||
include/gmock/gmock-generated-function-mockers.h \
|
||||
include/gmock/gmock-generated-matchers.h \
|
||||
include/gmock/gmock-generated-nice-strict.h \
|
||||
include/gmock/gmock-matchers.h \
|
||||
include/gmock/gmock-more-actions.h \
|
||||
include/gmock/gmock-more-matchers.h \
|
||||
include/gmock/gmock-spec-builders.h \
|
||||
include/gmock/gmock.h
|
||||
|
||||
pkginclude_internaldir = $(pkgincludedir)/internal
|
||||
pkginclude_internal_HEADERS = \
|
||||
include/gmock/internal/gmock-generated-internal-utils.h \
|
||||
include/gmock/internal/gmock-internal-utils.h \
|
||||
include/gmock/internal/gmock-port.h \
|
||||
include/gmock/internal/custom/gmock-generated-actions.h \
|
||||
include/gmock/internal/custom/gmock-matchers.h \
|
||||
include/gmock/internal/custom/gmock-port.h
|
||||
|
||||
lib_libgmock_main_la_SOURCES = src/gmock_main.cc
|
||||
lib_libgmock_main_la_LIBADD = lib/libgmock.la
|
||||
|
||||
# Build rules for tests. Automake's naming for some of these variables isn't
|
||||
# terribly obvious, so this is a brief reference:
|
||||
#
|
||||
# TESTS -- Programs run automatically by "make check"
|
||||
# check_PROGRAMS -- Programs built by "make check" but not necessarily run
|
||||
|
||||
TESTS=
|
||||
check_PROGRAMS=
|
||||
AM_LDFLAGS = $(GTEST_LDFLAGS)
|
||||
|
||||
# This exercises all major components of Google Mock. It also
|
||||
# verifies that libgmock works.
|
||||
TESTS += test/gmock-spec-builders_test
|
||||
check_PROGRAMS += test/gmock-spec-builders_test
|
||||
test_gmock_spec_builders_test_SOURCES = test/gmock-spec-builders_test.cc
|
||||
test_gmock_spec_builders_test_LDADD = $(GTEST_LIBS) lib/libgmock.la
|
||||
|
||||
# This tests using Google Mock in multiple translation units. It also
|
||||
# verifies that libgmock_main and libgmock work.
|
||||
TESTS += test/gmock_link_test
|
||||
check_PROGRAMS += test/gmock_link_test
|
||||
test_gmock_link_test_SOURCES = \
|
||||
test/gmock_link2_test.cc \
|
||||
test/gmock_link_test.cc \
|
||||
test/gmock_link_test.h
|
||||
test_gmock_link_test_LDADD = $(GTEST_LIBS) lib/libgmock_main.la lib/libgmock.la
|
||||
|
||||
if HAVE_PYTHON
|
||||
# Tests that fused gmock files compile and work.
|
||||
TESTS += test/gmock_fused_test
|
||||
check_PROGRAMS += test/gmock_fused_test
|
||||
test_gmock_fused_test_SOURCES = \
|
||||
fused-src/gmock-gtest-all.cc \
|
||||
fused-src/gmock/gmock.h \
|
||||
fused-src/gmock_main.cc \
|
||||
fused-src/gtest/gtest.h \
|
||||
test/gmock_test.cc
|
||||
test_gmock_fused_test_CPPFLAGS = -I"$(srcdir)/fused-src"
|
||||
endif
|
||||
|
||||
# Google Mock source files that we don't compile directly.
|
||||
GMOCK_SOURCE_INGLUDES = \
|
||||
src/gmock-cardinalities.cc \
|
||||
src/gmock-internal-utils.cc \
|
||||
src/gmock-matchers.cc \
|
||||
src/gmock-spec-builders.cc \
|
||||
src/gmock.cc
|
||||
|
||||
EXTRA_DIST += $(GMOCK_SOURCE_INGLUDES)
|
||||
|
||||
# C++ tests that we don't compile using autotools.
|
||||
EXTRA_DIST += \
|
||||
test/gmock-actions_test.cc \
|
||||
test/gmock_all_test.cc \
|
||||
test/gmock-cardinalities_test.cc \
|
||||
test/gmock_ex_test.cc \
|
||||
test/gmock-generated-actions_test.cc \
|
||||
test/gmock-generated-function-mockers_test.cc \
|
||||
test/gmock-generated-internal-utils_test.cc \
|
||||
test/gmock-generated-matchers_test.cc \
|
||||
test/gmock-internal-utils_test.cc \
|
||||
test/gmock-matchers_test.cc \
|
||||
test/gmock-more-actions_test.cc \
|
||||
test/gmock-nice-strict_test.cc \
|
||||
test/gmock-port_test.cc \
|
||||
test/gmock_stress_test.cc
|
||||
|
||||
# Python tests, which we don't run using autotools.
|
||||
EXTRA_DIST += \
|
||||
test/gmock_leak_test.py \
|
||||
test/gmock_leak_test_.cc \
|
||||
test/gmock_output_test.py \
|
||||
test/gmock_output_test_.cc \
|
||||
test/gmock_output_test_golden.txt \
|
||||
test/gmock_test_utils.py
|
||||
|
||||
# Nonstandard package files for distribution.
|
||||
EXTRA_DIST += \
|
||||
CHANGES \
|
||||
CONTRIBUTORS \
|
||||
make/Makefile
|
||||
|
||||
# Pump scripts for generating Google Mock headers.
|
||||
# TODO(chandlerc@google.com): automate the generation of *.h from *.h.pump.
|
||||
EXTRA_DIST += \
|
||||
include/gmock/gmock-generated-actions.h.pump \
|
||||
include/gmock/gmock-generated-function-mockers.h.pump \
|
||||
include/gmock/gmock-generated-matchers.h.pump \
|
||||
include/gmock/gmock-generated-nice-strict.h.pump \
|
||||
include/gmock/internal/gmock-generated-internal-utils.h.pump \
|
||||
include/gmock/internal/custom/gmock-generated-actions.h.pump
|
||||
|
||||
# Script for fusing Google Mock and Google Test source files.
|
||||
EXTRA_DIST += scripts/fuse_gmock_files.py
|
||||
|
||||
# The Google Mock Generator tool from the cppclean project.
|
||||
EXTRA_DIST += \
|
||||
scripts/generator/LICENSE \
|
||||
scripts/generator/README \
|
||||
scripts/generator/README.cppclean \
|
||||
scripts/generator/cpp/__init__.py \
|
||||
scripts/generator/cpp/ast.py \
|
||||
scripts/generator/cpp/gmock_class.py \
|
||||
scripts/generator/cpp/keywords.py \
|
||||
scripts/generator/cpp/tokenize.py \
|
||||
scripts/generator/cpp/utils.py \
|
||||
scripts/generator/gmock_gen.py
|
||||
|
||||
# Script for diagnosing compiler errors in programs that use Google
|
||||
# Mock.
|
||||
EXTRA_DIST += scripts/gmock_doctor.py
|
||||
|
||||
# CMake scripts.
|
||||
EXTRA_DIST += \
|
||||
CMakeLists.txt
|
||||
|
||||
# Microsoft Visual Studio 2005 projects.
|
||||
EXTRA_DIST += \
|
||||
msvc/2005/gmock.sln \
|
||||
msvc/2005/gmock.vcproj \
|
||||
msvc/2005/gmock_config.vsprops \
|
||||
msvc/2005/gmock_main.vcproj \
|
||||
msvc/2005/gmock_test.vcproj
|
||||
|
||||
# Microsoft Visual Studio 2010 projects.
|
||||
EXTRA_DIST += \
|
||||
msvc/2010/gmock.sln \
|
||||
msvc/2010/gmock.vcxproj \
|
||||
msvc/2010/gmock_config.props \
|
||||
msvc/2010/gmock_main.vcxproj \
|
||||
msvc/2010/gmock_test.vcxproj
|
||||
|
||||
if HAVE_PYTHON
|
||||
# gmock_test.cc does not really depend on files generated by the
|
||||
# fused-gmock-internal rule. However, gmock_test.o does, and it is
|
||||
# important to include test/gmock_test.cc as part of this rule in order to
|
||||
# prevent compiling gmock_test.o until all dependent files have been
|
||||
# generated.
|
||||
$(test_gmock_fused_test_SOURCES): fused-gmock-internal
|
||||
|
||||
# TODO(vladl@google.com): Find a way to add Google Tests's sources here.
|
||||
fused-gmock-internal: $(pkginclude_HEADERS) $(pkginclude_internal_HEADERS) \
|
||||
$(lib_libgmock_la_SOURCES) $(GMOCK_SOURCE_INGLUDES) \
|
||||
$(lib_libgmock_main_la_SOURCES) \
|
||||
scripts/fuse_gmock_files.py
|
||||
mkdir -p "$(srcdir)/fused-src"
|
||||
chmod -R u+w "$(srcdir)/fused-src"
|
||||
rm -f "$(srcdir)/fused-src/gtest/gtest.h"
|
||||
rm -f "$(srcdir)/fused-src/gmock/gmock.h"
|
||||
rm -f "$(srcdir)/fused-src/gmock-gtest-all.cc"
|
||||
"$(srcdir)/scripts/fuse_gmock_files.py" "$(srcdir)/fused-src"
|
||||
cp -f "$(srcdir)/src/gmock_main.cc" "$(srcdir)/fused-src"
|
||||
|
||||
maintainer-clean-local:
|
||||
rm -rf "$(srcdir)/fused-src"
|
||||
endif
|
||||
|
||||
# Death tests may produce core dumps in the build directory. In case
|
||||
# this happens, clean them to keep distcleancheck happy.
|
||||
CLEANFILES = core
|
||||
|
||||
# Disables 'make install' as installing a compiled version of Google
|
||||
# Mock can lead to undefined behavior due to violation of the
|
||||
# One-Definition Rule.
|
||||
|
||||
install-exec-local:
|
||||
echo "'make install' is dangerous and not supported. Instead, see README for how to integrate Google Mock into your build system."
|
||||
false
|
||||
|
||||
install-data-local:
|
||||
echo "'make install' is dangerous and not supported. Instead, see README for how to integrate Google Mock into your build system."
|
||||
false
|
||||
@@ -1,333 +0,0 @@
|
||||
## Google Mock ##
|
||||
|
||||
The Google C++ mocking framework.
|
||||
|
||||
### Overview ###
|
||||
|
||||
Google's framework for writing and using C++ mock classes.
|
||||
It can help you derive better designs of your system and write better tests.
|
||||
|
||||
It is inspired by:
|
||||
|
||||
* [jMock](http://www.jmock.org/),
|
||||
* [EasyMock](http://www.easymock.org/), and
|
||||
* [Hamcrest](http://code.google.com/p/hamcrest/),
|
||||
|
||||
and designed with C++'s specifics in mind.
|
||||
|
||||
Google mock:
|
||||
|
||||
* lets you create mock classes trivially using simple macros.
|
||||
* supports a rich set of matchers and actions.
|
||||
* handles unordered, partially ordered, or completely ordered expectations.
|
||||
* is extensible by users.
|
||||
|
||||
We hope you find it useful!
|
||||
|
||||
### Features ###
|
||||
|
||||
* Provides a declarative syntax for defining mocks.
|
||||
* Can easily define partial (hybrid) mocks, which are a cross of real
|
||||
and mock objects.
|
||||
* Handles functions of arbitrary types and overloaded functions.
|
||||
* Comes with a rich set of matchers for validating function arguments.
|
||||
* Uses an intuitive syntax for controlling the behavior of a mock.
|
||||
* Does automatic verification of expectations (no record-and-replay needed).
|
||||
* Allows arbitrary (partial) ordering constraints on
|
||||
function calls to be expressed,.
|
||||
* Lets a user extend it by defining new matchers and actions.
|
||||
* Does not use exceptions.
|
||||
* Is easy to learn and use.
|
||||
|
||||
Please see the project page above for more information as well as the
|
||||
mailing list for questions, discussions, and development. There is
|
||||
also an IRC channel on OFTC (irc.oftc.net) #gtest available. Please
|
||||
join us!
|
||||
|
||||
Please note that code under [scripts/generator](scripts/generator/) is
|
||||
from [cppclean](http://code.google.com/p/cppclean/) and released under
|
||||
the Apache License, which is different from Google Mock's license.
|
||||
|
||||
## Getting Started ##
|
||||
|
||||
If you are new to the project, we suggest that you read the user
|
||||
documentation in the following order:
|
||||
|
||||
* Learn the [basics](../googletest/docs/Primer.md) of
|
||||
Google Test, if you choose to use Google Mock with it (recommended).
|
||||
* Read [Google Mock for Dummies](docs/ForDummies.md).
|
||||
* Read the instructions below on how to build Google Mock.
|
||||
|
||||
You can also watch Zhanyong's [talk](http://www.youtube.com/watch?v=sYpCyLI47rM) on Google Mock's usage and implementation.
|
||||
|
||||
Once you understand the basics, check out the rest of the docs:
|
||||
|
||||
* [CheatSheet](docs/CheatSheet.md) - all the commonly used stuff
|
||||
at a glance.
|
||||
* [CookBook](docs/CookBook.md) - recipes for getting things done,
|
||||
including advanced techniques.
|
||||
|
||||
If you need help, please check the
|
||||
[KnownIssues](docs/KnownIssues.md) and
|
||||
[FrequentlyAskedQuestions](docs/FrequentlyAskedQuestions.md) before
|
||||
posting a question on the
|
||||
[discussion group](http://groups.google.com/group/googlemock).
|
||||
|
||||
|
||||
### Using Google Mock Without Google Test ###
|
||||
|
||||
Google Mock is not a testing framework itself. Instead, it needs a
|
||||
testing framework for writing tests. Google Mock works seamlessly
|
||||
with [Google Test](http://code.google.com/p/googletest/), but
|
||||
you can also use it with [any C++ testing framework](googlemock/ForDummies.md#Using_Google_Mock_with_Any_Testing_Framework).
|
||||
|
||||
### Requirements for End Users ###
|
||||
|
||||
Google Mock is implemented on top of [Google Test](
|
||||
http://github.com/google/googletest/), and depends on it.
|
||||
You must use the bundled version of Google Test when using Google Mock.
|
||||
|
||||
You can also easily configure Google Mock to work with another testing
|
||||
framework, although it will still need Google Test. Please read
|
||||
["Using_Google_Mock_with_Any_Testing_Framework"](
|
||||
docs/ForDummies.md#Using_Google_Mock_with_Any_Testing_Framework)
|
||||
for instructions.
|
||||
|
||||
Google Mock depends on advanced C++ features and thus requires a more
|
||||
modern compiler. The following are needed to use Google Mock:
|
||||
|
||||
#### Linux Requirements ####
|
||||
|
||||
* GNU-compatible Make or "gmake"
|
||||
* POSIX-standard shell
|
||||
* POSIX(-2) Regular Expressions (regex.h)
|
||||
* C++98-standard-compliant compiler (e.g. GCC 3.4 or newer)
|
||||
|
||||
#### Windows Requirements ####
|
||||
|
||||
* Microsoft Visual C++ 8.0 SP1 or newer
|
||||
|
||||
#### Mac OS X Requirements ####
|
||||
|
||||
* Mac OS X 10.4 Tiger or newer
|
||||
* Developer Tools Installed
|
||||
|
||||
### Requirements for Contributors ###
|
||||
|
||||
We welcome patches. If you plan to contribute a patch, you need to
|
||||
build Google Mock and its tests, which has further requirements:
|
||||
|
||||
* Automake version 1.9 or newer
|
||||
* Autoconf version 2.59 or newer
|
||||
* Libtool / Libtoolize
|
||||
* Python version 2.3 or newer (for running some of the tests and
|
||||
re-generating certain source files from templates)
|
||||
|
||||
### Building Google Mock ###
|
||||
|
||||
#### Preparing to Build (Unix only) ####
|
||||
|
||||
If you are using a Unix system and plan to use the GNU Autotools build
|
||||
system to build Google Mock (described below), you'll need to
|
||||
configure it now.
|
||||
|
||||
To prepare the Autotools build system:
|
||||
|
||||
cd googlemock
|
||||
autoreconf -fvi
|
||||
|
||||
To build Google Mock and your tests that use it, you need to tell your
|
||||
build system where to find its headers and source files. The exact
|
||||
way to do it depends on which build system you use, and is usually
|
||||
straightforward.
|
||||
|
||||
This section shows how you can integrate Google Mock into your
|
||||
existing build system.
|
||||
|
||||
Suppose you put Google Mock in directory `${GMOCK_DIR}` and Google Test
|
||||
in `${GTEST_DIR}` (the latter is `${GMOCK_DIR}/gtest` by default). To
|
||||
build Google Mock, create a library build target (or a project as
|
||||
called by Visual Studio and Xcode) to compile
|
||||
|
||||
${GTEST_DIR}/src/gtest-all.cc and ${GMOCK_DIR}/src/gmock-all.cc
|
||||
|
||||
with
|
||||
|
||||
${GTEST_DIR}/include and ${GMOCK_DIR}/include
|
||||
|
||||
in the system header search path, and
|
||||
|
||||
${GTEST_DIR} and ${GMOCK_DIR}
|
||||
|
||||
in the normal header search path. Assuming a Linux-like system and gcc,
|
||||
something like the following will do:
|
||||
|
||||
g++ -isystem ${GTEST_DIR}/include -I${GTEST_DIR} \
|
||||
-isystem ${GMOCK_DIR}/include -I${GMOCK_DIR} \
|
||||
-pthread -c ${GTEST_DIR}/src/gtest-all.cc
|
||||
g++ -isystem ${GTEST_DIR}/include -I${GTEST_DIR} \
|
||||
-isystem ${GMOCK_DIR}/include -I${GMOCK_DIR} \
|
||||
-pthread -c ${GMOCK_DIR}/src/gmock-all.cc
|
||||
ar -rv libgmock.a gtest-all.o gmock-all.o
|
||||
|
||||
(We need -pthread as Google Test and Google Mock use threads.)
|
||||
|
||||
Next, you should compile your test source file with
|
||||
${GTEST\_DIR}/include and ${GMOCK\_DIR}/include in the header search
|
||||
path, and link it with gmock and any other necessary libraries:
|
||||
|
||||
g++ -isystem ${GTEST_DIR}/include -isystem ${GMOCK_DIR}/include \
|
||||
-pthread path/to/your_test.cc libgmock.a -o your_test
|
||||
|
||||
As an example, the make/ directory contains a Makefile that you can
|
||||
use to build Google Mock on systems where GNU make is available
|
||||
(e.g. Linux, Mac OS X, and Cygwin). It doesn't try to build Google
|
||||
Mock's own tests. Instead, it just builds the Google Mock library and
|
||||
a sample test. You can use it as a starting point for your own build
|
||||
script.
|
||||
|
||||
If the default settings are correct for your environment, the
|
||||
following commands should succeed:
|
||||
|
||||
cd ${GMOCK_DIR}/make
|
||||
make
|
||||
./gmock_test
|
||||
|
||||
If you see errors, try to tweak the contents of
|
||||
[make/Makefile](make/Makefile) to make them go away.
|
||||
|
||||
### Windows ###
|
||||
|
||||
The msvc/2005 directory contains VC++ 2005 projects and the msvc/2010
|
||||
directory contains VC++ 2010 projects for building Google Mock and
|
||||
selected tests.
|
||||
|
||||
Change to the appropriate directory and run "msbuild gmock.sln" to
|
||||
build the library and tests (or open the gmock.sln in the MSVC IDE).
|
||||
If you want to create your own project to use with Google Mock, you'll
|
||||
have to configure it to use the `gmock_config` propety sheet. For that:
|
||||
|
||||
* Open the Property Manager window (View | Other Windows | Property Manager)
|
||||
* Right-click on your project and select "Add Existing Property Sheet..."
|
||||
* Navigate to `gmock_config.vsprops` or `gmock_config.props` and select it.
|
||||
* In Project Properties | Configuration Properties | General | Additional
|
||||
Include Directories, type <path to Google Mock>/include.
|
||||
|
||||
### Tweaking Google Mock ###
|
||||
|
||||
Google Mock can be used in diverse environments. The default
|
||||
configuration may not work (or may not work well) out of the box in
|
||||
some environments. However, you can easily tweak Google Mock by
|
||||
defining control macros on the compiler command line. Generally,
|
||||
these macros are named like `GTEST_XYZ` and you define them to either 1
|
||||
or 0 to enable or disable a certain feature.
|
||||
|
||||
We list the most frequently used macros below. For a complete list,
|
||||
see file [${GTEST\_DIR}/include/gtest/internal/gtest-port.h](
|
||||
../googletest/include/gtest/internal/gtest-port.h).
|
||||
|
||||
### Choosing a TR1 Tuple Library ###
|
||||
|
||||
Google Mock uses the C++ Technical Report 1 (TR1) tuple library
|
||||
heavily. Unfortunately TR1 tuple is not yet widely available with all
|
||||
compilers. The good news is that Google Test 1.4.0+ implements a
|
||||
subset of TR1 tuple that's enough for Google Mock's need. Google Mock
|
||||
will automatically use that implementation when the compiler doesn't
|
||||
provide TR1 tuple.
|
||||
|
||||
Usually you don't need to care about which tuple library Google Test
|
||||
and Google Mock use. However, if your project already uses TR1 tuple,
|
||||
you need to tell Google Test and Google Mock to use the same TR1 tuple
|
||||
library the rest of your project uses, or the two tuple
|
||||
implementations will clash. To do that, add
|
||||
|
||||
-DGTEST_USE_OWN_TR1_TUPLE=0
|
||||
|
||||
to the compiler flags while compiling Google Test, Google Mock, and
|
||||
your tests. If you want to force Google Test and Google Mock to use
|
||||
their own tuple library, just add
|
||||
|
||||
-DGTEST_USE_OWN_TR1_TUPLE=1
|
||||
|
||||
to the compiler flags instead.
|
||||
|
||||
If you want to use Boost's TR1 tuple library with Google Mock, please
|
||||
refer to the Boost website (http://www.boost.org/) for how to obtain
|
||||
it and set it up.
|
||||
|
||||
### As a Shared Library (DLL) ###
|
||||
|
||||
Google Mock is compact, so most users can build and link it as a static
|
||||
library for the simplicity. Google Mock can be used as a DLL, but the
|
||||
same DLL must contain Google Test as well. See
|
||||
[Google Test's README][gtest_readme]
|
||||
for instructions on how to set up necessary compiler settings.
|
||||
|
||||
### Tweaking Google Mock ###
|
||||
|
||||
Most of Google Test's control macros apply to Google Mock as well.
|
||||
Please see [Google Test's README][gtest_readme] for how to tweak them.
|
||||
|
||||
### Upgrading from an Earlier Version ###
|
||||
|
||||
We strive to keep Google Mock releases backward compatible.
|
||||
Sometimes, though, we have to make some breaking changes for the
|
||||
users' long-term benefits. This section describes what you'll need to
|
||||
do if you are upgrading from an earlier version of Google Mock.
|
||||
|
||||
#### Upgrading from 1.1.0 or Earlier ####
|
||||
|
||||
You may need to explicitly enable or disable Google Test's own TR1
|
||||
tuple library. See the instructions in section "[Choosing a TR1 Tuple
|
||||
Library](../googletest/#choosing-a-tr1-tuple-library)".
|
||||
|
||||
#### Upgrading from 1.4.0 or Earlier ####
|
||||
|
||||
On platforms where the pthread library is available, Google Test and
|
||||
Google Mock use it in order to be thread-safe. For this to work, you
|
||||
may need to tweak your compiler and/or linker flags. Please see the
|
||||
"[Multi-threaded Tests](../googletest#multi-threaded-tests
|
||||
)" section in file Google Test's README for what you may need to do.
|
||||
|
||||
If you have custom matchers defined using `MatcherInterface` or
|
||||
`MakePolymorphicMatcher()`, you'll need to update their definitions to
|
||||
use the new matcher API (
|
||||
[monomorphic](http://code.google.com/p/googlemock/wiki/CookBook#Writing_New_Monomorphic_Matchers),
|
||||
[polymorphic](http://code.google.com/p/googlemock/wiki/CookBook#Writing_New_Polymorphic_Matchers)).
|
||||
Matchers defined using `MATCHER()` or `MATCHER_P*()` aren't affected.
|
||||
|
||||
### Developing Google Mock ###
|
||||
|
||||
This section discusses how to make your own changes to Google Mock.
|
||||
|
||||
#### Testing Google Mock Itself ####
|
||||
|
||||
To make sure your changes work as intended and don't break existing
|
||||
functionality, you'll want to compile and run Google Test's own tests.
|
||||
For that you'll need Autotools. First, make sure you have followed
|
||||
the instructions above to configure Google Mock.
|
||||
Then, create a build output directory and enter it. Next,
|
||||
|
||||
${GMOCK_DIR}/configure # try --help for more info
|
||||
|
||||
Once you have successfully configured Google Mock, the build steps are
|
||||
standard for GNU-style OSS packages.
|
||||
|
||||
make # Standard makefile following GNU conventions
|
||||
make check # Builds and runs all tests - all should pass.
|
||||
|
||||
Note that when building your project against Google Mock, you are building
|
||||
against Google Test as well. There is no need to configure Google Test
|
||||
separately.
|
||||
|
||||
#### Contributing a Patch ####
|
||||
|
||||
We welcome patches.
|
||||
Please read the [Developer's Guide](docs/DevGuide.md)
|
||||
for how you can contribute. In particular, make sure you have signed
|
||||
the Contributor License Agreement, or we won't be able to accept the
|
||||
patch.
|
||||
|
||||
Happy testing!
|
||||
|
||||
[gtest_readme]: ../googletest/README.md "googletest"
|
||||