mirror of https://github.com/kiwix/libkiwix.git
commit
6e13d44459
|
@ -51,8 +51,10 @@ string appendToDirectory(const string& directoryPath, const string& filename);
|
||||||
|
|
||||||
unsigned int getFileSize(const string& path);
|
unsigned int getFileSize(const string& path);
|
||||||
string getFileSizeAsString(const string& path);
|
string getFileSizeAsString(const string& path);
|
||||||
|
string getFileContent(const string& path);
|
||||||
bool fileExists(const string& path);
|
bool fileExists(const string& path);
|
||||||
bool makeDirectory(const string& path);
|
bool makeDirectory(const string& path);
|
||||||
|
string makeTmpDirectory();
|
||||||
bool copyFile(const string& sourcePath, const string& destPath);
|
bool copyFile(const string& sourcePath, const string& destPath);
|
||||||
string getLastPathElement(const string& path);
|
string getLastPathElement(const string& path);
|
||||||
string getExecutablePath();
|
string getExecutablePath();
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 Matthieu Gautier <mgautier@kymeria.fr>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 3 of the License, or
|
||||||
|
* any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||||
|
* MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef KIWIX_DOWNLOADER_H
|
||||||
|
#define KIWIX_DOWNLOADER_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <aria2/aria2.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
namespace kiwix
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
struct DownloadedFile {
|
||||||
|
DownloadedFile()
|
||||||
|
: success(false) {}
|
||||||
|
bool success;
|
||||||
|
std::string path;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A tool to download things.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class Downloader
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Downloader();
|
||||||
|
~Downloader();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download a content.
|
||||||
|
*
|
||||||
|
* @param url the url to download
|
||||||
|
* @return the content downloaded.
|
||||||
|
*/
|
||||||
|
DownloadedFile download(const std::string& url);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static pthread_mutex_t globalLock;
|
||||||
|
|
||||||
|
aria2::Session* session;
|
||||||
|
DownloadedFile* fileHandle;
|
||||||
|
std::string tmpDir;
|
||||||
|
|
||||||
|
static int downloadEventCallback(aria2::Session* session,
|
||||||
|
aria2::DownloadEvent event,
|
||||||
|
aria2::A2Gid gid,
|
||||||
|
void* userData);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -84,10 +84,21 @@ class Manager
|
||||||
* @param libraryPath The library path (used to resolve relative path)
|
* @param libraryPath The library path (used to resolve relative path)
|
||||||
* @return True if the content has been properly parsed.
|
* @return True if the content has been properly parsed.
|
||||||
*/
|
*/
|
||||||
bool readXml(const string xml,
|
bool readXml(const string& xml,
|
||||||
const bool readOnly = true,
|
const bool readOnly = true,
|
||||||
const string libraryPath = "");
|
const string libraryPath = "");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a library content stored in a OPDS stream.
|
||||||
|
*
|
||||||
|
* @param content The content of the OPDS stream.
|
||||||
|
* @param readOnly Set if the library path could be overwritten later with
|
||||||
|
* updated content.
|
||||||
|
* @param libraryPath The library path (used to resolve relative path)
|
||||||
|
* @return True if the content has been properly parsed.
|
||||||
|
*/
|
||||||
|
bool readOpds(const string& content, const std::string& urlHost);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write the library to a file.
|
* Write the library to a file.
|
||||||
*
|
*
|
||||||
|
@ -97,8 +108,6 @@ class Manager
|
||||||
bool writeFile(const string path);
|
bool writeFile(const string path);
|
||||||
|
|
||||||
|
|
||||||
string write_OPDS_feed(const string& id, const string& title);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a book from the library.
|
* Remove a book from the library.
|
||||||
*
|
*
|
||||||
|
@ -256,6 +265,16 @@ class Manager
|
||||||
const string creator,
|
const string creator,
|
||||||
const string publisher,
|
const string publisher,
|
||||||
const string search);
|
const string search);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter the library and generate a new one with the keep elements.
|
||||||
|
*
|
||||||
|
* @param search List only books with search in the title or description.
|
||||||
|
* @return A `Library`.
|
||||||
|
*/
|
||||||
|
Library filter(const string& search);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all langagues of the books in the library.
|
* Get all langagues of the books in the library.
|
||||||
*
|
*
|
||||||
|
@ -295,6 +314,8 @@ class Manager
|
||||||
bool parseXmlDom(const pugi::xml_document& doc,
|
bool parseXmlDom(const pugi::xml_document& doc,
|
||||||
const bool readOnly,
|
const bool readOnly,
|
||||||
const string libraryPath);
|
const string libraryPath);
|
||||||
|
bool parseOpdsDom(const pugi::xml_document& doc,
|
||||||
|
const std::string& urlHost);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void checkAndCleanBookPaths(Book& book, const string& libraryPath);
|
void checkAndCleanBookPaths(Book& book, const string& libraryPath);
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
headers = [
|
headers = [
|
||||||
'library.h',
|
'library.h',
|
||||||
'manager.h',
|
'manager.h',
|
||||||
|
'opds_dumper.h',
|
||||||
|
'downloader.h',
|
||||||
'reader.h',
|
'reader.h',
|
||||||
'searcher.h'
|
'searcher.h'
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2017 Matthieu Gautier <mgautier@kymeria.fr>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 3 of the License, or
|
||||||
|
* any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||||
|
* MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef KIWIX_OPDS_DUMPER_H
|
||||||
|
#define KIWIX_OPDS_DUMPER_H
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <pugixml.hpp>
|
||||||
|
|
||||||
|
#include "common/base64.h"
|
||||||
|
#include "common/pathTools.h"
|
||||||
|
#include "common/regexTools.h"
|
||||||
|
#include "library.h"
|
||||||
|
#include "reader.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
namespace kiwix
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A tool to dump a `Library` into a opds stream.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class OPDSDumper
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
OPDSDumper() = default;
|
||||||
|
OPDSDumper(Library library);
|
||||||
|
~OPDSDumper();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dump the OPDS feed.
|
||||||
|
*
|
||||||
|
* @param id The id of the library.
|
||||||
|
* @return The OPDS feed.
|
||||||
|
*/
|
||||||
|
std::string dumpOPDSFeed();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the id of the opds stream.
|
||||||
|
*
|
||||||
|
* @param id the id to use.
|
||||||
|
*/
|
||||||
|
void setId(const std::string& id) { this->id = id;}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the title oft the opds stream.
|
||||||
|
*
|
||||||
|
* @param title the title to use.
|
||||||
|
*/
|
||||||
|
void setTitle(const std::string& title) { this->title = title; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the root location used when generating url.
|
||||||
|
*
|
||||||
|
* @param rootLocation the root location to use.
|
||||||
|
*/
|
||||||
|
void setRootLocation(const std::string& rootLocation) { this->rootLocation = rootLocation; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the search url.
|
||||||
|
*
|
||||||
|
* @param searchUrl the search url to use.
|
||||||
|
*/
|
||||||
|
void setSearchDescriptionUrl(const std::string& searchDescriptionUrl) { this->searchDescriptionUrl = searchDescriptionUrl; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the library to dump.
|
||||||
|
*
|
||||||
|
* @param library The library to dump.
|
||||||
|
*/
|
||||||
|
void setLibrary(Library library) { this->library = library; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
kiwix::Library library;
|
||||||
|
std::string id;
|
||||||
|
std::string title;
|
||||||
|
std::string date;
|
||||||
|
std::string rootLocation;
|
||||||
|
std::string searchDescriptionUrl;
|
||||||
|
|
||||||
|
private:
|
||||||
|
pugi::xml_node handleBook(Book book, pugi::xml_node root_node);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // KIWIX_OPDS_DUMPER_H
|
|
@ -12,6 +12,7 @@ thread_dep = dependency('threads')
|
||||||
libicu_dep = dependency('icu-i18n', static:static_deps)
|
libicu_dep = dependency('icu-i18n', static:static_deps)
|
||||||
libzim_dep = dependency('libzim', version : '>=3.2.0', static:static_deps)
|
libzim_dep = dependency('libzim', version : '>=3.2.0', static:static_deps)
|
||||||
pugixml_dep = dependency('pugixml', static:static_deps)
|
pugixml_dep = dependency('pugixml', static:static_deps)
|
||||||
|
libaria2_dep = dependency('libaria2', static:static_deps)
|
||||||
|
|
||||||
ctpp2_include_path = ''
|
ctpp2_include_path = ''
|
||||||
has_ctpp2_dep = false
|
has_ctpp2_dep = false
|
||||||
|
@ -72,7 +73,7 @@ endif
|
||||||
|
|
||||||
xapian_dep = dependency('xapian-core', required:false, static:static_deps)
|
xapian_dep = dependency('xapian-core', required:false, static:static_deps)
|
||||||
|
|
||||||
all_deps = [thread_dep, libicu_dep, libzim_dep, xapian_dep, pugixml_dep]
|
all_deps = [thread_dep, libicu_dep, libzim_dep, xapian_dep, pugixml_dep, libaria2_dep]
|
||||||
if has_ctpp2_dep
|
if has_ctpp2_dep
|
||||||
all_deps += [ctpp2_dep]
|
all_deps += [ctpp2_dep]
|
||||||
endif
|
endif
|
||||||
|
@ -88,7 +89,7 @@ subdir('scripts')
|
||||||
subdir('static')
|
subdir('static')
|
||||||
subdir('src')
|
subdir('src')
|
||||||
|
|
||||||
pkg_requires = ['libzim', 'icu-i18n', 'pugixml']
|
pkg_requires = ['libzim', 'icu-i18n', 'pugixml', 'libaria2']
|
||||||
if xapian_dep.found()
|
if xapian_dep.found()
|
||||||
pkg_requires += ['xapian-core']
|
pkg_requires += ['xapian-core']
|
||||||
endif
|
endif
|
||||||
|
|
|
@ -188,6 +188,20 @@ string getFileSizeAsString(const string& path)
|
||||||
return convert.str();
|
return convert.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string getFileContent(const string& path)
|
||||||
|
{
|
||||||
|
std::ifstream f(path, std::ios::in|std::ios::ate);
|
||||||
|
std::string content;
|
||||||
|
if (f.is_open()) {
|
||||||
|
auto size = f.tellg();
|
||||||
|
content.reserve(size);
|
||||||
|
f.seekg(0, std::ios::beg);
|
||||||
|
content.assign((std::istreambuf_iterator<char>(f)),
|
||||||
|
std::istreambuf_iterator<char>());
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
bool fileExists(const string& path)
|
bool fileExists(const string& path)
|
||||||
{
|
{
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
@ -214,6 +228,30 @@ bool makeDirectory(const string& path)
|
||||||
return status == 0;
|
return status == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string makeTmpDirectory()
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
char cbase[MAX_PATH+1];
|
||||||
|
int base_len = GetTempPath(MAX_PATH+1, cbase);
|
||||||
|
UUID uuid;
|
||||||
|
UuidCreate(&uuid);
|
||||||
|
char* dir_name;
|
||||||
|
UuidToString(&uuid, reinterpret_cast<unsigned char**>(&dir_name));
|
||||||
|
string dir(cbase, base_len);
|
||||||
|
dir += dir_name;
|
||||||
|
_mkdir(dir.c_str());
|
||||||
|
RpcStringFree(reinterpret_cast<unsigned char**>(&dir_name));
|
||||||
|
#else
|
||||||
|
string base = "/tmp";
|
||||||
|
auto _template = base + "/kiwix-lib_XXXXXX";
|
||||||
|
char* _template_array = new char[_template.size()+1];
|
||||||
|
memcpy(_template_array, _template.c_str(), _template.size());
|
||||||
|
string dir = mkdtemp(_template_array);
|
||||||
|
delete[] _template_array;
|
||||||
|
#endif
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
|
||||||
/* Try to create a link and if does not work then make a copy */
|
/* Try to create a link and if does not work then make a copy */
|
||||||
bool copyFile(const string& sourcePath, const string& destPath)
|
bool copyFile(const string& sourcePath, const string& destPath)
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,112 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 Matthieu Gautier <mgautier@kymeria.fr>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 3 of the License, or
|
||||||
|
* any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||||
|
* MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "downloader.h"
|
||||||
|
#include "common/pathTools.h"
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace kiwix
|
||||||
|
{
|
||||||
|
|
||||||
|
pthread_mutex_t Downloader::globalLock = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
|
||||||
|
|
||||||
|
/* Constructor */
|
||||||
|
Downloader::Downloader()
|
||||||
|
{
|
||||||
|
aria2::SessionConfig config;
|
||||||
|
config.downloadEventCallback = Downloader::downloadEventCallback;
|
||||||
|
config.userData = this;
|
||||||
|
tmpDir = makeTmpDirectory();
|
||||||
|
aria2::KeyVals options;
|
||||||
|
options.push_back(std::pair<std::string, std::string>("dir", tmpDir));
|
||||||
|
session = aria2::sessionNew(options, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Destructor */
|
||||||
|
Downloader::~Downloader()
|
||||||
|
{
|
||||||
|
aria2::sessionFinal(session);
|
||||||
|
rmdir(tmpDir.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int Downloader::downloadEventCallback(aria2::Session* session,
|
||||||
|
aria2::DownloadEvent event,
|
||||||
|
aria2::A2Gid gid,
|
||||||
|
void* userData)
|
||||||
|
{
|
||||||
|
Downloader* downloader = static_cast<Downloader*>(userData);
|
||||||
|
|
||||||
|
auto fileHandle = downloader->fileHandle;
|
||||||
|
auto dh = aria2::getDownloadHandle(session, gid);
|
||||||
|
|
||||||
|
if (!dh) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (event) {
|
||||||
|
case aria2::EVENT_ON_DOWNLOAD_COMPLETE:
|
||||||
|
{
|
||||||
|
if (dh->getNumFiles() > 0) {
|
||||||
|
auto f = dh->getFile(1);
|
||||||
|
fileHandle->path = f.path;
|
||||||
|
fileHandle->success = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case aria2::EVENT_ON_DOWNLOAD_ERROR:
|
||||||
|
{
|
||||||
|
fileHandle->success = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
aria2::deleteDownloadHandle(dh);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
DownloadedFile Downloader::download(const std::string& url) {
|
||||||
|
pthread_mutex_lock(&globalLock);
|
||||||
|
DownloadedFile fileHandle;
|
||||||
|
try {
|
||||||
|
std::vector<std::string> uris = {url};
|
||||||
|
aria2::KeyVals options;
|
||||||
|
aria2::A2Gid gid;
|
||||||
|
int ret;
|
||||||
|
DownloadedFile fileHandle;
|
||||||
|
|
||||||
|
ret = aria2::addUri(session, &gid, uris, options);
|
||||||
|
if (ret < 0) {
|
||||||
|
std::cerr << "Failed to download" << std::endl;
|
||||||
|
} else {
|
||||||
|
this->fileHandle = &fileHandle;
|
||||||
|
aria2::run(session, aria2::RUN_DEFAULT);
|
||||||
|
}
|
||||||
|
} catch (...) {};
|
||||||
|
this->fileHandle = nullptr;
|
||||||
|
pthread_mutex_unlock(&globalLock);
|
||||||
|
return fileHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -18,6 +18,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "manager.h"
|
#include "manager.h"
|
||||||
|
#include "downloader.h"
|
||||||
|
|
||||||
namespace kiwix
|
namespace kiwix
|
||||||
{
|
{
|
||||||
|
@ -88,7 +89,7 @@ bool Manager::parseXmlDom(const pugi::xml_document& doc,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Manager::readXml(const string xml,
|
bool Manager::readXml(const string& xml,
|
||||||
const bool readOnly,
|
const bool readOnly,
|
||||||
const string libraryPath)
|
const string libraryPath)
|
||||||
{
|
{
|
||||||
|
@ -103,6 +104,67 @@ bool Manager::readXml(const string xml,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool Manager::parseOpdsDom(const pugi::xml_document& doc, const std::string& urlHost)
|
||||||
|
{
|
||||||
|
pugi::xml_node libraryNode = doc.child("feed");
|
||||||
|
|
||||||
|
for (pugi::xml_node entryNode = libraryNode.child("entry"); entryNode;
|
||||||
|
entryNode = entryNode.next_sibling("entry")) {
|
||||||
|
kiwix::Book book;
|
||||||
|
|
||||||
|
book.readOnly = false;
|
||||||
|
book.id = entryNode.child("id").child_value();
|
||||||
|
book.title = entryNode.child("title").child_value();
|
||||||
|
book.description = entryNode.child("summary").child_value();
|
||||||
|
book.language = entryNode.child("language").child_value();
|
||||||
|
book.date = entryNode.child("updated").child_value();
|
||||||
|
book.creator = entryNode.child("author").child("name").child_value();
|
||||||
|
for(pugi::xml_node linkNode = entryNode.child("link"); linkNode;
|
||||||
|
linkNode = linkNode.next_sibling("link")) {
|
||||||
|
std::string rel = linkNode.attribute("rel").value();
|
||||||
|
|
||||||
|
if (rel == "http://opds-spec.org/image/thumbnail") {
|
||||||
|
auto faviconUrl = urlHost + linkNode.attribute("href").value();
|
||||||
|
auto downloader = Downloader();
|
||||||
|
auto fileHandle = downloader.download(faviconUrl);
|
||||||
|
if (fileHandle.success) {
|
||||||
|
auto content = getFileContent(fileHandle.path);
|
||||||
|
book.favicon = base64_encode((const unsigned char*)content.data(), content.size());
|
||||||
|
book.faviconMimeType = linkNode.attribute("type").value();
|
||||||
|
} else {
|
||||||
|
std::cerr << "Cannot get favicon content from " << faviconUrl << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (rel == "http://opds-spec.org/acquisition/open-access") {
|
||||||
|
book.url = linkNode.attribute("href").value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update the book properties with the new importer */
|
||||||
|
library.addBook(book);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool Manager::readOpds(const string& content, const std::string& urlHost)
|
||||||
|
{
|
||||||
|
pugi::xml_document doc;
|
||||||
|
pugi::xml_parse_result result
|
||||||
|
= doc.load_buffer_inplace((void*)content.data(), content.size());
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
this->parseOpdsDom(doc, urlHost);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool Manager::readFile(const string path, const bool readOnly)
|
bool Manager::readFile(const string path, const bool readOnly)
|
||||||
{
|
{
|
||||||
return this->readFile(path, path, readOnly);
|
return this->readFile(path, path, readOnly);
|
||||||
|
@ -231,6 +293,7 @@ bool Manager::writeFile(const string path)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool Manager::setCurrentBookId(const string id)
|
bool Manager::setCurrentBookId(const string id)
|
||||||
{
|
{
|
||||||
if (library.current.empty() || library.current.top() != id) {
|
if (library.current.empty() || library.current.top() != id) {
|
||||||
|
@ -625,6 +688,24 @@ bool Manager::listBooks(const supportedListMode mode,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Library Manager::filter(const std::string& search) {
|
||||||
|
Library library;
|
||||||
|
|
||||||
|
if (search.empty()) {
|
||||||
|
return library;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(auto book:this->library.books) {
|
||||||
|
if (matchRegex(book.title, "\\Q" + search + "\\E")
|
||||||
|
|| matchRegex(book.description, "\\Q" + search + "\\E")) {
|
||||||
|
library.addBook(book);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return library;
|
||||||
|
}
|
||||||
|
|
||||||
void Manager::checkAndCleanBookPaths(Book& book, const string& libraryPath)
|
void Manager::checkAndCleanBookPaths(Book& book, const string& libraryPath)
|
||||||
{
|
{
|
||||||
if (!book.path.empty()) {
|
if (!book.path.empty()) {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
kiwix_sources = [
|
kiwix_sources = [
|
||||||
'library.cpp',
|
'library.cpp',
|
||||||
'manager.cpp',
|
'manager.cpp',
|
||||||
|
'opds_dumper.cpp',
|
||||||
|
'downloader.cpp',
|
||||||
'reader.cpp',
|
'reader.cpp',
|
||||||
'searcher.cpp',
|
'searcher.cpp',
|
||||||
'common/base64.cpp',
|
'common/base64.cpp',
|
||||||
|
|
|
@ -0,0 +1,135 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2017 Matthieu Gautier <mgautier@kymeria.fr>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 3 of the License, or
|
||||||
|
* any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||||
|
* MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "opds_dumper.h"
|
||||||
|
|
||||||
|
namespace kiwix
|
||||||
|
{
|
||||||
|
/* Constructor */
|
||||||
|
OPDSDumper::OPDSDumper(Library library)
|
||||||
|
: library(library)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
/* Destructor */
|
||||||
|
OPDSDumper::~OPDSDumper()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
struct xml_string_writer: pugi::xml_writer
|
||||||
|
{
|
||||||
|
std::string result;
|
||||||
|
|
||||||
|
virtual void write(const void* data, size_t size)
|
||||||
|
{
|
||||||
|
result.append(static_cast<const char*>(data), size);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string node_to_string(pugi::xml_node node)
|
||||||
|
{
|
||||||
|
xml_string_writer writer;
|
||||||
|
node.print(writer, " ");
|
||||||
|
|
||||||
|
return writer.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string gen_date_str()
|
||||||
|
{
|
||||||
|
auto now = time(0);
|
||||||
|
auto tm = localtime(&now);
|
||||||
|
|
||||||
|
std::stringstream is;
|
||||||
|
is << std::setw(2) << std::setfill('0')
|
||||||
|
<< 1900+tm->tm_year << "-"
|
||||||
|
<< std::setw(2) << std::setfill('0') << tm->tm_mon << "-"
|
||||||
|
<< std::setw(2) << std::setfill('0') << tm->tm_mday << "T"
|
||||||
|
<< std::setw(2) << std::setfill('0') << tm->tm_hour << ":"
|
||||||
|
<< std::setw(2) << std::setfill('0') << tm->tm_min << ":"
|
||||||
|
<< std::setw(2) << std::setfill('0') << tm->tm_sec << "Z";
|
||||||
|
return is.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ADD_TEXT_ENTRY(node, child, value) (node).append_child((child)).append_child(pugi::node_pcdata).set_value((value).c_str())
|
||||||
|
|
||||||
|
pugi::xml_node OPDSDumper::handleBook(Book book, pugi::xml_node root_node) {
|
||||||
|
auto entry_node = root_node.append_child("entry");
|
||||||
|
ADD_TEXT_ENTRY(entry_node, "title", book.title);
|
||||||
|
ADD_TEXT_ENTRY(entry_node, "id", "urn:uuid:"+book.id);
|
||||||
|
ADD_TEXT_ENTRY(entry_node, "icon", rootLocation + "/meta?name=favicon&content=" + book.getHumanReadableIdFromPath());
|
||||||
|
ADD_TEXT_ENTRY(entry_node, "updated", date);
|
||||||
|
ADD_TEXT_ENTRY(entry_node, "summary", book.description);
|
||||||
|
|
||||||
|
auto content_node = entry_node.append_child("link");
|
||||||
|
content_node.append_attribute("type") = "text/html";
|
||||||
|
content_node.append_attribute("href") = (rootLocation + "/" + book.getHumanReadableIdFromPath()).c_str();
|
||||||
|
|
||||||
|
auto author_node = entry_node.append_child("author");
|
||||||
|
ADD_TEXT_ENTRY(author_node, "name", book.creator);
|
||||||
|
|
||||||
|
if (! book.url.empty()) {
|
||||||
|
auto acquisition_link = entry_node.append_child("link");
|
||||||
|
acquisition_link.append_attribute("rel") = "http://opds-spec.org/acquisition/open-access";
|
||||||
|
acquisition_link.append_attribute("type") = "application/x-zim";
|
||||||
|
acquisition_link.append_attribute("href") = book.url.c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! book.faviconMimeType.empty() ) {
|
||||||
|
auto image_link = entry_node.append_child("link");
|
||||||
|
image_link.append_attribute("rel") = "http://opds-spec.org/image/thumbnail";
|
||||||
|
image_link.append_attribute("type") = book.faviconMimeType.c_str();
|
||||||
|
image_link.append_attribute("href") = (rootLocation + "/meta?name=favicon&content=" + book.getHumanReadableIdFromPath()).c_str();
|
||||||
|
}
|
||||||
|
return entry_node;
|
||||||
|
}
|
||||||
|
|
||||||
|
string OPDSDumper::dumpOPDSFeed()
|
||||||
|
{
|
||||||
|
date = gen_date_str();
|
||||||
|
pugi::xml_document doc;
|
||||||
|
|
||||||
|
auto root_node = doc.append_child("feed");
|
||||||
|
root_node.append_attribute("xmlns") = "http://www.w3.org/2005/Atom";
|
||||||
|
root_node.append_attribute("xmlns:opds") = "http://opds-spec.org/2010/catalog";
|
||||||
|
|
||||||
|
ADD_TEXT_ENTRY(root_node, "id", id);
|
||||||
|
|
||||||
|
ADD_TEXT_ENTRY(root_node, "title", title);
|
||||||
|
ADD_TEXT_ENTRY(root_node, "updated", date);
|
||||||
|
|
||||||
|
auto self_link_node = root_node.append_child("link");
|
||||||
|
self_link_node.append_attribute("rel") = "self";
|
||||||
|
self_link_node.append_attribute("href") = "";
|
||||||
|
self_link_node.append_attribute("type") = "application/atom+xml";
|
||||||
|
|
||||||
|
|
||||||
|
if (!searchDescriptionUrl.empty() ) {
|
||||||
|
auto search_link = root_node.append_child("link");
|
||||||
|
search_link.append_attribute("rel") = "search";
|
||||||
|
search_link.append_attribute("type") = "application/opensearchdescription+xml";
|
||||||
|
search_link.append_attribute("href") = searchDescriptionUrl.c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto book: library.books) {
|
||||||
|
handleBook(book, root_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
return node_to_string(root_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue