mirror of
https://github.com/kiwix/libkiwix.git
synced 2026-02-18 15:20:22 -05:00
Compare commits
67 Commits
i18n_trans
...
feature/av
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
142bf834f0 | ||
|
|
f32ab92e85 | ||
|
|
97ad8232e4 | ||
|
|
766816b3c4 | ||
|
|
e32e9e7722 | ||
|
|
0b14fda94d | ||
|
|
fe965faf1b | ||
|
|
6ad1776242 | ||
|
|
cbfd3ec7c4 | ||
|
|
f6765137e7 | ||
|
|
c24e04c8da | ||
|
|
327fec1877 | ||
|
|
c8524b95bc | ||
|
|
0ac3130b0d | ||
|
|
425ae1efae | ||
|
|
920d603a89 | ||
|
|
f5c91cc272 | ||
|
|
3cdc036858 | ||
|
|
29bfaa5c5b | ||
|
|
bec80e8091 | ||
|
|
2da9801bac | ||
|
|
16ebc6611b | ||
|
|
d5a44b913e | ||
|
|
a63a162c58 | ||
|
|
c29cd8cf3b | ||
|
|
04bf1be9d6 | ||
|
|
59054aa5ad | ||
|
|
1b8dde0115 | ||
|
|
9d0f6a3170 | ||
|
|
c16ed0aa4c | ||
|
|
3d95b386c6 | ||
|
|
a3f5a654f2 | ||
|
|
801b1df304 | ||
|
|
2b8a071c6f | ||
|
|
00fae37f2d | ||
|
|
846404e959 | ||
|
|
fbcd160efd | ||
|
|
196185dd73 | ||
|
|
affb996769 | ||
|
|
418abbcefa | ||
|
|
00867a13f6 | ||
|
|
e096c7e2fd | ||
|
|
69341eab47 | ||
|
|
082727ebb6 | ||
|
|
75a4f8b806 | ||
|
|
2eaa1c4649 | ||
|
|
199a10d093 | ||
|
|
4812fb18f6 | ||
|
|
940818d801 | ||
|
|
5182a66b19 | ||
|
|
b688aa294a | ||
|
|
27ad77c566 | ||
|
|
7677f76854 | ||
|
|
513a8d1383 | ||
|
|
be464a5986 | ||
|
|
c2042c3be8 | ||
|
|
8d480c8b6d | ||
|
|
82ff88f5d8 | ||
|
|
2535f210b3 | ||
|
|
cb0a2c234a | ||
|
|
5a73a75798 | ||
|
|
0ea756c42a | ||
|
|
7108dfa9c2 | ||
|
|
9fd8e81de2 | ||
|
|
566b40a2f8 | ||
|
|
ff6d8a4b30 | ||
|
|
f456ce3e93 |
60
.github/workflows/ci.yml
vendored
60
.github/workflows/ci.yml
vendored
@@ -71,6 +71,52 @@ jobs:
|
||||
LD_LIBRARY_PATH: ${{env.HOME}}/BUILD_${{matrix.arch_name}}/INSTALL/lib:${{env.HOME}}/BUILD_${{matrix.arch_name}}/INSTALL/lib64
|
||||
run: meson test -C build --verbose
|
||||
|
||||
Windows:
|
||||
runs-on: windows-2022
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup python 3.10
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Install packages
|
||||
run:
|
||||
choco install pkgconfiglite ninja
|
||||
|
||||
- name: Install python modules
|
||||
run: pip3 install meson
|
||||
|
||||
- name: Setup MSVC compiler
|
||||
uses: bus1/cabuild/action/msdevshell@v1
|
||||
with:
|
||||
architecture: x64
|
||||
|
||||
- name: Install dependencies
|
||||
uses: kiwix/kiwix-build/actions/dl_deps_archive@main
|
||||
with:
|
||||
target_platform: win-x86_64-static
|
||||
|
||||
- name: Compile
|
||||
shell: cmd
|
||||
run: |
|
||||
set PKG_CONFIG_PATH=%cd%\BUILD_win-amd64\INSTALL\lib\pkgconfig
|
||||
set CPPFLAGS=-I%cd%\BUILD_win-amd64\INSTALL\include
|
||||
meson.exe setup . build -Dwerror=false --default-library=static --buildtype=debug
|
||||
cd build
|
||||
ninja.exe
|
||||
|
||||
- name: Test
|
||||
shell: cmd
|
||||
run: |
|
||||
cd build
|
||||
meson.exe test --verbose
|
||||
env:
|
||||
WAIT_TIME_FACTOR_TEST: 10
|
||||
|
||||
Linux:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -80,8 +126,6 @@ jobs:
|
||||
- linux-x86_64-dyn
|
||||
- android-arm
|
||||
- android-arm64
|
||||
- win32-static
|
||||
- win32-dyn
|
||||
include:
|
||||
- target: linux-x86_64-static
|
||||
image_variant: focal
|
||||
@@ -107,18 +151,6 @@ jobs:
|
||||
arch_name: aarch64-linux-android
|
||||
run_test: false
|
||||
coverage: false
|
||||
- target: win32-static
|
||||
image_variant: f35
|
||||
lib_postfix: '64'
|
||||
arch_name: i686-w64-mingw32
|
||||
run_test: false
|
||||
coverage: false
|
||||
- target: win32-dyn
|
||||
image_variant: f35
|
||||
lib_postfix: '64'
|
||||
arch_name: i686-w64-mingw32
|
||||
run_test: false
|
||||
coverage: false
|
||||
env:
|
||||
HOME: /home/runner
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
52
.github/workflows/package.yml
vendored
52
.github/workflows/package.yml
vendored
@@ -15,11 +15,16 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
distro:
|
||||
- debian-unstable
|
||||
# - debian-unstable
|
||||
# - debian-trixie
|
||||
# - debian-bookworm
|
||||
# - debian-bullseye
|
||||
- ubuntu-noble
|
||||
- ubuntu-jammy
|
||||
- ubuntu-focal
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# Determine which PPA we should upload to
|
||||
- name: PPA
|
||||
@@ -34,18 +39,47 @@ jobs:
|
||||
env:
|
||||
REF: ${{ github.ref }}
|
||||
|
||||
- uses: legoktm/gh-action-auto-dch@master
|
||||
- uses: legoktm/gh-action-auto-dch@main
|
||||
with:
|
||||
fullname: Kiwix builder
|
||||
email: release+launchpad@kiwix.org
|
||||
distro: ${{ matrix.distro }}
|
||||
|
||||
- uses: legoktm/gh-action-build-deb@debian-unstable
|
||||
if: matrix.distro == 'debian-unstable'
|
||||
name: Build package for debian-unstable
|
||||
id: build-debian-unstable
|
||||
# - uses: legoktm/gh-action-build-deb@debian-unstable
|
||||
# if: matrix.distro == 'debian-unstable'
|
||||
# name: Build package for debian-unstable
|
||||
# id: build-debian-unstable
|
||||
# with:
|
||||
# args: --no-sign
|
||||
#
|
||||
# - uses: legoktm/gh-action-build-deb@b47978ba8498dc8b8153cc3b5f99a5fc1afa5de1 # pin@debian-trixie
|
||||
# if: matrix.distro == 'debian-trixie'
|
||||
# name: Build package for debian-trixie
|
||||
# id: build-debian-trixie
|
||||
# with:
|
||||
# args: --no-sign
|
||||
#
|
||||
# - uses: legoktm/gh-action-build-deb@1f4e86a6bb34aaad388167eaf5eb85d553935336 # pin@debian-bookworm
|
||||
# if: matrix.distro == 'debian-bookworm'
|
||||
# name: Build package for debian-bookworm
|
||||
# id: build-debian-bookworm
|
||||
# with:
|
||||
# args: --no-sign
|
||||
#
|
||||
# - uses: legoktm/gh-action-build-deb@084b4263209252ec80a75d2c78a586192c17f18d # pin@debian-bullseye
|
||||
# if: matrix.distro == 'debian-bullseye'
|
||||
# name: Build package for debian-bullseye
|
||||
# id: build-debian-bullseye
|
||||
# with:
|
||||
# args: --no-sign
|
||||
|
||||
- uses: legoktm/gh-action-build-deb@9114a536498b65c40b932209b9833aa942bf108d # pin@ubuntu-noble
|
||||
if: matrix.distro == 'ubuntu-noble'
|
||||
name: Build package for ubuntu-noble
|
||||
id: build-ubuntu-noble
|
||||
with:
|
||||
args: --no-sign
|
||||
ppa: ${{ steps.ppa.outputs.ppa }}
|
||||
|
||||
- uses: legoktm/gh-action-build-deb@ubuntu-jammy
|
||||
if: matrix.distro == 'ubuntu-jammy'
|
||||
@@ -68,7 +102,7 @@ jobs:
|
||||
name: Packages for ${{ matrix.distro }}
|
||||
path: output
|
||||
|
||||
- uses: legoktm/gh-action-dput@master
|
||||
- uses: legoktm/gh-action-dput@main
|
||||
name: Upload dev package
|
||||
# Only upload on pushes to main
|
||||
if: github.event_name == 'push' && github.event.ref == 'refs/heads/main' && startswith(matrix.distro, 'ubuntu-')
|
||||
@@ -77,7 +111,7 @@ jobs:
|
||||
repository: ppa:kiwixteam/dev
|
||||
packages: output/*_source.changes
|
||||
|
||||
- uses: legoktm/gh-action-dput@master
|
||||
- uses: legoktm/gh-action-dput@main
|
||||
name: Upload release package
|
||||
if: github.event_name == 'release' && startswith(matrix.distro, 'ubuntu-')
|
||||
with:
|
||||
|
||||
17
debian/control
vendored
17
debian/control
vendored
@@ -3,13 +3,12 @@ Priority: optional
|
||||
Maintainer: Kiwix team <kiwix@kiwix.org>
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
meson,
|
||||
pkg-config,
|
||||
libzim-dev (>= 7.2.0~),
|
||||
pkgconf,
|
||||
libzim-dev (>= 9.0.0), libzim-dev (<< 10.0.0),
|
||||
libcurl4-gnutls-dev,
|
||||
libicu-dev,
|
||||
libgtest-dev,
|
||||
libkainjow-mustache-dev,
|
||||
liblzma-dev,
|
||||
libmicrohttpd-dev,
|
||||
libpugixml-dev,
|
||||
zlib1g-dev
|
||||
@@ -22,12 +21,13 @@ Package: libkiwix-dev
|
||||
Section: libdevel
|
||||
Architecture: any
|
||||
Multi-Arch: same
|
||||
Depends: libkiwix10 (= ${binary:Version}), ${misc:Depends}, python3,
|
||||
libzim-dev (>= 7.2.0~),
|
||||
Depends: libkiwix14 (= ${binary:Version}), ${misc:Depends}, python3,
|
||||
libzim-dev (>= 9.0.0), libzim-dev (<< 10.0.0),
|
||||
libicu-dev,
|
||||
libpugixml-dev,
|
||||
libcurl4-gnutls-dev,
|
||||
libmicrohttpd-dev
|
||||
libmicrohttpd-dev,
|
||||
zlib1g-dev
|
||||
Description: library of common code for Kiwix (development)
|
||||
Kiwix is an offline Wikipedia reader. libkiwix provides the
|
||||
software core for Kiwix, and contains the code shared by all
|
||||
@@ -35,11 +35,12 @@ Description: library of common code for Kiwix (development)
|
||||
.
|
||||
This package contains development files.
|
||||
|
||||
Package: libkiwix10
|
||||
Package: libkiwix14
|
||||
Architecture: any
|
||||
Multi-Arch: same
|
||||
Depends: ${shlibs:Depends}, ${misc:Depends}, aria2
|
||||
Conflicts: libkiwix0, libkiwix3, libkiwix9
|
||||
Conflicts: libkiwix0, libkiwix3, libkiwix9, libkiwix10, libkiwix11, libkiwix12, libkiwix13
|
||||
Replaces: libkiwix0, libkiwix3, libkiwix9, libkiwix10, libkiwix11, libkiwix12, libkiwix13
|
||||
Description: library of common code for Kiwix
|
||||
Kiwix is an offline Wikipedia reader. libkiwix provides the
|
||||
software core for Kiwix, and contains the code shared by all
|
||||
|
||||
@@ -172,7 +172,12 @@ class Downloader
|
||||
typedef std::vector<std::pair<std::string, std::string>> Options;
|
||||
|
||||
public: // functions
|
||||
Downloader();
|
||||
/*
|
||||
* Create a new Downloader object.
|
||||
*
|
||||
* @param sessionFileDir: The directory where aria2 will store its session file.
|
||||
*/
|
||||
explicit Downloader(std::string sessionFileDir);
|
||||
virtual ~Downloader();
|
||||
|
||||
void close();
|
||||
@@ -191,10 +196,11 @@ class Downloader
|
||||
* User should call `update` on the returned `Download` to have an accurate status.
|
||||
*
|
||||
* @param uri: The uri of the thing to download.
|
||||
* @param downloadDir: The download directory where the thing should be stored (takes precedence over any "dir" in `options`).
|
||||
* @param options: A series of pair <option_name, option_value> to pass to aria.
|
||||
* @return: The newly created Download.
|
||||
*/
|
||||
std::shared_ptr<Download> startDownload(const std::string& uri, const Options& options = {});
|
||||
std::shared_ptr<Download> startDownload(const std::string& uri, const std::string& downloadDir, Options options = {});
|
||||
|
||||
/**
|
||||
* Get a download corrsponding to a download id (did)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2022 Veloman Yunkan <veloman.yunkan@gmail.com>
|
||||
* Copyright 2024 Veloman Yunkan <veloman.yunkan@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -17,29 +17,16 @@
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef KIWIX_SERVER_I18N
|
||||
#define KIWIX_SERVER_I18N
|
||||
#ifndef KIWIX_I18N
|
||||
#define KIWIX_I18N
|
||||
|
||||
#include <tools.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <mustache.hpp>
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
struct I18nString {
|
||||
const char* const key;
|
||||
const char* const value;
|
||||
};
|
||||
|
||||
struct I18nStringTable {
|
||||
const char* const lang;
|
||||
const size_t entryCount;
|
||||
const I18nString* const entries;
|
||||
|
||||
const char* get(const std::string& key) const;
|
||||
};
|
||||
std::string getTranslatedString(const std::string& lang, const std::string& key);
|
||||
|
||||
namespace i18n
|
||||
{
|
||||
@@ -69,28 +56,6 @@ private:
|
||||
const std::string m_lang;
|
||||
};
|
||||
|
||||
class GetTranslatedStringWithMsgId
|
||||
{
|
||||
typedef kainjow::mustache::basic_data<std::string> MustacheString;
|
||||
typedef std::pair<std::string, MustacheString> MsgIdAndTranslation;
|
||||
|
||||
public:
|
||||
explicit GetTranslatedStringWithMsgId(const std::string& lang) : m_lang(lang) {}
|
||||
|
||||
MsgIdAndTranslation operator()(const std::string& key) const
|
||||
{
|
||||
return {key, getTranslatedString(m_lang, key)};
|
||||
}
|
||||
|
||||
MsgIdAndTranslation operator()(const std::string& key, const Parameters& params) const
|
||||
{
|
||||
return {key, expandParameterizedString(m_lang, key, params)};
|
||||
}
|
||||
|
||||
private:
|
||||
const std::string m_lang;
|
||||
};
|
||||
|
||||
} // namespace i18n
|
||||
|
||||
class ParameterizedMessage
|
||||
@@ -120,18 +85,8 @@ inline ParameterizedMessage nonParameterizedMessage(const std::string& msgId)
|
||||
return ParameterizedMessage(msgId, noParams);
|
||||
}
|
||||
|
||||
struct LangPreference
|
||||
{
|
||||
const std::string lang;
|
||||
const float preference;
|
||||
};
|
||||
|
||||
typedef std::vector<LangPreference> UserLangPreferences;
|
||||
|
||||
UserLangPreferences parseUserLanguagePreferences(const std::string& s);
|
||||
|
||||
std::string selectMostSuitableLanguage(const UserLangPreferences& prefs);
|
||||
std::string translateBookCategory(const std::string& lang, const std::string& category);
|
||||
|
||||
} // namespace kiwix
|
||||
|
||||
#endif // KIWIX_SERVER_I18N
|
||||
#endif // KIWIX_I18N
|
||||
@@ -10,7 +10,8 @@ headers = [
|
||||
'kiwixserve.h',
|
||||
'name_mapper.h',
|
||||
'tools.h',
|
||||
'version.h'
|
||||
'version.h',
|
||||
'i18n.h'
|
||||
]
|
||||
|
||||
install_headers(headers, subdir:'kiwix')
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include "common.h"
|
||||
#include "tools.h"
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
@@ -52,7 +52,7 @@ namespace kiwix
|
||||
void stop();
|
||||
|
||||
void setRoot(const std::string& root);
|
||||
void setAddress(const std::string& addr) { m_addr = addr; }
|
||||
void setAddress(const std::string& addr);
|
||||
void setPort(int port) { m_port = port; }
|
||||
void setNbThreads(int threads) { m_nbThreads = threads; }
|
||||
void setMultiZimSearchLimit(unsigned int limit) { m_multizimSearchLimit = limit; }
|
||||
@@ -64,15 +64,15 @@ namespace kiwix
|
||||
void setBlockExternalLinks(bool blockExternalLinks)
|
||||
{ m_blockExternalLinks = blockExternalLinks; }
|
||||
void setIpMode(IpMode mode) { m_ipMode = mode; }
|
||||
int getPort();
|
||||
std::string getAddress();
|
||||
int getPort() const;
|
||||
IpAddress getAddress() const;
|
||||
IpMode getIpMode() const;
|
||||
|
||||
protected:
|
||||
std::shared_ptr<Library> mp_library;
|
||||
std::shared_ptr<NameMapper> mp_nameMapper;
|
||||
std::string m_root = "";
|
||||
std::string m_addr = "";
|
||||
IpAddress m_addr;
|
||||
std::string m_indexTemplateString = "";
|
||||
int m_port = 80;
|
||||
int m_nbThreads = 1;
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <cstdint>
|
||||
#include "common.h"
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
@@ -32,6 +33,12 @@ struct IpAddress
|
||||
{
|
||||
std::string addr; // IPv4 address
|
||||
std::string addr6; // IPv6 address
|
||||
|
||||
bool empty() const { return addr.empty() && addr6.empty(); }
|
||||
bool empty4() const { return addr.empty(); }
|
||||
bool empty6() const { return addr6.empty(); }
|
||||
bool valid4(IpMode mode) const { return (mode == IpMode::ipv4 || mode == IpMode::all) && !this->empty4(); }
|
||||
bool valid6(IpMode mode) const { return (mode == IpMode::ipv6 || mode == IpMode::all) && !this->empty6(); }
|
||||
};
|
||||
|
||||
typedef std::pair<std::string, std::string> LangNameCodePair;
|
||||
@@ -45,32 +52,6 @@ typedef std::vector<std::string> FeedCategories;
|
||||
*/
|
||||
std::string getCurrentDirectory();
|
||||
|
||||
/**
|
||||
* Return the data directory
|
||||
*
|
||||
* The data directory is the default directory where downloaded files
|
||||
* should be saved (it can be overriden via the options parameter of
|
||||
* `kiwix::Downloader::startDownload()`).
|
||||
*
|
||||
* Its path can vary and is determined as follows:
|
||||
*
|
||||
* * `$KIWIX_DATA_DIR` if `$KIWIX_DATA_DIR` environment variable set, *otherwise...*
|
||||
* * On Windows:
|
||||
*
|
||||
* * `$APPDATA/kiwix` if environment variable `$APPDATA` set, *otherwise...*
|
||||
* * `$USERPROFILE/kiwix` if environment variable `$USERPROFILE` set, *otherwise...*
|
||||
*
|
||||
* * On other Operating Systems:
|
||||
*
|
||||
* * `$XDG_DATA_HOME/kiwix` if environment variable `$XDG_DATA_HOME` set, *otherwise...*
|
||||
* * `$HOME/.local/share/kiwx` if environment variable `$HOME` set, *otherwise...*
|
||||
*
|
||||
* * Current working directory.
|
||||
*
|
||||
* @return the path of the data directory (UTF-8 encoded)
|
||||
*/
|
||||
std::string getDataDirectory();
|
||||
|
||||
/** Return the path of the executable
|
||||
*
|
||||
* Some application may be packaged in auto extractible archive (Appimage) and the
|
||||
@@ -242,7 +223,7 @@ std::map<std::string, std::string> getNetworkInterfaces();
|
||||
/** Provides the best IP address
|
||||
* This function provides the best IP address from the list given by getNetworkInterfacesIPv4Or6()
|
||||
*/
|
||||
std::string getBestPublicIp(bool ipv6);
|
||||
IpAddress getBestPublicIps(IpMode mode);
|
||||
|
||||
/** Provides the best IPv4 adddress
|
||||
* Equivalent to getBestPublicIp(false). Provided for backward compatibility
|
||||
@@ -250,6 +231,15 @@ std::string getBestPublicIp(bool ipv6);
|
||||
*/
|
||||
std::string getBestPublicIp();
|
||||
|
||||
/** Checks if IP address is available
|
||||
*
|
||||
* Check if the given IP address is configured on any network interface of the system
|
||||
*
|
||||
* @param addr the IP address to check.
|
||||
* @return true if the IP address is available on the system.
|
||||
*/
|
||||
bool ipAvailable(const IpAddress& addr);
|
||||
|
||||
/** Converts file size to human readable format.
|
||||
*
|
||||
* This function will convert a number to its equivalent size using units.
|
||||
@@ -284,12 +274,12 @@ FeedCategories readCategoriesFromFeed(const std::string& content);
|
||||
std::string getLanguageSelfName(const std::string& lang);
|
||||
|
||||
/**
|
||||
* Retrieve the translation corresponding to key in language lang
|
||||
* Slugifies the filename by converting any characters reserved by the operating
|
||||
* system to '_'. Note filename is only the file name and not a path.
|
||||
*
|
||||
* @param lang ISO 639-3 language code.
|
||||
* @param key translation key string
|
||||
* @return translated key
|
||||
* @param filename Valid UTF-8 encoded file name string.
|
||||
* @return slugified string.
|
||||
*/
|
||||
std::string getTranslatedString(const std::string& lang, const std::string& key);
|
||||
std::string getSlugifiedFileName(const std::string& filename);
|
||||
}
|
||||
#endif // KIWIX_TOOLS_H
|
||||
|
||||
10
kiwix.pc.in
10
kiwix.pc.in
@@ -1,10 +0,0 @@
|
||||
prefix=@prefix@
|
||||
libdir=${prefix}/lib64
|
||||
includedir=${prefix}/include
|
||||
|
||||
Name: libkiwix
|
||||
Description: A library that contains a lot of things used by used by other kiwix programs
|
||||
Version: @version@
|
||||
Requires: @requires@
|
||||
Libs: -L${libdir} -lkiwix @extra_libs@
|
||||
Cflags: -I${includedir}/ @extra_cflags@
|
||||
28
meson.build
28
meson.build
@@ -1,5 +1,5 @@
|
||||
project('libkiwix', 'cpp',
|
||||
version : '13.1.0',
|
||||
version : '14.0.0',
|
||||
license : 'GPLv3+',
|
||||
default_options : ['c_std=c11', 'cpp_std=c++17', 'werror=true'])
|
||||
|
||||
@@ -35,9 +35,10 @@ else
|
||||
error('Cannot found header mustache.hpp')
|
||||
endif
|
||||
|
||||
libzim_dep = dependency('libzim', version : '>=8.1.0', static:static_deps)
|
||||
libzim_dep = dependency('libzim', version:['>=9.0.0', '<10.0.0'], static:static_deps)
|
||||
|
||||
if not compiler.has_header_symbol('zim/zim.h', 'LIBZIM_WITH_XAPIAN', dependencies: libzim_dep)
|
||||
error('Libzim seems to be compiled without xapian. Xapian support is mandatory.')
|
||||
error('Libzim seems to be compiled without Xapian. Xapian support is mandatory.')
|
||||
endif
|
||||
|
||||
|
||||
@@ -73,17 +74,10 @@ if get_option('doc')
|
||||
subdir('docs')
|
||||
endif
|
||||
|
||||
pkg_requires = ['libzim', 'icu-i18n', 'pugixml', 'libcurl', 'libmicrohttpd', 'xapian-core']
|
||||
|
||||
pkg_conf = configuration_data()
|
||||
pkg_conf.set('prefix', get_option('prefix'))
|
||||
pkg_conf.set('requires', ' '.join(pkg_requires))
|
||||
pkg_conf.set('extra_libs', ' '.join(extra_libs))
|
||||
pkg_conf.set('extra_cflags', extra_cflags)
|
||||
pkg_conf.set('version', meson.project_version())
|
||||
configure_file(output : 'kiwix.pc',
|
||||
configuration : pkg_conf,
|
||||
input : 'kiwix.pc.in',
|
||||
install_dir: get_option('libdir')+'/pkgconfig'
|
||||
)
|
||||
|
||||
pkg_mod = import('pkgconfig')
|
||||
pkg_mod.generate(libraries : [libkiwix] + extra_libs,
|
||||
version : meson.project_version(),
|
||||
name : 'libkiwix',
|
||||
filebase : 'libkiwix',
|
||||
description : 'A library that contains useful primitives that Kiwix readers have in common',
|
||||
extra_cflags: extra_cflags)
|
||||
|
||||
@@ -61,7 +61,7 @@ lang_table_entry_cxx_template = '''
|
||||
|
||||
cxxfile_template = '''// This file is automatically generated. Do not modify it.
|
||||
|
||||
#include "server/i18n.h"
|
||||
#include "server/i18n_utils.h"
|
||||
|
||||
namespace kiwix {
|
||||
namespace i18n {
|
||||
|
||||
@@ -61,6 +61,32 @@ resource_decl_template = """{namespaces_open}
|
||||
extern const std::string {identifier};
|
||||
{namespaces_close}"""
|
||||
|
||||
BINARY_RESOURCE_EXTENSIONS = {'.ico', '.png', '.ttf'}
|
||||
|
||||
TEXT_RESOURCE_EXTENSIONS = {
|
||||
'.css',
|
||||
'.html',
|
||||
'.js',
|
||||
'.json',
|
||||
'.svg',
|
||||
'.tmpl',
|
||||
'.webmanifest',
|
||||
'.xml',
|
||||
}
|
||||
|
||||
if not BINARY_RESOURCE_EXTENSIONS.isdisjoint(TEXT_RESOURCE_EXTENSIONS):
|
||||
raise RuntimeError(f"The following file type extensions are declared to be both binary and text: {BINARY_RESOURCE_EXTENSIONS.intersection(TEXT_RESOURCE_EXTENSIONS)}")
|
||||
|
||||
def is_binary_resource(filename):
|
||||
_, extension = os.path.splitext(filename)
|
||||
is_binary = extension in BINARY_RESOURCE_EXTENSIONS
|
||||
is_text = extension in TEXT_RESOURCE_EXTENSIONS
|
||||
if not is_binary and not is_text:
|
||||
# all file type extensions of static resources must be listed
|
||||
# in either BINARY_RESOURCE_EXTENSIONS or TEXT_RESOURCE_EXTENSIONS
|
||||
raise RuntimeError(f"Unknown file type extension: {extension}")
|
||||
return is_binary
|
||||
|
||||
class Resource:
|
||||
def __init__(self, base_dirs, filename, cacheid=None):
|
||||
filename = filename
|
||||
@@ -72,6 +98,8 @@ class Resource:
|
||||
try:
|
||||
with open(os.path.join(base_dir, filename), 'rb') as f:
|
||||
self.data = f.read()
|
||||
if not is_binary_resource(filename):
|
||||
self.data = self.data.replace(b"\r\n", b"\n")
|
||||
found = True
|
||||
break
|
||||
except FileNotFoundError:
|
||||
|
||||
@@ -55,18 +55,15 @@ void pauseAnyActiveDownloads(const std::string& ariaSessionFilePath)
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
Aria2::Aria2():
|
||||
Aria2::Aria2(std::string sessionFileDir):
|
||||
mp_aria(nullptr),
|
||||
m_port(42042),
|
||||
m_secret(getNewRpcSecret())
|
||||
{
|
||||
m_downloadDir = getDataDirectory();
|
||||
makeDirectory(m_downloadDir);
|
||||
std::vector<const char*> callCmd;
|
||||
|
||||
std::string rpc_port = "--rpc-listen-port=" + to_string(m_port);
|
||||
std::string download_dir = "--dir=" + getDataDirectory();
|
||||
std::string session_file = appendToDirectory(getDataDirectory(), "kiwix.session");
|
||||
std::string session_file = appendToDirectory(sessionFileDir, "kiwix.session");
|
||||
pauseAnyActiveDownloads(session_file);
|
||||
std::string session = "--save-session=" + session_file;
|
||||
std::string inputFile = "--input-file=" + session_file;
|
||||
@@ -94,7 +91,6 @@ Aria2::Aria2():
|
||||
callCmd.push_back("--enable-rpc");
|
||||
callCmd.push_back(rpc_secret.c_str());
|
||||
callCmd.push_back(rpc_port.c_str());
|
||||
callCmd.push_back(download_dir.c_str());
|
||||
if (fileReadable(session_file)) {
|
||||
callCmd.push_back(inputFile.c_str());
|
||||
}
|
||||
|
||||
@@ -22,11 +22,10 @@ class Aria2
|
||||
std::unique_ptr<Subprocess> mp_aria;
|
||||
int m_port;
|
||||
std::string m_secret;
|
||||
std::string m_downloadDir;
|
||||
std::string doRequest(const MethodCall& methodCall);
|
||||
|
||||
public:
|
||||
Aria2();
|
||||
explicit Aria2(std::string sessionFileDir);
|
||||
virtual ~Aria2() = default;
|
||||
void close();
|
||||
|
||||
|
||||
@@ -125,8 +125,8 @@ void Download::cancelDownload()
|
||||
}
|
||||
|
||||
/* Constructor */
|
||||
Downloader::Downloader() :
|
||||
mp_aria(new Aria2())
|
||||
Downloader::Downloader(std::string sessionFileDir) :
|
||||
mp_aria(new Aria2(sessionFileDir))
|
||||
{
|
||||
try {
|
||||
for (auto gid : mp_aria->tellWaiting()) {
|
||||
@@ -209,9 +209,13 @@ bool downloadCanBeReused(const Download& d,
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
std::shared_ptr<Download> Downloader::startDownload(const std::string& uri, const Options& options)
|
||||
std::shared_ptr<Download> Downloader::startDownload(const std::string& uri, const std::string& downloadDir, Options options)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_lock);
|
||||
options.erase(std::remove_if(options.begin(), options.end(), [](const auto& option) {
|
||||
return option.first == "dir";
|
||||
}), options.end());
|
||||
options.push_back({"dir", downloadDir});
|
||||
for (auto& p: m_knownDownloads) {
|
||||
auto& d = p.second;
|
||||
if ( downloadCanBeReused(*d, uri, options) )
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "tools/otherTools.h"
|
||||
#include "tools.h"
|
||||
#include "tools/regexTools.h"
|
||||
#include "server/i18n.h"
|
||||
#include "server/i18n_utils.h"
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
@@ -77,7 +77,7 @@ std::string HTMLDumper::dumpPlainHTML(kiwix::Filter filter) const
|
||||
const auto tags = bookObj.getTags();
|
||||
const auto downloadAvailable = (bookObj.getUrl() != "");
|
||||
std::string faviconAttr = "style=background-image:url(" + bookIconUrl + ")";
|
||||
|
||||
|
||||
booksData.push_back(kainjow::mustache::object{
|
||||
{"id", contentId},
|
||||
{"title", bookTitle},
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
#include "libkiwix-resources.h"
|
||||
#include "tools/stringTools.h"
|
||||
|
||||
#include "server/i18n.h"
|
||||
#include "server/i18n_utils.h"
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
@@ -75,12 +75,25 @@ void Server::setRoot(const std::string& root)
|
||||
}
|
||||
}
|
||||
|
||||
int Server::getPort()
|
||||
void Server::setAddress(const std::string& addr)
|
||||
{
|
||||
if (addr.empty())
|
||||
return;
|
||||
|
||||
if (addr.find(':') != std::string::npos) { // IPv6
|
||||
for (char ch : addr) if (ch == '[' or ch == ']') ch = '\0'; // Clean possible brackets
|
||||
m_addr.addr6 = addr;
|
||||
} else {
|
||||
m_addr.addr = addr;
|
||||
}
|
||||
}
|
||||
|
||||
int Server::getPort() const
|
||||
{
|
||||
return mp_server->getPort();
|
||||
}
|
||||
|
||||
std::string Server::getAddress()
|
||||
IpAddress Server::getAddress() const
|
||||
{
|
||||
return mp_server->getAddress();
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "i18n.h"
|
||||
#include "i18n_utils.h"
|
||||
|
||||
#include "tools/otherTools.h"
|
||||
|
||||
@@ -193,4 +193,13 @@ std::string selectMostSuitableLanguage(const UserLangPreferences& prefs)
|
||||
return bestLangSoFar;
|
||||
}
|
||||
|
||||
std::string translateBookCategory(const std::string& lang, const std::string& category)
|
||||
{
|
||||
try {
|
||||
return getTranslatedString(lang, "book-category." + category);
|
||||
} catch (...) {
|
||||
return category;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace kiwix
|
||||
|
||||
83
src/server/i18n_utils.h
Normal file
83
src/server/i18n_utils.h
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2022 Veloman Yunkan <veloman.yunkan@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef KIWIX_SERVER_I18N_UTILS
|
||||
#define KIWIX_SERVER_I18N_UTILS
|
||||
|
||||
#include "i18n.h"
|
||||
#include <mustache.hpp>
|
||||
|
||||
namespace kiwix
|
||||
{
|
||||
|
||||
struct I18nString {
|
||||
const char* const key;
|
||||
const char* const value;
|
||||
};
|
||||
|
||||
struct I18nStringTable {
|
||||
const char* const lang;
|
||||
const size_t entryCount;
|
||||
const I18nString* const entries;
|
||||
|
||||
const char* get(const std::string& key) const;
|
||||
};
|
||||
|
||||
namespace i18n
|
||||
{
|
||||
|
||||
class GetTranslatedStringWithMsgId
|
||||
{
|
||||
typedef kainjow::mustache::basic_data<std::string> MustacheString;
|
||||
typedef std::pair<std::string, MustacheString> MsgIdAndTranslation;
|
||||
|
||||
public:
|
||||
explicit GetTranslatedStringWithMsgId(const std::string& lang) : m_lang(lang) {}
|
||||
|
||||
MsgIdAndTranslation operator()(const std::string& key) const
|
||||
{
|
||||
return {key, getTranslatedString(m_lang, key)};
|
||||
}
|
||||
|
||||
MsgIdAndTranslation operator()(const std::string& key, const Parameters& params) const
|
||||
{
|
||||
return {key, expandParameterizedString(m_lang, key, params)};
|
||||
}
|
||||
|
||||
private:
|
||||
const std::string m_lang;
|
||||
};
|
||||
|
||||
} // namespace i18n
|
||||
|
||||
struct LangPreference
|
||||
{
|
||||
const std::string lang;
|
||||
const float preference;
|
||||
};
|
||||
|
||||
typedef std::vector<LangPreference> UserLangPreferences;
|
||||
|
||||
UserLangPreferences parseUserLanguagePreferences(const std::string& s);
|
||||
|
||||
std::string selectMostSuitableLanguage(const UserLangPreferences& prefs);
|
||||
|
||||
} // namespace kiwix
|
||||
|
||||
#endif // KIWIX_SERVER_I18N_UTILS
|
||||
@@ -54,7 +54,7 @@ extern "C" {
|
||||
#include "search_renderer.h"
|
||||
#include "opds_dumper.h"
|
||||
#include "html_dumper.h"
|
||||
#include "i18n.h"
|
||||
#include "i18n_utils.h"
|
||||
|
||||
#include <zim/uuid.h>
|
||||
#include <zim/error.h>
|
||||
@@ -407,7 +407,7 @@ public:
|
||||
|
||||
InternalServer::InternalServer(LibraryPtr library,
|
||||
std::shared_ptr<NameMapper> nameMapper,
|
||||
std::string addr,
|
||||
IpAddress addr,
|
||||
int port,
|
||||
std::string root,
|
||||
int nbThreads,
|
||||
@@ -462,18 +462,23 @@ bool InternalServer::start() {
|
||||
sockAddr6.sin6_port = htons(m_port);
|
||||
|
||||
if (m_addr.empty()) {
|
||||
if (0 != INADDR_ANY) {
|
||||
sockAddr6.sin6_addr = in6addr_any;
|
||||
sockAddr4.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
}
|
||||
m_addr = kiwix::getBestPublicIp(m_ipMode == IpMode::ipv6 || m_ipMode == IpMode::all);
|
||||
sockAddr6.sin6_addr = in6addr_any;
|
||||
sockAddr4.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
m_addr = kiwix::getBestPublicIps(m_ipMode);
|
||||
} else {
|
||||
bool ipv6 = inet_pton(AF_INET6, m_addr.c_str(), &(sockAddr6.sin6_addr.s6_addr)) == 1;
|
||||
bool ipv4 = inet_pton(AF_INET, m_addr.c_str(), &(sockAddr4.sin_addr.s_addr)) == 1;
|
||||
bool ipv6 = inet_pton(AF_INET6, m_addr.addr6.c_str(), &(sockAddr6.sin6_addr.s6_addr)) == 1;
|
||||
bool ipv4 = inet_pton(AF_INET, m_addr.addr.c_str(), &(sockAddr4.sin_addr.s_addr)) == 1;
|
||||
|
||||
if (ipv6){
|
||||
m_ipMode = IpMode::all;
|
||||
m_ipMode = IpMode::ipv6;
|
||||
} else if (!ipv4) {
|
||||
std::cerr << "Ip address " << m_addr << " is not a valid ip address" << std::endl;
|
||||
const std::string& address = m_addr.addr.empty() ? m_addr.addr6 : m_addr.addr;
|
||||
std::cerr << "IP address " << address << " is not a valid IP address" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ipAvailable(m_addr)) {
|
||||
std::cerr << "IP address " << (m_addr.addr.empty() ? m_addr.addr6 : m_addr.addr) << " is not available on this system" << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -959,7 +964,7 @@ std::unique_ptr<Response> InternalServer::handle_search_request(const RequestCon
|
||||
} catch(std::runtime_error& e) {
|
||||
// Searcher->search will throw a runtime error if there is no valid xapian database to do the search.
|
||||
// (in case of zim file not containing a index)
|
||||
const auto cssUrl = renderUrl(m_root, RESOURCE::templates::url_of_search_results_css);
|
||||
const auto cssUrl = renderUrl(m_root, RESOURCE::templates::url_of_search_results_css_tmpl);
|
||||
HTTPErrorResponse response(request, MHD_HTTP_NOT_FOUND,
|
||||
"fulltext-search-unavailable",
|
||||
"404-page-heading",
|
||||
|
||||
@@ -27,6 +27,7 @@ extern "C" {
|
||||
|
||||
#include "library.h"
|
||||
#include "name_mapper.h"
|
||||
#include "tools.h"
|
||||
|
||||
#include <zim/search.h>
|
||||
#include <zim/suggestion.h>
|
||||
@@ -94,7 +95,7 @@ class InternalServer {
|
||||
public:
|
||||
InternalServer(LibraryPtr library,
|
||||
std::shared_ptr<NameMapper> nameMapper,
|
||||
std::string addr,
|
||||
IpAddress addr,
|
||||
int port,
|
||||
std::string root,
|
||||
int nbThreads,
|
||||
@@ -117,8 +118,8 @@ class InternalServer {
|
||||
void** cont_cls);
|
||||
bool start();
|
||||
void stop();
|
||||
std::string getAddress() { return m_addr; }
|
||||
int getPort() { return m_port; }
|
||||
IpAddress getAddress() const { return m_addr; }
|
||||
int getPort() const { return m_port; }
|
||||
IpMode getIpMode() const { return m_ipMode; }
|
||||
|
||||
private: // functions
|
||||
@@ -166,7 +167,7 @@ class InternalServer {
|
||||
typedef ConcurrentCache<std::string, std::shared_ptr<LockableSuggestionSearcher>> SuggestionSearcherCache;
|
||||
|
||||
private: // data
|
||||
std::string m_addr;
|
||||
IpAddress m_addr;
|
||||
int m_port;
|
||||
std::string m_root; // URI-encoded
|
||||
std::string m_rootPrefixOfDecodedURL; // URI-decoded
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
#include <cctype>
|
||||
|
||||
#include "tools/stringTools.h"
|
||||
#include "i18n.h"
|
||||
#include "i18n_utils.h"
|
||||
|
||||
namespace kiwix {
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
#include <mustache.hpp>
|
||||
#include "byte_range.h"
|
||||
#include "etag.h"
|
||||
#include "i18n.h"
|
||||
#include "i18n_utils.h"
|
||||
|
||||
#include <zim/item.h>
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@ void fillLanguagesMap()
|
||||
const kiwix::ICULanguageInfo lang(*icuLangPtr);
|
||||
iso639_3.insert({lang.iso3Code(), lang.selfName()});
|
||||
}
|
||||
iso639_3.erase("mul");
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
@@ -211,40 +211,48 @@ std::map<std::string, std::string> getNetworkInterfaces() {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
std::string getBestPublicIp(bool ipv6) {
|
||||
IpAddress bestPublicIp = IpAddress{"127.0.0.1","::1"};
|
||||
IpAddress getBestPublicIps(IpMode mode) {
|
||||
IpAddress bestPublicIps = IpAddress{"127.0.0.1","::1"};
|
||||
std::map<std::string, IpAddress> interfaces = getNetworkInterfacesIPv4Or6();
|
||||
|
||||
#ifndef _WIN32
|
||||
const char* const prioritizedNames[] =
|
||||
{ "eth0", "eth1", "wlan0", "wlan1", "en0", "en1" };
|
||||
const char* const prioritizedNames[] = { "eth0", "eth1", "wlan0", "wlan1", "en0", "en1" };
|
||||
for(auto name: prioritizedNames) {
|
||||
auto it=interfaces.find(name);
|
||||
if(it != interfaces.end() && !(ipv6 && (*it).second.addr6.empty())) {
|
||||
bestPublicIp = (*it).second;
|
||||
break;
|
||||
}
|
||||
const auto it = interfaces.find(name);
|
||||
if(it == interfaces.end()) continue;
|
||||
const IpAddress& interfaceIps = (*it).second;
|
||||
if(!bestPublicIps.empty4() && interfaceIps.valid4(mode)) bestPublicIps.addr = interfaceIps.addr;
|
||||
if(!bestPublicIps.empty6() && interfaceIps.valid6(mode)) bestPublicIps.addr6 = interfaceIps.addr6;
|
||||
}
|
||||
#endif
|
||||
|
||||
const char* const prefixes[] = { "192.168", "172.16.", "10.0" };
|
||||
for(auto prefix : prefixes){
|
||||
for(auto& itr : interfaces) {
|
||||
std::string interfaceIp(itr.second.addr);
|
||||
if (interfaceIp.find(prefix) == 0 && !(ipv6 && itr.second.addr6.empty())) {
|
||||
bestPublicIp = itr.second;
|
||||
break;
|
||||
}
|
||||
const IpAddress& interfaceIps = itr.second;
|
||||
if(!bestPublicIps.empty4() && interfaceIps.valid4(mode) && interfaceIps.addr.find(prefix) == 0) bestPublicIps.addr = interfaceIps.addr;
|
||||
if(!bestPublicIps.empty6() && interfaceIps.valid6(mode)) bestPublicIps.addr6 = interfaceIps.addr6;
|
||||
}
|
||||
}
|
||||
return ipv6 ? bestPublicIp.addr6 : bestPublicIp.addr;
|
||||
}
|
||||
|
||||
return bestPublicIps;
|
||||
}
|
||||
|
||||
std::string getBestPublicIp()
|
||||
{
|
||||
return getBestPublicIp(false);
|
||||
return getBestPublicIps(IpMode::ipv4).addr;
|
||||
}
|
||||
|
||||
bool ipAvailable(const IpAddress& addr)
|
||||
{
|
||||
auto interfaces = kiwix::getNetworkInterfacesIPv4Or6();
|
||||
for (const auto& interface : interfaces) {
|
||||
IpAddress interfaceIps = interface.second;
|
||||
if (!interfaceIps.empty4() && interfaceIps.addr == addr.addr) return true;
|
||||
if (!interfaceIps.empty6() && interfaceIps.addr6 == addr.addr6) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace kiwix
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
#endif
|
||||
|
||||
#include "tools/stringTools.h"
|
||||
#include "server/i18n.h"
|
||||
#include "server/i18n_utils.h"
|
||||
#include "libkiwix-resources.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
@@ -320,16 +320,6 @@ bool kiwix::fileReadable(const std::string& path)
|
||||
#endif
|
||||
}
|
||||
|
||||
bool makeDirectory(const std::string& path)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
int status = _wmkdir(Utf8ToWide(path).c_str());
|
||||
#else
|
||||
int status = mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
|
||||
#endif
|
||||
return status == 0;
|
||||
}
|
||||
|
||||
std::string makeTmpDirectory()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
@@ -438,52 +428,6 @@ std::string kiwix::getCurrentDirectory()
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string kiwix::getDataDirectory()
|
||||
{
|
||||
// Try to get the dataDir from the `KIWIX_DATA_DIR` env var
|
||||
#ifdef _WIN32
|
||||
wchar_t* cDataDir = ::_wgetenv(L"KIWIX_DATA_DIR");
|
||||
if (cDataDir != nullptr) {
|
||||
return WideToUtf8(cDataDir);
|
||||
}
|
||||
#else
|
||||
char* cDataDir = ::getenv("KIWIX_DATA_DIR");
|
||||
if (cDataDir != nullptr) {
|
||||
return cDataDir;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Compute the dataDir from the user directory.
|
||||
std::string dataDir;
|
||||
#ifdef _WIN32
|
||||
cDataDir = ::_wgetenv(L"APPDATA");
|
||||
if (cDataDir == nullptr)
|
||||
cDataDir = ::_wgetenv(L"USERPROFILE");
|
||||
if (cDataDir != nullptr)
|
||||
dataDir = WideToUtf8(cDataDir);
|
||||
#else
|
||||
cDataDir = ::getenv("XDG_DATA_HOME");
|
||||
if (cDataDir != nullptr) {
|
||||
dataDir = cDataDir;
|
||||
} else {
|
||||
cDataDir = ::getenv("HOME");
|
||||
if (cDataDir != nullptr) {
|
||||
dataDir = cDataDir;
|
||||
dataDir = appendToDirectory(dataDir, ".local");
|
||||
dataDir = appendToDirectory(dataDir, "share");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (!dataDir.empty()) {
|
||||
dataDir = appendToDirectory(dataDir, "kiwix");
|
||||
makeDirectory(dataDir);
|
||||
return dataDir;
|
||||
}
|
||||
|
||||
// Let's use the currentDirectory
|
||||
return getCurrentDirectory();
|
||||
}
|
||||
|
||||
static std::map<std::string, std::string> extMimeTypes = {
|
||||
{ "html", "text/html"},
|
||||
{ "htm", "text/html"},
|
||||
|
||||
@@ -29,7 +29,6 @@ std::wstring Utf8ToWide(const std::string& str);
|
||||
|
||||
unsigned int getFileSize(const std::string& path);
|
||||
std::string getFileSizeAsString(const std::string& path);
|
||||
bool makeDirectory(const std::string& path);
|
||||
std::string makeTmpDirectory();
|
||||
bool copyFile(const std::string& sourcePath, const std::string& destPath);
|
||||
bool writeTextFile(const std::string& path, const std::string& content);
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <regex>
|
||||
|
||||
/* tell ICU where to find its dat file (tables) */
|
||||
void kiwix::loadICUExternalTables()
|
||||
@@ -256,7 +257,7 @@ std::string kiwix::urlDecode(const std::string& value, bool component)
|
||||
|
||||
// If there aren't enough characters left for this to be a
|
||||
// valid escape code, just use the character and move on
|
||||
if (it > value.end() - 3) {
|
||||
if (value.end() - it < 3) {
|
||||
os << *it;
|
||||
continue;
|
||||
}
|
||||
@@ -439,3 +440,13 @@ template<>
|
||||
std::string kiwix::extractFromString(const std::string& str) {
|
||||
return str;
|
||||
}
|
||||
|
||||
std::string kiwix::getSlugifiedFileName(const std::string& filename)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
const std::regex reservedCharsReg(R"([<>:"/\\|?*])");
|
||||
#else
|
||||
const std::regex reservedCharsReg("/");
|
||||
#endif
|
||||
return std::regex_replace(filename, reservedCharsReg, "_");
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ skin/magnet.png
|
||||
skin/feed.svg
|
||||
skin/langSelector.svg
|
||||
skin/download.png
|
||||
skin/download-white.svg
|
||||
skin/hash.png
|
||||
skin/search-icon.svg
|
||||
skin/iso6391To3.js
|
||||
@@ -37,7 +38,7 @@ templates/catalog_v2_entry.xml
|
||||
templates/catalog_v2_partial_entry.xml
|
||||
templates/catalog_v2_categories.xml
|
||||
templates/catalog_v2_languages.xml
|
||||
templates/url_of_search_results_css
|
||||
templates/url_of_search_results_css.tmpl
|
||||
templates/viewer_settings.js
|
||||
templates/no_js_library_page.html
|
||||
templates/no_js_download.html
|
||||
|
||||
6
static/skin/download-white.svg
Normal file
6
static/skin/download-white.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
|
||||
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<g id="SVGRepo_iconCarrier"> <g id="Interface / Download"> <path id="Vector" d="M6 21H18M12 3V17M12 17L17 12M12 17L7 12" stroke="#f6f5f4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </g> </g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 546 B |
@@ -16,5 +16,15 @@
|
||||
"search": "অনুসন্ধান",
|
||||
"welcome-to-kiwix-server": "কিউইক্স সার্ভারে স্বাগতম",
|
||||
"download-links-title": "বই ডাউনলোড করুন",
|
||||
"preview-book": "প্রাকদর্শন"
|
||||
"preview-book": "প্রাকদর্শন",
|
||||
"book-category.wikibooks": "উইকিবই",
|
||||
"book-category.wikinews": "উইকিসংবাদ",
|
||||
"book-category.wikipedia": "উইকিপিডিয়া",
|
||||
"book-category.wikiquote": "উইকিউক্তি",
|
||||
"book-category.wikisource": "উইকিসংকলন",
|
||||
"book-category.wikispecies": "উইকিপ্রজাতি",
|
||||
"book-category.wikiversity": "উইকিবিশ্ববিদ্যালয়",
|
||||
"book-category.wikivoyage": "উইকিভ্রমণ",
|
||||
"book-category.wiktionary": "উইকিঅভিধান",
|
||||
"book-category.other": "অন্যান্য"
|
||||
}
|
||||
|
||||
@@ -51,8 +51,8 @@
|
||||
, "torrent-download-link-text": "BitTorrent"
|
||||
, "torrent-download-alt-text": "Download via BitTorrent"
|
||||
, "library-opds-feed-all-entries": "Library OPDS Feed - All entries"
|
||||
, "filter-by-tag": "Filter by tag \"{{TAG}}\""
|
||||
, "stop-filtering-by-tag": "Stop filtering by tag \"{{TAG}}\""
|
||||
, "filter-by-tag": "Filter by tag \"{{{TAG}}}\""
|
||||
, "stop-filtering-by-tag": "Stop filtering by tag \"{{{TAG}}}\""
|
||||
, "library-opds-feed-parameterised": "Library OPDS Feed - entries matching {{#LANG}}\nLanguage: {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategory: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nTag: {{TAG}} {{/TAG}}{{#Q}}\nQuery: {{Q}} {{/Q}}"
|
||||
, "welcome-to-kiwix-server": "Welcome to Kiwix Server"
|
||||
, "download-links-heading": "Download links for <b><i>{{BOOK_TITLE}}</i></b>"
|
||||
@@ -60,4 +60,22 @@
|
||||
, "preview-book": "Preview"
|
||||
, "non-translated-text": "{{MSG}}"
|
||||
, "unknown-error": "Unknown error"
|
||||
, "book-category.gutenberg": "Gutenberg"
|
||||
, "book-category.iFixit": "iFixit"
|
||||
, "book-category.mooc": "MOOC"
|
||||
, "book-category.phet": "Phet"
|
||||
, "book-category.stack_exchange": "Stack Exchange"
|
||||
, "book-category.ted": "Ted"
|
||||
, "book-category.vikidia": "Vikidia"
|
||||
, "book-category.wikibooks": "Wikibooks"
|
||||
, "book-category.wikihow": "wikiHow"
|
||||
, "book-category.wikinews": "Wikinews"
|
||||
, "book-category.wikipedia": "Wikipedia"
|
||||
, "book-category.wikiquote": "Wikiquote"
|
||||
, "book-category.wikisource": "Wikisource"
|
||||
, "book-category.wikispecies": "Wikispecies"
|
||||
, "book-category.wikiversity": "Wikiversity"
|
||||
, "book-category.wikivoyage": "Wikivoyage"
|
||||
, "book-category.wiktionary": "Wiktionary"
|
||||
, "book-category.other": "Other"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"authors": [
|
||||
"Adriendelucca",
|
||||
"Gomoko",
|
||||
"Goombiis",
|
||||
"Melimeli",
|
||||
"Stephane",
|
||||
"Thibaut120094",
|
||||
@@ -67,5 +68,15 @@
|
||||
"download-links-heading": "Liens de téléchargement pour <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
"download-links-title": "Télécharger le livre",
|
||||
"preview-book": "Aperçu",
|
||||
"unknown-error": "Erreur inconnue"
|
||||
"unknown-error": "Erreur inconnue",
|
||||
"book-category.wikibooks": "Wikilivres",
|
||||
"book-category.wikinews": "Wikinews",
|
||||
"book-category.wikipedia": "Wikipédia",
|
||||
"book-category.wikiquote": "Wikiquote",
|
||||
"book-category.wikisource": "Wikisource",
|
||||
"book-category.wikispecies": "Wikispecies",
|
||||
"book-category.wikiversity": "Wikiversité",
|
||||
"book-category.wikivoyage": "Wikivoyage",
|
||||
"book-category.wiktionary": "Wiktionnaire",
|
||||
"book-category.other": "Autre"
|
||||
}
|
||||
|
||||
@@ -60,5 +60,17 @@
|
||||
"download-links-heading": "הורדת קישורים עבור <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
"download-links-title": "הורדת ספר",
|
||||
"preview-book": "תצוגה מקדימה",
|
||||
"unknown-error": "שגיאה בלתי־ידועה"
|
||||
"unknown-error": "שגיאה בלתי־ידועה",
|
||||
"book-category.gutenberg": "גוטנברג",
|
||||
"book-category.vikidia": "ויקידיה",
|
||||
"book-category.wikibooks": "ויקיספר",
|
||||
"book-category.wikinews": "ויקיחדשות",
|
||||
"book-category.wikipedia": "ויקיפדיה",
|
||||
"book-category.wikiquote": "ויקיציטוט",
|
||||
"book-category.wikisource": "ויקיטקסט (Wikisource)",
|
||||
"book-category.wikispecies": "ויקימינים",
|
||||
"book-category.wikiversity": "ויקיברסיטה",
|
||||
"book-category.wikivoyage": "ויקימסע",
|
||||
"book-category.wiktionary": "ויקימילון",
|
||||
"book-category.other": "אחר"
|
||||
}
|
||||
|
||||
@@ -52,5 +52,15 @@
|
||||
"welcome-to-kiwix-server": "कीविक्स सर्वर में आपका स्वागत है",
|
||||
"download-links-heading": "<b><i>{{BOOK_TITLE}}</i></b> के लिए डाउनलोड लिंक",
|
||||
"download-links-title": "पुस्तक डाउनलोड करें",
|
||||
"preview-book": "पूर्वावलोकन"
|
||||
"preview-book": "पूर्वावलोकन",
|
||||
"book-category.wikibooks": "विकिपुस्तक",
|
||||
"book-category.wikinews": "विकिसमाचार",
|
||||
"book-category.wikipedia": "विकिपीडिया",
|
||||
"book-category.wikiquote": "विकिसूक्ति",
|
||||
"book-category.wikisource": "विकिस्रोत",
|
||||
"book-category.wikispecies": "विकिप्रजाति",
|
||||
"book-category.wikiversity": "विकिविश्वविद्यालय",
|
||||
"book-category.wikivoyage": "विकियात्रा",
|
||||
"book-category.wiktionary": "विकिकोश",
|
||||
"book-category.other": "अन्य"
|
||||
}
|
||||
|
||||
@@ -17,5 +17,15 @@
|
||||
"home-button-text": "Դեպի '{{BOOK_TITLE}}'֊ի գլխավոր էջը",
|
||||
"random-page-button-text": "Բացել պատահական էջ",
|
||||
"searchbox-tooltip": "Որոնել '{{BOOK_TITLE}}'֊ում",
|
||||
"book-filtering-all-categories": "Բոլոր կատեգորիաներ"
|
||||
"book-filtering-all-categories": "Բոլոր կատեգորիաներ",
|
||||
"book-category.wikibooks": "Վիքիգրքեր",
|
||||
"book-category.wikinews": "Վիքիլուրեր",
|
||||
"book-category.wikipedia": "Վիքիպեդիա",
|
||||
"book-category.wikiquote": "Վիքիքաղվածք",
|
||||
"book-category.wikisource": "Վիքիդարան",
|
||||
"book-category.wikispecies": "Վիքիցեղեր",
|
||||
"book-category.wikiversity": "Վիքիլսարան",
|
||||
"book-category.wikivoyage": "Վիքիճամփորդ",
|
||||
"book-category.wiktionary": "Վիքիբառարան",
|
||||
"book-category.other": "Այլ"
|
||||
}
|
||||
|
||||
@@ -36,5 +36,16 @@
|
||||
"direct-download-link-text": "Direkt",
|
||||
"torrent-download-link-text": "BitTorrent",
|
||||
"download-links-title": "Buch eroflueden",
|
||||
"unknown-error": "Onbekannte Feeler"
|
||||
"unknown-error": "Onbekannte Feeler",
|
||||
"book-category.stack_exchange": "Stack Exchange",
|
||||
"book-category.wikibooks": "Wikibooks",
|
||||
"book-category.wikinews": "Wikinews",
|
||||
"book-category.wikipedia": "Wikipedia",
|
||||
"book-category.wikiquote": "Wikiquote",
|
||||
"book-category.wikisource": "Wikisource",
|
||||
"book-category.wikispecies": "Wikispecies",
|
||||
"book-category.wikiversity": "Wikiversity",
|
||||
"book-category.wikivoyage": "Wikivoyage",
|
||||
"book-category.wiktionary": "Wiktionnaire",
|
||||
"book-category.other": "Anerer"
|
||||
}
|
||||
|
||||
@@ -60,5 +60,23 @@
|
||||
"download-links-heading": "Врски за преземање на <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
"download-links-title": "Преземи книга",
|
||||
"preview-book": "Преглед",
|
||||
"unknown-error": "Непозната грешка"
|
||||
"unknown-error": "Непозната грешка",
|
||||
"book-category.gutenberg": "Гутенберг",
|
||||
"book-category.iFixit": "iFixit",
|
||||
"book-category.mooc": "MOOC",
|
||||
"book-category.phet": "Phet",
|
||||
"book-category.stack_exchange": "Stack Exchange",
|
||||
"book-category.ted": "Ted",
|
||||
"book-category.vikidia": "Викидија",
|
||||
"book-category.wikibooks": "Викикниги",
|
||||
"book-category.wikihow": "wikiHow",
|
||||
"book-category.wikinews": "Викивести",
|
||||
"book-category.wikipedia": "Википедија",
|
||||
"book-category.wikiquote": "Викицитат",
|
||||
"book-category.wikisource": "Викиизвор",
|
||||
"book-category.wikispecies": "Викивидови",
|
||||
"book-category.wikiversity": "Викиуниверзитет",
|
||||
"book-category.wikivoyage": "Википатување",
|
||||
"book-category.wiktionary": "Викиречник",
|
||||
"book-category.other": "друго"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"authors": [
|
||||
"Kelson",
|
||||
"McDutchie",
|
||||
"Siebrand",
|
||||
"Vistaus"
|
||||
]
|
||||
},
|
||||
@@ -24,8 +25,12 @@
|
||||
"404-page-heading": "Niet gevonden",
|
||||
"500-page-title": "Interne serverfout",
|
||||
"500-page-heading": "Interne serverfout",
|
||||
"500-page-text": "Er is een interne serverfout opgetreden. Dat spijt ons",
|
||||
"fulltext-search-unavailable": "Zoeken in volledige tekst is niet beschikbaar",
|
||||
"no-search-results": "De zoekmachine voor volledige tekst is niet beschikbaar voor deze inhoud.",
|
||||
"search-results-page-title": "Zoeken: {{SEARCH_PATTERN}}",
|
||||
"search-result-book-info": "uit {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} woorden",
|
||||
"library-button-text": "Naar de welkomstpagina",
|
||||
"home-button-text": "Naar de hoofdpagina van ‘{{BOOK_TITLE}}’",
|
||||
"random-page-button-text": "Naar een willekeurig geselecteerde pagina gaan",
|
||||
@@ -53,5 +58,6 @@
|
||||
"welcome-to-kiwix-server": "Welkom bij de Kiwix-server",
|
||||
"download-links-heading": "Downloadkoppelingen voor <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
"download-links-title": "Boek downloaden",
|
||||
"preview-book": "Voorvertoning"
|
||||
"preview-book": "Voorvertoning",
|
||||
"unknown-error": "Onbekende fout"
|
||||
}
|
||||
|
||||
@@ -34,5 +34,6 @@
|
||||
"torrent-download-link-text": "Plik torrent",
|
||||
"welcome-to-kiwix-server": "Witamy na serwerze Kiwix",
|
||||
"download-links-title": "Pobierz książkę",
|
||||
"preview-book": "Podgląd"
|
||||
"preview-book": "Podgląd",
|
||||
"book-category.other": "Inne"
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
"authors": [
|
||||
"Eduardoaddad",
|
||||
"Obiru",
|
||||
"Re demz"
|
||||
"Re demz",
|
||||
"YoReaper"
|
||||
]
|
||||
},
|
||||
"name": "Português",
|
||||
@@ -31,10 +32,10 @@
|
||||
"book-filtering-all-languages": "Todos os idiomas",
|
||||
"count-of-matching-books": "{{COUNT}} livro(s)",
|
||||
"download": "Baixar",
|
||||
"hash-download-link-text": "Hash sha256",
|
||||
"hash-download-alt-text": "baixar hash",
|
||||
"torrent-download-link-text": "Arquivo torrent",
|
||||
"torrent-download-alt-text": "baixar torrent",
|
||||
"hash-download-link-text": "Verificação SHA-256",
|
||||
"hash-download-alt-text": "Exibir arquivo de verificação SHA-256",
|
||||
"torrent-download-link-text": "BitTorrent",
|
||||
"torrent-download-alt-text": "Baixar via BitTorrent",
|
||||
"welcome-to-kiwix-server": "Bem vindo ao servidor Kiwix",
|
||||
"download-links-heading": "Links para baixar <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
"download-links-title": "Download do livro",
|
||||
|
||||
73
static/skin/i18n/pt.json
Normal file
73
static/skin/i18n/pt.json
Normal file
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"@metadata": {
|
||||
"authors": [
|
||||
"B3rnas"
|
||||
]
|
||||
},
|
||||
"name": "português",
|
||||
"suggest-full-text-search": "contendo '{{{SEARCH_TERMS}}}'...",
|
||||
"no-such-book": "Não existe o livro: {{BOOK_NAME}}",
|
||||
"too-many-books": "Demasiadas solicitações de livros ({{NB_BOOKS}}) onde o limite é {{LIMIT}}",
|
||||
"no-book-found": "Nenhum livro corresponde aos critérios de seleção",
|
||||
"url-not-found": "A URL solicitada \"{{url}}\" não foi encontrada neste servidor.",
|
||||
"suggest-search": "Faça uma pesquisa de texto completo para <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
|
||||
"random-article-failure": "Ups! Erro ao eleger um artigo aleatório :(",
|
||||
"invalid-raw-data-type": "{{DATATYPE}} não é uma solicitação válida para conteúdo bruto.",
|
||||
"invalid-request": "A URL solicitada \"{{{url}}}\" não é uma solicitação válida.",
|
||||
"no-value-for-arg": "Nenhum valor fornecido para o argumento {{ARGUMENT}}",
|
||||
"no-query": "Nenhuma consulta fornecida.",
|
||||
"raw-entry-not-found": "Não é possível encontrar a entrada {{DATATYPE}} {{ENTRY}}",
|
||||
"400-page-title": "Solicitação inválida",
|
||||
"400-page-heading": "Solicitação inválida",
|
||||
"404-page-title": "Conteúdo não encontrado",
|
||||
"404-page-heading": "Não encontrado",
|
||||
"500-page-title": "Erro interno do servidor",
|
||||
"500-page-heading": "Erro interno do servidor",
|
||||
"500-page-text": "Ocorreu um erro interno do servidor. Lamentamos por isso :/",
|
||||
"fulltext-search-unavailable": "Pesquisa de texto completo indisponível",
|
||||
"no-search-results": "O motor de busca de texto completo não está disponível para este conteúdo.",
|
||||
"search-results-page-title": "Pesquisar: {{SEARCH_PATTERN}}",
|
||||
"search-results-page-header": "Resultados <b>{{START}}-{{END}}</b> de <b>{{COUNT}}</b> para <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"empty-search-results-page-header": "Nenhum resultado foi encontrado para <b>\"{{{SEARCH_PATTERN}}}\"</b>",
|
||||
"search-result-book-info": "de {{BOOK_TITLE}}",
|
||||
"word-count": "{{COUNT}} palavras",
|
||||
"library-button-text": "Ir para página inicial",
|
||||
"home-button-text": "Vá para a página principal de '{{BOOK_TITLE}}'",
|
||||
"random-page-button-text": "Vá para uma página selecionada aleatoriamente",
|
||||
"searchbox-tooltip": "Procurar '{{BOOK_TITLE}}'",
|
||||
"confusion-of-tongues": "Dois ou mais livros em idiomas diferentes participariam da pesquisa, o que poderia levar a resultados confusos.",
|
||||
"welcome-page-overzealous-filter": "Nenhum resultado. Gostaria de <a href=\"{{URL}}\">redefinir o filtro</a> ?",
|
||||
"powered-by-kiwix-html": "Desenvolvido por <a href=\"https://kiwix.org\">Kiwix</a>",
|
||||
"search": "Pesquisar",
|
||||
"book-filtering-all-categories": "Todas as categorias",
|
||||
"book-filtering-all-languages": "Todos os idiomas",
|
||||
"count-of-matching-books": "{{COUNT}} livro(s)",
|
||||
"download": "Transferir",
|
||||
"direct-download-link-text": "Direto",
|
||||
"direct-download-alt-text": "Descarregar diretamente através de HTTP (S)",
|
||||
"hash-download-link-text": "Verificação SHA-256",
|
||||
"hash-download-alt-text": "Exibir arquivo de verificação SHA-256",
|
||||
"magnet-link-text": "Link magnético",
|
||||
"magnet-alt-text": "Descarregar através do link Magnet",
|
||||
"torrent-download-link-text": "BitTorrent",
|
||||
"torrent-download-alt-text": "Descarregar através de BitTorrent",
|
||||
"library-opds-feed-all-entries": "Feed OPDS da biblioteca - Todas as entradas",
|
||||
"filter-by-tag": "Filtrar por tag \"{{TAG}}\"",
|
||||
"stop-filtering-by-tag": "Pare de filtrar pela tag \"{{TAG}}\"",
|
||||
"library-opds-feed-parameterised": "Feed OPDS da biblioteca - entradas que correspondem a {{#LANG}}\nIdioma: {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategoria: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nTag: {{TAG}} {{/TAG}}{{#Q}}\nConsulta: {{Q}} {{/Q}}",
|
||||
"welcome-to-kiwix-server": "Bem-vindo ao Kiwix Server",
|
||||
"download-links-heading": "Links para download de <b><i>{{BOOK_TITLE}}</i></b>",
|
||||
"download-links-title": "Descarregar livros",
|
||||
"preview-book": "Pré-visualização",
|
||||
"unknown-error": "Erro desconhecido",
|
||||
"book-category.wikibooks": "Wikilivros",
|
||||
"book-category.wikinews": "Wikinotícias",
|
||||
"book-category.wikipedia": "Wikipédia",
|
||||
"book-category.wikiquote": "Wikiquote",
|
||||
"book-category.wikisource": "Wikisource",
|
||||
"book-category.wikispecies": "Wikispecies",
|
||||
"book-category.wikiversity": "Wikiversidade",
|
||||
"book-category.wikivoyage": "Wikivoyage",
|
||||
"book-category.wiktionary": "Wikcionário",
|
||||
"book-category.other": "Outro"
|
||||
}
|
||||
@@ -62,5 +62,23 @@
|
||||
"download-links-title": "Title for no-js download page",
|
||||
"preview-book": "Tooltip of book-tile leading to the book",
|
||||
"non-translated-text": "{{ignored}}\nUsed to display text that is generated at runtime and cannot be translated. Nothing to translate about this one.",
|
||||
"unknown-error": "Unknown error"
|
||||
"unknown-error": "Unknown error",
|
||||
"book-category.gutenberg": "Name for the category of books from the Gutenberg project",
|
||||
"book-category.iFixit": "Name for the category of iFixit books",
|
||||
"book-category.mooc": "Name for the category of MOOC books",
|
||||
"book-category.phet": "Name for the category of Phet books",
|
||||
"book-category.stack_exchange": "Name for the category of books from the Stack Exchange network books",
|
||||
"book-category.ted": "Name for the category of Ted books",
|
||||
"book-category.vikidia": "Name for the category of Vikidia books",
|
||||
"book-category.wikibooks": "Name for the category of Wikibooks books books",
|
||||
"book-category.wikihow": "Name for the category of wikiHow books",
|
||||
"book-category.wikinews": "Name for the category of Wikinews books",
|
||||
"book-category.wikipedia": "Name for the category of Wikipedia books",
|
||||
"book-category.wikiquote": "Name for the category of Wikiquote books",
|
||||
"book-category.wikisource": "Name for the category of Wikisource books",
|
||||
"book-category.wikispecies": "Name for the category of Wikispecies books",
|
||||
"book-category.wikiversity": "Name for the category of Wikiversity books",
|
||||
"book-category.wikivoyage": "Name for the category of Wikivoyage books",
|
||||
"book-category.wiktionary": "Name for the category of Wiktionary books",
|
||||
"book-category.other": "Books not belonging to any special category are listed under this one"
|
||||
}
|
||||
|
||||
@@ -22,5 +22,16 @@
|
||||
"torrent-download-link-text": "ٹورنٹ فائل",
|
||||
"torrent-download-alt-text": "ٹورںٹ ݙاؤن لوڈ کرو",
|
||||
"download-links-title": "کتاب ڈاؤن لوڈ کرو",
|
||||
"preview-book": "پیشگی ݙکھالا"
|
||||
"preview-book": "پیشگی ݙکھالا",
|
||||
"book-category.vikidia": "وکی ڈیا",
|
||||
"book-category.wikibooks": "وکی کتاباں",
|
||||
"book-category.wikinews": "وکی خبراں",
|
||||
"book-category.wikipedia": "وکیپیڈیا",
|
||||
"book-category.wikiquote": "وکی ٻول",
|
||||
"book-category.wikisource": "وکی ماخذ",
|
||||
"book-category.wikispecies": "وکی سپیشیز",
|
||||
"book-category.wikiversity": "وکی ورسٹی",
|
||||
"book-category.wikivoyage": "وکی سیرسپاٹا",
|
||||
"book-category.wiktionary": "وکشنری",
|
||||
"book-category.other": "ٻیا"
|
||||
}
|
||||
|
||||
@@ -47,4 +47,22 @@
|
||||
, "empty-search-results-page-header": "[I18N TESTING] No results were found for <b>\"{{{SEARCH_PATTERN}}}\"</b>"
|
||||
, "search-result-book-info": "from [I18N TESTING] {{BOOK_TITLE}}"
|
||||
, "word-count": "{{COUNT}} [I18N TESTING] words"
|
||||
, "book-category.gutenberg": "[I18N] Gutenberg [TESTING]"
|
||||
, "book-category.iFixit": "[I18N] iFixit [TESTING]"
|
||||
, "book-category.mooc": "[I18N] MOOC [TESTING]"
|
||||
, "book-category.phet": "[I18N] Phet [TESTING]"
|
||||
, "book-category.stack_exchange": "[I18N] Stack Exchange [TESTING]"
|
||||
, "book-category.ted": "[I18N] Ted [TESTING]"
|
||||
, "book-category.vikidia": "[I18N] Vikidia [TESTING]"
|
||||
, "book-category.wikibooks": "[I18N] Wikibooks [TESTING]"
|
||||
, "book-category.wikihow": "[I18N] wikiHow [TESTING]"
|
||||
, "book-category.wikinews": "[I18N] Wikinews [TESTING]"
|
||||
, "book-category.wikipedia": "[I18N] Wikipedia [TESTING]"
|
||||
, "book-category.wikiquote": "[I18N] Wikiquote [TESTING]"
|
||||
, "book-category.wikisource": "[I18N] Wikisource [TESTING]"
|
||||
, "book-category.wikispecies": "[I18N] Wikispecies [TESTING]"
|
||||
, "book-category.wikiversity": "[I18N] Wikiversity [TESTING]"
|
||||
, "book-category.wikivoyage": "[I18N] Wikivoyage [TESTING]"
|
||||
, "book-category.wiktionary": "[I18N] Wiktionary [TESTING]"
|
||||
, "book-category.other": "[I18N] Other [TESTING]"
|
||||
}
|
||||
|
||||
@@ -65,5 +65,15 @@
|
||||
"download-links-heading": "下载<b><i>{{BOOK_TITLE}}</i></b>的链接",
|
||||
"download-links-title": "下载书籍",
|
||||
"preview-book": "预览",
|
||||
"unknown-error": "未知错误"
|
||||
"unknown-error": "未知错误",
|
||||
"book-category.wikibooks": "维基教科书",
|
||||
"book-category.wikinews": "维基新闻",
|
||||
"book-category.wikipedia": "维基百科",
|
||||
"book-category.wikiquote": "维基语录",
|
||||
"book-category.wikisource": "维基文库",
|
||||
"book-category.wikispecies": "维基物种",
|
||||
"book-category.wikiversity": "维基学院",
|
||||
"book-category.wikivoyage": "维基导游",
|
||||
"book-category.wiktionary": "维基词典",
|
||||
"book-category.other": "其他"
|
||||
}
|
||||
|
||||
@@ -46,13 +46,13 @@
|
||||
"count-of-matching-books": "{{COUNT}} 本書籍",
|
||||
"download": "下載",
|
||||
"direct-download-link-text": "直接",
|
||||
"direct-download-alt-text": "直接下載",
|
||||
"hash-download-link-text": "Sha256 雜湊",
|
||||
"hash-download-alt-text": "下載雜湊",
|
||||
"direct-download-alt-text": "直接透過 HTTP(S)下載",
|
||||
"hash-download-link-text": "SHA-256 核對和",
|
||||
"hash-download-alt-text": "顯示 SHA-256 檔案核對和",
|
||||
"magnet-link-text": "Magnet 連結",
|
||||
"magnet-alt-text": "下載 magnet",
|
||||
"torrent-download-link-text": "Torrent 檔案",
|
||||
"torrent-download-alt-text": "下載 torrent",
|
||||
"magnet-alt-text": "透過磁力連結下載",
|
||||
"torrent-download-link-text": "BitTorrent",
|
||||
"torrent-download-alt-text": "透過 BitTorrent 下載",
|
||||
"library-opds-feed-all-entries": "圖書館 OPDS 摘要 - 所有項目",
|
||||
"filter-by-tag": "依標籤「{{TAG}}」篩選",
|
||||
"stop-filtering-by-tag": "停止依標籤「{{TAG}}」篩選",
|
||||
@@ -61,5 +61,23 @@
|
||||
"download-links-heading": "下載<b><i>{{BOOK_TITLE}}</i></b>的連結",
|
||||
"download-links-title": "下載書籍",
|
||||
"preview-book": "預覽",
|
||||
"unknown-error": "不明錯誤"
|
||||
"unknown-error": "不明錯誤",
|
||||
"book-category.gutenberg": "古騰堡計劃",
|
||||
"book-category.iFixit": "iFixit",
|
||||
"book-category.mooc": "MOOC",
|
||||
"book-category.phet": "PhET",
|
||||
"book-category.stack_exchange": "Stack Exchange",
|
||||
"book-category.ted": "Ted",
|
||||
"book-category.vikidia": "Vikidia",
|
||||
"book-category.wikibooks": "維基教科書",
|
||||
"book-category.wikihow": "wikiHow",
|
||||
"book-category.wikinews": "維基新聞",
|
||||
"book-category.wikipedia": "維基百科",
|
||||
"book-category.wikiquote": "維基語錄",
|
||||
"book-category.wikisource": "維基文庫",
|
||||
"book-category.wikispecies": "維基物種",
|
||||
"book-category.wikiversity": "維基學院",
|
||||
"book-category.wikivoyage": "維基導遊",
|
||||
"book-category.wiktionary": "維基詞典",
|
||||
"book-category.other": "其他"
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
|
||||
.tagFilterLabel {
|
||||
width: max-content;
|
||||
padding: 10px;
|
||||
padding: 7px;
|
||||
font-family: roboto;
|
||||
font-size: 12px;
|
||||
margin: 0 0 0 17px;
|
||||
@@ -152,23 +152,24 @@
|
||||
|
||||
.book__link {
|
||||
text-decoration: none;
|
||||
grid-column: 1 / 3;
|
||||
grid-row: 1 / 3;
|
||||
}
|
||||
|
||||
.book__wrapper {
|
||||
--tile-width: 250px;
|
||||
--tile-border-width: 1px;
|
||||
--tile-border-radius: 3px;
|
||||
color: #444343;
|
||||
height: 248px;
|
||||
width: 250px;
|
||||
height: 258px;
|
||||
width: var(--tile-width);
|
||||
margin: 15px;
|
||||
background-color: #f7f7f7;
|
||||
border: 1px solid #ececec;
|
||||
border-radius: 3px;
|
||||
display: grid;
|
||||
grid-template-columns: 65px 1fr;
|
||||
grid-template-rows: 70px 120px 1fr 1fr;
|
||||
grid-gap: 5px;
|
||||
border: var(--tile-border-width) solid #ececec;
|
||||
border-radius: var(--tile-border-radius);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
transition: transform 0.25s;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.book__wrapper:hover {
|
||||
@@ -177,8 +178,12 @@
|
||||
|
||||
.book__link__wrapper {
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"bookIcon bookHeader"
|
||||
"bookDesc bookDesc"
|
||||
;
|
||||
grid-template-columns: 65px 1fr;
|
||||
grid-template-rows: 70px 120px 1fr 1fr;
|
||||
grid-template-rows: 70px 120px;
|
||||
}
|
||||
|
||||
.book__icon {
|
||||
@@ -189,7 +194,10 @@
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
margin: 10px 0 0 10px;
|
||||
grid-area: bookIcon;
|
||||
}
|
||||
|
||||
.book__header {
|
||||
@@ -201,6 +209,7 @@
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
grid-area: bookHeader;
|
||||
}
|
||||
|
||||
.book__title {
|
||||
@@ -211,28 +220,36 @@
|
||||
}
|
||||
|
||||
.book__download {
|
||||
font-size: 1.1rem;
|
||||
margin: 3px 0;
|
||||
cursor: pointer;
|
||||
font-size: 1.2rem;
|
||||
background: #00b4e4;
|
||||
padding: 8px;
|
||||
border-radius: 0 0 var(--tile-border-radius) var(--tile-border-radius);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
width: var(--tile-width);
|
||||
left: calc(0px - var(--tile-border-width));
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.book__download > img {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.book__download > span {
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
padding: 0 3px 1px;
|
||||
font-family: roboto;
|
||||
background: #00b4e4;
|
||||
color: white;
|
||||
box-shadow: 0 0 0 0;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.book__download > span:hover {
|
||||
box-shadow: 0 5px 5px rgba(0, 0, 0, 0.1)
|
||||
.book__download:hover {
|
||||
background: royalblue;
|
||||
}
|
||||
|
||||
.book__description {
|
||||
grid-column: 1 / 3;
|
||||
margin: 10px 10px 5px;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
@@ -243,6 +260,13 @@
|
||||
font-size: 1.2rem;
|
||||
color: #4d4d4d;
|
||||
line-height: 1.25;
|
||||
grid-area: bookDesc;
|
||||
}
|
||||
|
||||
.book__meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 10px 4px;
|
||||
}
|
||||
|
||||
.book__languageTag {
|
||||
@@ -255,7 +279,6 @@
|
||||
color: black;
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
margin: 10px auto 0 10px;
|
||||
border-radius: 5px;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
@@ -266,8 +289,6 @@
|
||||
font-size: 1.1rem;
|
||||
justify-content: flex-end;
|
||||
font-family: roboto;
|
||||
margin-right: 10px;
|
||||
margin-top: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
@@ -105,6 +105,14 @@
|
||||
return '';
|
||||
}
|
||||
|
||||
// Borrowed from https://stackoverflow.com/a/1912522
|
||||
function htmlDecode(input){
|
||||
var e = document.createElement('textarea');
|
||||
e.innerHTML = input;
|
||||
// handle case of empty input
|
||||
return e.childNodes.length === 0 ? "" : e.childNodes[0].nodeValue;
|
||||
}
|
||||
|
||||
function htmlEncode(str) {
|
||||
return str.replace(/[\u00A0-\u9999<>\&]/gim, (i) => `&#${i.charCodeAt(0)};`);
|
||||
}
|
||||
@@ -121,9 +129,27 @@
|
||||
|
||||
function generateTagLink(tagValue) {
|
||||
tagValue = tagValue.toLowerCase();
|
||||
const humanFriendlyTagValue = humanFriendlyTitle(tagValue);
|
||||
const tagMessage = $t("filter-by-tag", {TAG: humanFriendlyTagValue});
|
||||
return `<span class='tag__link' aria-label='${tagMessage}' title='${tagMessage}' data-tag=${tagValue}>${humanFriendlyTagValue}</span>`
|
||||
const tagMessage = $t("filter-by-tag", {TAG: tagValue});
|
||||
const spanElement = document.createElement("span");
|
||||
spanElement.className = 'tag__link';
|
||||
spanElement.setAttribute('aria-label', tagMessage);
|
||||
spanElement.setAttribute('title', tagMessage);
|
||||
spanElement.setAttribute('data-tag', tagValue);
|
||||
spanElement.innerHTML = htmlEncode(tagValue);
|
||||
return spanElement.outerHTML;
|
||||
}
|
||||
|
||||
function downloadButtonText(zimSize) {
|
||||
return $t("download") + (zimSize ? ` - ${zimSize}`: '');
|
||||
}
|
||||
|
||||
function downloadButtonHtml(url, zimSize) {
|
||||
return url
|
||||
? `<div class="book__download" data-link="${url}" data-size="${zimSize}">
|
||||
<img src="${root}/skin/download-white.svg?KIWIXCACHEID">
|
||||
<span ">${downloadButtonText(zimSize)}</span>
|
||||
</div>`
|
||||
: '';
|
||||
}
|
||||
|
||||
function generateBookHtml(book, sort = false) {
|
||||
@@ -144,7 +170,7 @@
|
||||
const mulLangList = langCodesList.filter(x => languages.hasOwnProperty(x)).map(x => languages[x]);
|
||||
language = mulLangList.join(', ');
|
||||
}
|
||||
const tags = getInnerHtml(book, 'tags');
|
||||
const tags = htmlDecode(getInnerHtml(book, 'tags'));
|
||||
const tagList = tags.split(';').filter(tag => {return !(tag.startsWith('_'))});
|
||||
const tagFilterLinks = tagList.map((tagValue) => generateTagLink(tagValue));
|
||||
const tagHtml = tagFilterLinks.join(' | ');
|
||||
@@ -170,6 +196,7 @@
|
||||
}
|
||||
const faviconAttr = iconUrl != undefined ? `style="background-image: url('${iconUrl}')"` : '';
|
||||
const languageAttr = langCode != '' ? `title="${language}" aria-label="${language}"` : 'style="background-color: transparent"';
|
||||
|
||||
divTag.innerHTML = `
|
||||
<div class="book__wrapper">
|
||||
<a class="book__link" href="${viewerLink}" data-hover="Preview">
|
||||
@@ -177,13 +204,15 @@
|
||||
<div class="book__icon" ${faviconAttr}></div>
|
||||
<div class="book__header">
|
||||
<div id="book__title">${title}</div>
|
||||
${downloadLink ? `<div class="book__download"><span data-link="${downloadLink}">${$t("download")} ${humanFriendlyZimSize ? ` - ${humanFriendlyZimSize}</span></div>`: ''}` : ''}
|
||||
</div>
|
||||
<div class="book__description" title="${description}">${description}</div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="book__meta">
|
||||
<div class="book__languageTag" ${languageAttr}>${getLanguageCodeToDisplay(langCode)}</div>
|
||||
<div class="book__tags"><div class="book__tags--wrapper">${tagHtml}</div></div>
|
||||
</div>
|
||||
${downloadButtonHtml(downloadLink, humanFriendlyZimSize)}
|
||||
</div></div>`;
|
||||
return divTag;
|
||||
}
|
||||
@@ -269,6 +298,7 @@
|
||||
}
|
||||
|
||||
function insertModal(button) {
|
||||
const downloadSize = button.getAttribute('data-size');
|
||||
const downloadLink = button.getAttribute('data-link');
|
||||
button.addEventListener('click', async (event) => {
|
||||
event.preventDefault();
|
||||
@@ -278,7 +308,7 @@
|
||||
<div class="modal-heading">
|
||||
<div class="modal-title">
|
||||
<div>
|
||||
Download
|
||||
${downloadButtonText(downloadSize)}
|
||||
</div>
|
||||
</div>
|
||||
<div onclick="closeModal()" class="modal-close-button">
|
||||
@@ -438,7 +468,7 @@
|
||||
booksToDelete.forEach(book => {iso.remove(book);});
|
||||
books.forEach((book) => {
|
||||
iso.insert(generateBookHtml(book, sort))
|
||||
const downloadButton = document.querySelector(`[data-id="${getInnerHtml(book, 'id')}"] .book__download span`);
|
||||
const downloadButton = document.querySelector(`[data-id="${getInnerHtml(book, 'id')}"] .book__download`);
|
||||
if (downloadButton) {
|
||||
insertModal(downloadButton);
|
||||
}
|
||||
@@ -492,7 +522,7 @@
|
||||
function addTagElement(tagValue, resetFilter) {
|
||||
const tagElement = document.getElementsByClassName('tagFilterLabel')[0];
|
||||
tagElement.style.display = 'inline-block';
|
||||
tagElement.innerHTML = `${tagValue}`;
|
||||
tagElement.innerHTML = htmlEncode(tagValue);
|
||||
const tagMessage = $t("stop-filtering-by-tag", {TAG: tagValue});
|
||||
tagElement.setAttribute('aria-label', tagMessage);
|
||||
tagElement.setAttribute('title', tagMessage);
|
||||
|
||||
@@ -310,6 +310,12 @@ function blockLink(url) {
|
||||
: url;
|
||||
}
|
||||
|
||||
function urlMustBeHandledByAnExternalApp(url) {
|
||||
const WHITELISTED_URL_SCHEMATA = ['http:', 'https:', 'about:', 'javascript:'];
|
||||
|
||||
return WHITELISTED_URL_SCHEMATA.indexOf(url.protocol) == -1;
|
||||
}
|
||||
|
||||
function isExternalUrl(url) {
|
||||
if ( url.startsWith(window.location.origin) )
|
||||
return false;
|
||||
@@ -329,20 +335,34 @@ function getRealHref(target) {
|
||||
return target_href;
|
||||
}
|
||||
|
||||
function setHrefAvoidingWombatRewriting(target, url) {
|
||||
const old_no_rewrite = target._no_rewrite;
|
||||
target._no_rewrite = true;
|
||||
target.setAttribute("href", url);
|
||||
target._no_rewrite = old_no_rewrite;
|
||||
}
|
||||
|
||||
function onClickEvent(e) {
|
||||
const iframeDocument = contentIframe.contentDocument;
|
||||
const target = matchingAncestorElement(e.target, iframeDocument, "a");
|
||||
if (target !== null && "href" in target) {
|
||||
const target_href = getRealHref(target);
|
||||
if (isExternalUrl(target_href)) {
|
||||
const target_url = new URL(target_href, iframeDocument.location);
|
||||
const isExternalAppUrl = urlMustBeHandledByAnExternalApp(target_url);
|
||||
if ( isExternalAppUrl && !viewerSettings.linkBlockingEnabled ) {
|
||||
target.setAttribute("target", "_blank");
|
||||
}
|
||||
|
||||
if (isExternalAppUrl || isExternalUrl(target_href)) {
|
||||
const possiblyBlockedLink = blockLink(target_href);
|
||||
if ( e.ctrlKey || e.shiftKey ) {
|
||||
// The link will be loaded in a new tab/window - update the link
|
||||
// and let the browser handle the rest.
|
||||
target.setAttribute("href", possiblyBlockedLink);
|
||||
setHrefAvoidingWombatRewriting(target, possiblyBlockedLink);
|
||||
} else {
|
||||
// Load the external URL in the viewer window (rather than iframe)
|
||||
contentIframe.contentWindow.parent.location = possiblyBlockedLink;
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,20 +50,11 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.book__link__wrapper {
|
||||
grid-column: 1 / 3;
|
||||
grid-row: 1 / 3;
|
||||
}
|
||||
|
||||
.book__link {
|
||||
grid-row: 2 / 3;
|
||||
}
|
||||
|
||||
.kiwixHomeBody__results {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
#book__title>a, .book__download a {
|
||||
#book__title>a {
|
||||
text-decoration: none;
|
||||
all: unset;
|
||||
}
|
||||
@@ -117,25 +108,32 @@
|
||||
<h3 class="kiwixHomeBody__results">{{translations.count-of-matching-books}}</h3>
|
||||
{{#books}}
|
||||
<div class="book__wrapper">
|
||||
<div class="book__link__wrapper">
|
||||
<div class="book__icon" {{faviconAttr}}></div>
|
||||
<div class="book__header">
|
||||
<div id="book__title"><a href="{{root}}/content/{{id}}">{{title}}</a></div>
|
||||
{{#downloadAvailable}}
|
||||
<div class="book__download"><span><a href="{{root}}/nojs/download/{{id}}">{{translations.download}}</a></span></div>
|
||||
{{/downloadAvailable}}
|
||||
</div>
|
||||
<a class="book__link" href="{{root}}/content/{{id}}" title="{{translations.preview-book}}" aria-label="{{translations.preview-book}}">
|
||||
<div class="book__description" title="{{description}}">{{description}}</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="book__languageTag" {{languageAttr}}>{{langCode}}</div>
|
||||
<div class="book__tags"><div class="book__tags--wrapper">
|
||||
{{#tagList}}
|
||||
<span class="tag__link" aria-label='{{tag}}' title='{{tag}}'>{{tag}}</span>
|
||||
{{/tagList}}
|
||||
<a class="book__link" href="{{root}}/content/{{id}}" title="{{translations.preview-book}}" aria-label="{{translations.preview-book}}">
|
||||
<div class="book__link__wrapper">
|
||||
<div class="book__icon" {{faviconAttr}}></div>
|
||||
<div class="book__header">
|
||||
<div id="book__title">{{title}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="book__description" title="{{description}}">{{description}}</div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="book__meta">
|
||||
<div class="book__languageTag" {{languageAttr}}>{{langCode}}</div>
|
||||
<div class="book__tags"><div class="book__tags--wrapper">
|
||||
{{#tagList}}
|
||||
<span class="tag__link" aria-label='{{tag}}' title='{{tag}}'>{{tag}}</span>
|
||||
{{/tagList}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{#downloadAvailable}}
|
||||
<div>
|
||||
<a class="book__download" href="{{root}}/nojs/download/{{id}}">
|
||||
<img src="{{root}}/skin/download-white.svg?KIWIXCACHEID">
|
||||
<span>{{translations.download}}</span>
|
||||
</a>
|
||||
</div>
|
||||
{{/downloadAvailable}}
|
||||
</div>
|
||||
{{/books}}
|
||||
</div>
|
||||
|
||||
@@ -15,11 +15,19 @@ struct XMLDoc : pugi::xml_document
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
#if _WIN32
|
||||
# define DATA_ABS_PATH "C:\\data\\zim"
|
||||
# define ZARA_ABS_PATH "C:\\data\\zim\\zara.zim"
|
||||
#else
|
||||
# define DATA_ABS_PATH "/data/zim"
|
||||
# define ZARA_ABS_PATH "/data/zim/zara.zim"
|
||||
#endif
|
||||
|
||||
TEST(BookTest, updateFromXMLTest)
|
||||
{
|
||||
const XMLDoc xml(R"(
|
||||
<book id="zara"
|
||||
path="./zara.zim"
|
||||
path="zara.zim"
|
||||
url="https://who.org/zara.zim"
|
||||
title="Catch an infection in 24 hours"
|
||||
description="Complete guide to contagious diseases"
|
||||
@@ -40,9 +48,9 @@ TEST(BookTest, updateFromXMLTest)
|
||||
)");
|
||||
|
||||
kiwix::Book book;
|
||||
book.updateFromXml(xml.child("book"), "/data/zim");
|
||||
book.updateFromXml(xml.child("book"), DATA_ABS_PATH);
|
||||
|
||||
EXPECT_EQ(book.getPath(), "/data/zim/zara.zim");
|
||||
EXPECT_EQ(book.getPath(), ZARA_ABS_PATH);
|
||||
EXPECT_EQ(book.getUrl(), "https://who.org/zara.zim");
|
||||
EXPECT_EQ(book.getTitle(), "Catch an infection in 24 hours");
|
||||
EXPECT_EQ(book.getDescription(), "Complete guide to contagious diseases");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "../src/server/i18n.h"
|
||||
#include "../src/server/i18n_utils.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
using namespace kiwix;
|
||||
@@ -48,3 +48,17 @@ TEST(ParameterizedMessage, messagesWithParameters)
|
||||
EXPECT_EQ(msg.getText("test"), "Filter [I18N] by [TESTING] tag \"\"");
|
||||
}
|
||||
}
|
||||
|
||||
TEST(I18n, translateBookCategory)
|
||||
{
|
||||
|
||||
EXPECT_EQ(translateBookCategory("en", "ted"), "Ted");
|
||||
EXPECT_EQ(translateBookCategory("test", "ted"), "[I18N] Ted [TESTING]");
|
||||
|
||||
EXPECT_EQ(translateBookCategory("en", "stack_exchange"), "Stack Exchange");
|
||||
EXPECT_EQ(translateBookCategory("test", "stack_exchange"), "[I18N] Stack Exchange [TESTING]");
|
||||
|
||||
// unknown categories are simply not translated
|
||||
EXPECT_EQ(translateBookCategory("en", "Qwerty"), "Qwerty");
|
||||
EXPECT_EQ(translateBookCategory("test", "Qwerty"), "Qwerty");
|
||||
}
|
||||
|
||||
@@ -267,11 +267,21 @@ const char * sampleOpdsStream = R"(
|
||||
|
||||
)";
|
||||
|
||||
#ifdef _WIN32
|
||||
# define ZIMFILE_PATH ".\\zimfile.zim"
|
||||
# define EXAMPLE_PATH ".\\example.zim"
|
||||
# define LIBRARY_PATH ".\\test\\library.xml"
|
||||
#else
|
||||
# define ZIMFILE_PATH "./zimfile.zim"
|
||||
# define EXAMPLE_PATH "./example.zim"
|
||||
# define LIBRARY_PATH "./test/library.xml"
|
||||
#endif
|
||||
|
||||
const char sampleLibraryXML[] = R"(
|
||||
<library version="1.0">
|
||||
<book
|
||||
id="raycharles"
|
||||
path="./zimfile.zim"
|
||||
path=")" ZIMFILE_PATH R"("
|
||||
url="https://github.com/kiwix/libkiwix/raw/master/test/data/zimfile.zim"
|
||||
title="Ray Charles"
|
||||
description="Wikipedia articles about Ray Charles"
|
||||
@@ -287,7 +297,7 @@ const char sampleLibraryXML[] = R"(
|
||||
></book>
|
||||
<book
|
||||
id="example"
|
||||
path="./example.zim"
|
||||
path=")" EXAMPLE_PATH R"("
|
||||
title="An example ZIM archive"
|
||||
description="An eXaMpLe book added to the catalog via XML"
|
||||
language="deu"
|
||||
@@ -383,7 +393,7 @@ class LibraryTest : public ::testing::Test {
|
||||
void SetUp() override {
|
||||
kiwix::Manager manager(lib);
|
||||
manager.readOpds(sampleOpdsStream, "foo.urlHost");
|
||||
manager.readXml(sampleLibraryXML, false, "./test/library.xml", true);
|
||||
manager.readXml(sampleLibraryXML, false, LIBRARY_PATH, true);
|
||||
}
|
||||
|
||||
kiwix::Bookmark createBookmark(const std::string &id, const std::string& url="", const std::string& title="") {
|
||||
|
||||
@@ -1033,7 +1033,7 @@ TEST_F(LibraryServerTest, no_name_mapper_catalog_v2_individual_entry_access)
|
||||
" />\n" \
|
||||
" <link\n" \
|
||||
" type=\"text/css\"\n" \
|
||||
" href=\"/ROOT%23%3F/skin/index.css?cacheid=1e78e7cf\"\n" \
|
||||
" href=\"/ROOT%23%3F/skin/index.css?cacheid=ae79e41a\"\n" \
|
||||
" rel=\"Stylesheet\"\n" \
|
||||
" />\n" \
|
||||
" <link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"/ROOT%23%3F/skin/favicon/apple-touch-icon.png?cacheid=f86f8df3\">\n" \
|
||||
@@ -1066,17 +1066,10 @@ TEST_F(LibraryServerTest, no_name_mapper_catalog_v2_individual_entry_access)
|
||||
" .tag__link {\n" \
|
||||
" pointer-events: none;\n" \
|
||||
" }\n\n" \
|
||||
" .book__link__wrapper {\n" \
|
||||
" grid-column: 1 / 3;\n" \
|
||||
" grid-row: 1 / 3;\n" \
|
||||
" }\n\n" \
|
||||
" .book__link {\n" \
|
||||
" grid-row: 2 / 3;\n" \
|
||||
" }\n\n" \
|
||||
" .kiwixHomeBody__results {\n" \
|
||||
" flex-basis: 100%;\n" \
|
||||
" }\n\n" \
|
||||
" #book__title>a, .book__download a {\n" \
|
||||
" #book__title>a {\n" \
|
||||
" text-decoration: none;\n" \
|
||||
" all: unset;\n" \
|
||||
" }\n" \
|
||||
@@ -1087,62 +1080,83 @@ TEST_F(LibraryServerTest, no_name_mapper_catalog_v2_individual_entry_access)
|
||||
|
||||
#define CHARLES_RAY_BOOK_HTML \
|
||||
" <div class=\"book__wrapper\">\n" \
|
||||
" <div class=\"book__link__wrapper\">\n" \
|
||||
" <div class=\"book__icon\" style=background-image:url(/ROOT%23%3F/catalog/v2/illustration/charlesray/?size=48)></div>\n" \
|
||||
" <div class=\"book__header\">\n" \
|
||||
" <div id=\"book__title\"><a href=\"/ROOT%23%3F/content/zimfile%26other\">Charles, Ray</a></div>\n" \
|
||||
" <div class=\"book__download\"><span><a href=\"/ROOT%23%3F/nojs/download/zimfile%26other\">Download</a></span></div>\n" \
|
||||
" </div>\n" \
|
||||
" <a class=\"book__link\" href=\"/ROOT%23%3F/content/zimfile%26other\" title=\"Preview\" aria-label=\"Preview\">\n" \
|
||||
" <div class=\"book__description\" title=\"Wikipedia articles about Ray Charles\">Wikipedia articles about Ray Charles</div>\n" \
|
||||
" </a>\n" \
|
||||
" </div>\n" \
|
||||
" <div class=\"book__languageTag\" >fra</div>\n" \
|
||||
" <div class=\"book__tags\"><div class=\"book__tags--wrapper\">\n" \
|
||||
" <span class=\"tag__link\" aria-label='unittest' title='unittest'>unittest</span>\n" \
|
||||
" <span class=\"tag__link\" aria-label='wikipedia' title='wikipedia'>wikipedia</span>\n" \
|
||||
" <a class=\"book__link\" href=\"/ROOT%23%3F/content/zimfile%26other\" title=\"Preview\" aria-label=\"Preview\">\n" \
|
||||
" <div class=\"book__link__wrapper\">\n" \
|
||||
" <div class=\"book__icon\" style=background-image:url(/ROOT%23%3F/catalog/v2/illustration/charlesray/?size=48)></div>\n" \
|
||||
" <div class=\"book__header\">\n" \
|
||||
" <div id=\"book__title\">Charles, Ray</div>\n" \
|
||||
" </div>\n" \
|
||||
" <div class=\"book__description\" title=\"Wikipedia articles about Ray Charles\">Wikipedia articles about Ray Charles</div>\n" \
|
||||
" </div>\n" \
|
||||
" </a>\n" \
|
||||
" <div class=\"book__meta\">\n" \
|
||||
" <div class=\"book__languageTag\" >fra</div>\n" \
|
||||
" <div class=\"book__tags\"><div class=\"book__tags--wrapper\">\n" \
|
||||
" <span class=\"tag__link\" aria-label='unittest' title='unittest'>unittest</span>\n" \
|
||||
" <span class=\"tag__link\" aria-label='wikipedia' title='wikipedia'>wikipedia</span>\n" \
|
||||
" </div>\n" \
|
||||
" </div>\n" \
|
||||
" </div>\n" \
|
||||
" <div>\n" \
|
||||
" <a class=\"book__download\" href=\"/ROOT%23%3F/nojs/download/zimfile%26other\">\n" \
|
||||
" <img src=\"/ROOT%23%3F/skin/download-white.svg?cacheid=079ab989\">\n" \
|
||||
" <span>Download</span>\n" \
|
||||
" </a>\n" \
|
||||
" </div>\n" \
|
||||
" </div>\n" \
|
||||
" </div>\n"
|
||||
|
||||
#define RAY_CHARLES_BOOK_HTML \
|
||||
" <div class=\"book__wrapper\">\n" \
|
||||
" <div class=\"book__link__wrapper\">\n" \
|
||||
" <div class=\"book__icon\" style=background-image:url(/ROOT%23%3F/catalog/v2/illustration/raycharles/?size=48)></div>\n" \
|
||||
" <div class=\"book__header\">\n" \
|
||||
" <div id=\"book__title\"><a href=\"/ROOT%23%3F/content/zimfile_raycharles\">Ray Charles</a></div>\n" \
|
||||
" <div class=\"book__download\"><span><a href=\"/ROOT%23%3F/nojs/download/zimfile_raycharles\">Download</a></span></div>\n" \
|
||||
" </div>\n" \
|
||||
" <a class=\"book__link\" href=\"/ROOT%23%3F/content/zimfile_raycharles\" title=\"Preview\" aria-label=\"Preview\">\n" \
|
||||
" <div class=\"book__description\" title=\"Wikipedia articles about Ray Charles\">Wikipedia articles about Ray Charles</div>\n" \
|
||||
" </a>\n" \
|
||||
" </div>\n" \
|
||||
" <div class=\"book__languageTag\" >eng</div>\n" \
|
||||
" <div class=\"book__tags\"><div class=\"book__tags--wrapper\">\n" \
|
||||
" <span class=\"tag__link\" aria-label='public_tag_without_a_value' title='public_tag_without_a_value'>public_tag_without_a_value</span>\n" \
|
||||
" <span class=\"tag__link\" aria-label='wikipedia' title='wikipedia'>wikipedia</span>\n" \
|
||||
" <a class=\"book__link\" href=\"/ROOT%23%3F/content/zimfile_raycharles\" title=\"Preview\" aria-label=\"Preview\">\n" \
|
||||
" <div class=\"book__link__wrapper\">\n" \
|
||||
" <div class=\"book__icon\" style=background-image:url(/ROOT%23%3F/catalog/v2/illustration/raycharles/?size=48)></div>\n" \
|
||||
" <div class=\"book__header\">\n" \
|
||||
" <div id=\"book__title\">Ray Charles</div>\n" \
|
||||
" </div>\n" \
|
||||
" <div class=\"book__description\" title=\"Wikipedia articles about Ray Charles\">Wikipedia articles about Ray Charles</div>\n" \
|
||||
" </div>\n" \
|
||||
" </a>\n" \
|
||||
" <div class=\"book__meta\">\n" \
|
||||
" <div class=\"book__languageTag\" >eng</div>\n" \
|
||||
" <div class=\"book__tags\"><div class=\"book__tags--wrapper\">\n" \
|
||||
" <span class=\"tag__link\" aria-label='public_tag_without_a_value' title='public_tag_without_a_value'>public_tag_without_a_value</span>\n" \
|
||||
" <span class=\"tag__link\" aria-label='wikipedia' title='wikipedia'>wikipedia</span>\n" \
|
||||
" </div>\n" \
|
||||
" </div>\n" \
|
||||
" </div>\n" \
|
||||
" <div>\n" \
|
||||
" <a class=\"book__download\" href=\"/ROOT%23%3F/nojs/download/zimfile_raycharles\">\n" \
|
||||
" <img src=\"/ROOT%23%3F/skin/download-white.svg?cacheid=079ab989\">\n" \
|
||||
" <span>Download</span>\n" \
|
||||
" </a>\n" \
|
||||
" </div>\n" \
|
||||
" </div>\n" \
|
||||
" </div>\n"
|
||||
|
||||
#define RAY_CHARLES_UNCTZ_BOOK_HTML \
|
||||
" <div class=\"book__wrapper\">\n" \
|
||||
" <div class=\"book__link__wrapper\">\n" \
|
||||
" <div class=\"book__icon\" style=background-image:url(/ROOT%23%3F/catalog/v2/illustration/raycharles_uncategorized/?size=48)></div>\n" \
|
||||
" <div class=\"book__header\">\n" \
|
||||
" <div id=\"book__title\"><a href=\"/ROOT%23%3F/content/zimfile_raycharles_uncategorized\">Ray (uncategorized) Charles</a></div>\n" \
|
||||
" <div class=\"book__download\"><span><a href=\"/ROOT%23%3F/nojs/download/zimfile_raycharles_uncategorized\">Download</a></span></div>\n" \
|
||||
" </div>\n" \
|
||||
" <a class=\"book__link\" href=\"/ROOT%23%3F/content/zimfile_raycharles_uncategorized\" title=\"Preview\" aria-label=\"Preview\">\n" \
|
||||
" <div class=\"book__description\" title=\"No category is assigned to this library entry.\">No category is assigned to this library entry.</div>\n" \
|
||||
" </a>\n" \
|
||||
" </div>\n" \
|
||||
" <div class=\"book__languageTag\" >rus,eng</div>\n" \
|
||||
" <div class=\"book__tags\"><div class=\"book__tags--wrapper\">\n" \
|
||||
" <span class=\"tag__link\" aria-label='public_tag_with_a_value:value_of_a_public_tag' title='public_tag_with_a_value:value_of_a_public_tag'>public_tag_with_a_value:value_of_a_public_tag</span>\n" \
|
||||
" <span class=\"tag__link\" aria-label='wikipedia' title='wikipedia'>wikipedia</span>\n" \
|
||||
" <a class=\"book__link\" href=\"/ROOT%23%3F/content/zimfile_raycharles_uncategorized\" title=\"Preview\" aria-label=\"Preview\">\n" \
|
||||
" <div class=\"book__link__wrapper\">\n" \
|
||||
" <div class=\"book__icon\" style=background-image:url(/ROOT%23%3F/catalog/v2/illustration/raycharles_uncategorized/?size=48)></div>\n" \
|
||||
" <div class=\"book__header\">\n" \
|
||||
" <div id=\"book__title\">Ray (uncategorized) Charles</div>\n" \
|
||||
" </div>\n" \
|
||||
" <div class=\"book__description\" title=\"No category is assigned to this library entry.\">No category is assigned to this library entry.</div>\n" \
|
||||
" </div>\n" \
|
||||
" </a>\n" \
|
||||
" <div class=\"book__meta\">\n" \
|
||||
" <div class=\"book__languageTag\" >rus,eng</div>\n" \
|
||||
" <div class=\"book__tags\"><div class=\"book__tags--wrapper\">\n" \
|
||||
" <span class=\"tag__link\" aria-label='public_tag_with_a_value:value_of_a_public_tag' title='public_tag_with_a_value:value_of_a_public_tag'>public_tag_with_a_value:value_of_a_public_tag</span>\n" \
|
||||
" <span class=\"tag__link\" aria-label='wikipedia' title='wikipedia'>wikipedia</span>\n" \
|
||||
" </div>\n" \
|
||||
" </div>\n" \
|
||||
" </div>\n" \
|
||||
" <div>\n" \
|
||||
" <a class=\"book__download\" href=\"/ROOT%23%3F/nojs/download/zimfile_raycharles_uncategorized\">\n" \
|
||||
" <img src=\"/ROOT%23%3F/skin/download-white.svg?cacheid=079ab989\">\n" \
|
||||
" <span>Download</span>\n" \
|
||||
" </a>\n" \
|
||||
" </div>\n" \
|
||||
" </div>\n" \
|
||||
" </div>\n"
|
||||
|
||||
#define FINAL_HTML_TEXT \
|
||||
|
||||
@@ -25,11 +25,23 @@ TEST(ManagerTest, addBookFromPathAndGetIdTest)
|
||||
EXPECT_EQ(book.getUrl(), url);
|
||||
}
|
||||
|
||||
|
||||
|
||||
#if _WIN32
|
||||
# define UNITTEST_ZIM_PATH "zimfiles\\unittest.zim"
|
||||
# define LIB_ABS_PATH "C:\\data\\lib.xml"
|
||||
# define ZIM_ABS_PATH "C:\\data\\zimfiles\\unittest.zim"
|
||||
#else
|
||||
# define UNITTEST_ZIM_PATH "zimfiles/unittest.zim"
|
||||
# define LIB_ABS_PATH "/data/lib.xml"
|
||||
# define ZIM_ABS_PATH "/data/zimfiles/unittest.zim"
|
||||
#endif
|
||||
|
||||
const char sampleLibraryXML[] = R"(
|
||||
<library version="1.0">
|
||||
<book
|
||||
id="0d0bcd57-d3f6-cb22-44cc-a723ccb4e1b2"
|
||||
path="zimfiles/unittest.zim"
|
||||
path=")" UNITTEST_ZIM_PATH R"("
|
||||
url="https://example.com/zimfiles/unittest.zim"
|
||||
title="Unit Test"
|
||||
description="Wikipedia articles about unit testing"
|
||||
@@ -51,9 +63,9 @@ TEST(ManagerTest, readXml)
|
||||
auto lib = kiwix::Library::create();
|
||||
kiwix::Manager manager = kiwix::Manager(lib);
|
||||
|
||||
EXPECT_EQ(true, manager.readXml(sampleLibraryXML, true, "/data/lib.xml", true));
|
||||
EXPECT_EQ(true, manager.readXml(sampleLibraryXML, true, LIB_ABS_PATH, true));
|
||||
kiwix::Book book = lib->getBookById("0d0bcd57-d3f6-cb22-44cc-a723ccb4e1b2");
|
||||
EXPECT_EQ("/data/zimfiles/unittest.zim", book.getPath());
|
||||
EXPECT_EQ(ZIM_ABS_PATH, book.getPath());
|
||||
EXPECT_EQ("https://example.com/zimfiles/unittest.zim", book.getUrl());
|
||||
EXPECT_EQ("Unit Test", book.getTitle());
|
||||
EXPECT_EQ("Wikipedia articles about unit testing", book.getDescription());
|
||||
|
||||
@@ -7,6 +7,23 @@
|
||||
namespace
|
||||
{
|
||||
|
||||
#if _WIN32
|
||||
const char libraryXML[] = R"(
|
||||
<library version="1.0">
|
||||
<book id="01" path="C:\data\zero_one.zim"> </book>
|
||||
<book id="02" path="C:\data\zero two.zim"> </book>
|
||||
<book id="03" path="C:\data\ZERO thrêë.zim"> </book>
|
||||
<book id="04-2021-10" path="C:\data\zero_four_2021-10.zim"></book>
|
||||
<book id="04-2021-11" path="C:\data\zero_four_2021-11.zim"></book>
|
||||
<book id="05-a" path="C:\data\zero_five-a.zim" name="zero_five"></book>
|
||||
<book id="05-b" path="C:\data\zero_five-b.zim" name="zero_five"></book>
|
||||
<book id="06+" path="C:\data\zërô + SIX.zim"></book>
|
||||
<book id="06plus" path="C:\data\zero_plus_six.zim"></book>
|
||||
<book id="07-super" path="C:\data\zero_seven.zim"></book>
|
||||
<book id="07-sub" path="C:\data\subdir\zero_seven.zim"></book>
|
||||
</library>
|
||||
)";
|
||||
#else
|
||||
const char libraryXML[] = R"(
|
||||
<library version="1.0">
|
||||
<book id="01" path="/data/zero_one.zim"> </book>
|
||||
@@ -22,6 +39,7 @@ const char libraryXML[] = R"(
|
||||
<book id="07-sub" path="/data/subdir/zero_seven.zim"></book>
|
||||
</library>
|
||||
)";
|
||||
#endif
|
||||
|
||||
class NameMapperTest : public ::testing::Test {
|
||||
public:
|
||||
@@ -61,7 +79,22 @@ public:
|
||||
operator std::string() const { return buffer.str(); }
|
||||
};
|
||||
|
||||
#if _WIN32
|
||||
const std::string ZERO_FOUR_NAME_CONFLICT_MSG =
|
||||
"Path collision: 'C:\\data\\zero_four_2021-10.zim' and"
|
||||
" 'C:\\data\\zero_four_2021-11.zim' can't share the same URL path 'zero_four'."
|
||||
" Therefore, only 'C:\\data\\zero_four_2021-10.zim' will be served.\n";
|
||||
|
||||
const std::string ZERO_SIX_NAME_CONFLICT_MSG =
|
||||
"Path collision: 'C:\\data\\zërô + SIX.zim' and "
|
||||
"'C:\\data\\zero_plus_six.zim' can't share the same URL path 'zero_plus_six'."
|
||||
" Therefore, only 'C:\\data\\zërô + SIX.zim' will be served.\n";
|
||||
|
||||
const std::string ZERO_SEVEN_NAME_CONFLICT_MSG =
|
||||
"Path collision: 'C:\\data\\subdir\\zero_seven.zim' and"
|
||||
" 'C:\\data\\zero_seven.zim' can't share the same URL path 'zero_seven'."
|
||||
" Therefore, only 'C:\\data\\subdir\\zero_seven.zim' will be served.\n";
|
||||
#else
|
||||
const std::string ZERO_FOUR_NAME_CONFLICT_MSG =
|
||||
"Path collision: '/data/zero_four_2021-10.zim' and"
|
||||
" '/data/zero_four_2021-11.zim' can't share the same URL path 'zero_four'."
|
||||
@@ -76,6 +109,7 @@ const std::string ZERO_SEVEN_NAME_CONFLICT_MSG =
|
||||
"Path collision: '/data/subdir/zero_seven.zim' and"
|
||||
" '/data/zero_seven.zim' can't share the same URL path 'zero_seven'."
|
||||
" Therefore, only '/data/subdir/zero_seven.zim' will be served.\n";
|
||||
#endif
|
||||
|
||||
// Name conflicts in the default mode (without the --nodatealiases is off
|
||||
const std::string DEFAULT_NAME_CONFLICTS = ZERO_SIX_NAME_CONFLICT_MSG
|
||||
|
||||
@@ -18,9 +18,10 @@
|
||||
*/
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "../include/tools.h"
|
||||
#include "../src/tools/otherTools.h"
|
||||
#include "zim/suggestion_iterator.h"
|
||||
#include "../src/server/i18n.h"
|
||||
#include "../src/server/i18n_utils.h"
|
||||
|
||||
#include <regex>
|
||||
|
||||
@@ -252,11 +253,14 @@ TEST(networkTools, getNetworkInterfaces)
|
||||
}
|
||||
}
|
||||
|
||||
TEST(networkTools, getBestPublicIp)
|
||||
TEST(networkTools, getBestPublicIps)
|
||||
{
|
||||
using kiwix::getBestPublicIps;
|
||||
using kiwix::getBestPublicIp;
|
||||
using kiwix::IpMode;
|
||||
|
||||
std::cout << "getBestPublicIp(true) " << getBestPublicIp(true) << std::endl;
|
||||
std::cout << "getBestPublicIp(false) " << getBestPublicIp(false) << std::endl;
|
||||
std::cout << "getBestPublicIp() " << getBestPublicIp() << std::endl;
|
||||
std::cout << "getBestPublicIps(true) : [" << getBestPublicIps(IpMode::ipv4).addr << ", " << getBestPublicIps(IpMode::ipv4).addr6 << "]" << std::endl;
|
||||
std::cout << "getBestPublicIps(false) : [" << getBestPublicIps(IpMode::ipv6).addr << ", " << getBestPublicIps(IpMode::ipv6).addr6 << "]" << std::endl;
|
||||
std::cout << "getBestPublicIps(false) : [" << getBestPublicIps(IpMode::all).addr << ", " << getBestPublicIps(IpMode::all).addr6 << "]" << std::endl;
|
||||
std::cout << "getBestPublicIp() : " << getBestPublicIp() << std::endl;
|
||||
}
|
||||
|
||||
@@ -61,9 +61,9 @@ const ResourceCollection resources200Compressible{
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/i18n.js" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/i18n.js?cacheid=071abc9a" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/index.css" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.css?cacheid=1e78e7cf" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.css?cacheid=ae79e41a" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/index.js" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.js?cacheid=f43eb0b9" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.js?cacheid=8f4b6a1e" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/iso6391To3.js" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/iso6391To3.js?cacheid=ecde2bb3" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/isotope.pkgd.min.js" },
|
||||
@@ -75,7 +75,7 @@ const ResourceCollection resources200Compressible{
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/taskbar.css" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/taskbar.css?cacheid=80d56607" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/viewer.js" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/viewer.js?cacheid=5fc4badf" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/viewer.js?cacheid=aca897b0" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/fonts/Poppins.ttf" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/fonts/Poppins.ttf?cacheid=af705837" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/fonts/Roboto.ttf" },
|
||||
@@ -114,6 +114,8 @@ const ResourceCollection resources200Uncompressible{
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/caret.png?cacheid=22b942b4" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/download.png" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/download.png?cacheid=a39aa502" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/download-white.svg" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/download-white.svg?cacheid=079ab989"},
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/favicon/android-chrome-192x192.png" },
|
||||
{ STATIC_CONTENT, "/ROOT%23%3F/skin/favicon/android-chrome-192x192.png?cacheid=bfac158b" },
|
||||
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/favicon/android-chrome-512x512.png" },
|
||||
@@ -279,7 +281,7 @@ TEST_F(ServerTest, CacheIdsOfStaticResources)
|
||||
{
|
||||
/* url */ "/ROOT%23%3F/",
|
||||
R"EXPECTEDRESULT( href="/ROOT%23%3F/skin/kiwix.css?cacheid=2158fad9"
|
||||
href="/ROOT%23%3F/skin/index.css?cacheid=1e78e7cf"
|
||||
href="/ROOT%23%3F/skin/index.css?cacheid=ae79e41a"
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/ROOT%23%3F/skin/favicon/apple-touch-icon.png?cacheid=f86f8df3">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/ROOT%23%3F/skin/favicon/favicon-32x32.png?cacheid=79ded625">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/ROOT%23%3F/skin/favicon/favicon-16x16.png?cacheid=a986fedc">
|
||||
@@ -292,7 +294,7 @@ R"EXPECTEDRESULT( href="/ROOT%23%3F/skin/kiwix.css?cacheid=2158fad9"
|
||||
<script type="text/javascript" src="/ROOT%23%3F/skin/languages.js?cacheid=ee7d95b5" defer></script>
|
||||
<script src="/ROOT%23%3F/skin/isotope.pkgd.min.js?cacheid=2e48d392" defer></script>
|
||||
<script src="/ROOT%23%3F/skin/iso6391To3.js?cacheid=ecde2bb3"></script>
|
||||
<script type="text/javascript" src="/ROOT%23%3F/skin/index.js?cacheid=f43eb0b9" defer></script>
|
||||
<script type="text/javascript" src="/ROOT%23%3F/skin/index.js?cacheid=8f4b6a1e" defer></script>
|
||||
<img src="/ROOT%23%3F/skin/feed.svg?cacheid=055b333f"
|
||||
<img src="/ROOT%23%3F/skin/langSelector.svg?cacheid=00b59961"
|
||||
)EXPECTEDRESULT"
|
||||
@@ -310,7 +312,8 @@ R"EXPECTEDRESULT( background-image: url('../skin/search-icon.svg?cacheid=b10a
|
||||
},
|
||||
{
|
||||
/* url */ "/ROOT%23%3F/skin/index.js",
|
||||
R"EXPECTEDRESULT( <img src="${root}/skin/download.png?cacheid=a39aa502" alt="${$t("direct-download-alt-text")}" />
|
||||
R"EXPECTEDRESULT( <img src="${root}/skin/download-white.svg?cacheid=079ab989">
|
||||
<img src="${root}/skin/download.png?cacheid=a39aa502" alt="${$t("direct-download-alt-text")}" />
|
||||
<img src="${root}/skin/hash.png?cacheid=f836e872" alt="${$t("hash-download-alt-text")}" />
|
||||
<img src="${root}/skin/magnet.png?cacheid=73b6bddf" alt="${$t("magnet-alt-text")}" />
|
||||
<img src="${root}/skin/bittorrent.png?cacheid=4f5c6882" alt="${$t("torrent-download-alt-text")}" />
|
||||
@@ -324,7 +327,7 @@ R"EXPECTEDRESULT( <link type="text/css" href="./skin/kiwix.css?cacheid=2158fa
|
||||
<script type="text/javascript" src="./skin/polyfills.js?cacheid=a0e0343d"></script>
|
||||
<script type="module" src="./skin/i18n.js?cacheid=071abc9a" defer></script>
|
||||
<script type="text/javascript" src="./skin/languages.js?cacheid=ee7d95b5" defer></script>
|
||||
<script type="text/javascript" src="./skin/viewer.js?cacheid=5fc4badf" defer></script>
|
||||
<script type="text/javascript" src="./skin/viewer.js?cacheid=aca897b0" defer></script>
|
||||
<script type="text/javascript" src="./skin/autoComplete/autoComplete.min.js?cacheid=1191aaaf"></script>
|
||||
const blankPageUrl = root + "/skin/blank.html?cacheid=6b1fa032";
|
||||
<label for="kiwix_button_show_toggle"><img src="./skin/caret.png?cacheid=22b942b4" alt=""></label>
|
||||
@@ -356,6 +359,57 @@ R"EXPECTEDRESULT( <link type="text/css" href="/ROOT%23%3F/skin/search_results
|
||||
}
|
||||
}
|
||||
|
||||
std::string getCacheIdFromUrl(const std::string& url)
|
||||
{
|
||||
const std::string q("?cacheid=");
|
||||
const auto i = url.find(q);
|
||||
return i == std::string::npos ? "" : url.substr(i + q.size());
|
||||
}
|
||||
|
||||
std::string runExternalCmdAndGetItsOutput(const std::string& cmd)
|
||||
{
|
||||
std::string cmdOutput;
|
||||
|
||||
#ifdef _WIN32
|
||||
#define popen _popen
|
||||
#define pclose _pclose
|
||||
#endif
|
||||
|
||||
if (FILE* pPipe = popen(cmd.c_str(), "r"))
|
||||
{
|
||||
char buf[128];
|
||||
while (fgets(buf, 128, pPipe)) {
|
||||
cmdOutput += std::string(buf, buf+128);
|
||||
}
|
||||
|
||||
pclose(pPipe);
|
||||
}
|
||||
|
||||
return cmdOutput;
|
||||
}
|
||||
|
||||
std::string getSha1OfResponseData(const std::string& url)
|
||||
{
|
||||
const std::string pythonScript =
|
||||
"import urllib.request as req; "
|
||||
"import hashlib; "
|
||||
"print(hashlib.sha1(req.urlopen('" + url + "').read()).hexdigest())";
|
||||
const std::string cmd = "python3 -c \"" + pythonScript + "\"";
|
||||
return runExternalCmdAndGetItsOutput(cmd);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, CacheIdsOfStaticResourcesMatchTheSha1HashOfResourceContent)
|
||||
{
|
||||
for ( const Resource& res : all200Resources() ) {
|
||||
if ( res.kind == STATIC_CONTENT ) {
|
||||
const TestContext ctx{ {"url", res.url} };
|
||||
const std::string fullUrl = "http://localhost:" + std::to_string(SERVER_PORT) + res.url;
|
||||
const std::string sha1 = getSha1OfResponseData(fullUrl);
|
||||
ASSERT_EQ(sha1.substr(0, 8), getCacheIdFromUrl(res.url)) << ctx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char* urls400[] = {
|
||||
"/ROOT%23%3F/search",
|
||||
"/ROOT%23%3F/search?content=zimfile",
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "../src/tools/stringTools.h"
|
||||
#include "../include/tools.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -170,4 +171,17 @@ TEST(stringTools, stripSuffix)
|
||||
EXPECT_EQ(stripSuffix("abc123", "987"), "abc123");
|
||||
}
|
||||
|
||||
TEST(stringTools, getSlugifiedFileName)
|
||||
{
|
||||
EXPECT_EQ(getSlugifiedFileName("abc123.png"), "abc123.png");
|
||||
EXPECT_EQ(getSlugifiedFileName("/"), "_");
|
||||
EXPECT_EQ(getSlugifiedFileName("abc/123.pdf"), "abc_123.pdf");
|
||||
EXPECT_EQ(getSlugifiedFileName("abc//123.yaml"), "abc__123.yaml");
|
||||
EXPECT_EQ(getSlugifiedFileName("//abc//123//"), "__abc__123__");
|
||||
#ifdef _WIN32
|
||||
EXPECT_EQ(getSlugifiedFileName(R"(<>:"/\\|?*)"), "__________");
|
||||
EXPECT_EQ(getSlugifiedFileName(R"(<abc>:"/123\\|?*<.txt>)"), "_abc____123______.txt_");
|
||||
#endif
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user