mirror of https://github.com/kiwix/libkiwix.git
295 lines
9.6 KiB
C++
295 lines
9.6 KiB
C++
/*
|
||
* 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"
|
||
#include "book.h"
|
||
|
||
#include "libkiwix-resources.h"
|
||
#include <mustache.hpp>
|
||
|
||
#include "tools/stringTools.h"
|
||
#include "tools/otherTools.h"
|
||
|
||
namespace kiwix
|
||
{
|
||
|
||
/* Constructor */
|
||
OPDSDumper::OPDSDumper(Library* library, NameMapper* nameMapper)
|
||
: library(library),
|
||
nameMapper(nameMapper)
|
||
{
|
||
}
|
||
/* Destructor */
|
||
OPDSDumper::~OPDSDumper()
|
||
{
|
||
}
|
||
|
||
void OPDSDumper::setOpenSearchInfo(int totalResults, int startIndex, int count)
|
||
{
|
||
m_totalResults = totalResults;
|
||
m_startIndex = startIndex,
|
||
m_count = count;
|
||
}
|
||
|
||
namespace
|
||
{
|
||
|
||
const std::string XML_HEADER(R"(<?xml version="1.0" encoding="UTF-8"?>)");
|
||
|
||
typedef kainjow::mustache::data MustacheData;
|
||
typedef kainjow::mustache::list BooksData;
|
||
typedef kainjow::mustache::list IllustrationInfo;
|
||
|
||
IllustrationInfo getBookIllustrationInfo(const Book& book)
|
||
{
|
||
kainjow::mustache::list illustrations;
|
||
if ( book.isPathValid() ) {
|
||
for ( const auto& illustration : book.getIllustrations() ) {
|
||
// For now, we are handling only sizexsize@1 illustration.
|
||
// So we can simply pass one size to mustache.
|
||
illustrations.push_back(kainjow::mustache::object{
|
||
{"icon_size", to_string(illustration->width)},
|
||
{"icon_mimetype", illustration->mimeType}
|
||
});
|
||
}
|
||
}
|
||
return illustrations;
|
||
}
|
||
|
||
std::string fullEntryXML(const Book& book, const std::string& rootLocation, const std::string& contentId)
|
||
{
|
||
const auto bookDate = book.getDate() + "T00:00:00Z";
|
||
const kainjow::mustache::object data{
|
||
{"root", rootLocation},
|
||
{"id", book.getId()},
|
||
{"name", book.getName()},
|
||
{"title", book.getTitle()},
|
||
{"description", book.getDescription()},
|
||
{"language", book.getLanguage()},
|
||
{"content_id", urlEncode(contentId)},
|
||
{"updated", bookDate}, // XXX: this should be the entry update datetime
|
||
{"book_date", bookDate},
|
||
{"category", book.getCategory()},
|
||
{"flavour", book.getFlavour()},
|
||
{"tags", book.getTags()},
|
||
{"article_count", to_string(book.getArticleCount())},
|
||
{"media_count", to_string(book.getMediaCount())},
|
||
{"author_name", book.getCreator()},
|
||
{"publisher_name", book.getPublisher()},
|
||
{"url", onlyAsNonEmptyMustacheValue(book.getUrl())},
|
||
{"size", to_string(book.getSize())},
|
||
{"icons", getBookIllustrationInfo(book)},
|
||
};
|
||
return render_template(RESOURCE::templates::catalog_v2_entry_xml, data);
|
||
}
|
||
|
||
std::string partialEntryXML(const Book& book, const std::string& rootLocation)
|
||
{
|
||
const auto bookDate = book.getDate() + "T00:00:00Z";
|
||
const kainjow::mustache::object data{
|
||
{"root", rootLocation},
|
||
{"endpoint_root", rootLocation + "/catalog/v2"},
|
||
{"id", book.getId()},
|
||
{"title", book.getTitle()},
|
||
{"updated", bookDate}, // XXX: this should be the entry update datetime
|
||
};
|
||
const auto xmlTemplate = RESOURCE::templates::catalog_v2_partial_entry_xml;
|
||
return render_template(xmlTemplate, data);
|
||
}
|
||
|
||
BooksData getBooksData(const Library* library, const NameMapper* nameMapper, const std::vector<std::string>& bookIds, const std::string& rootLocation, bool partial)
|
||
{
|
||
BooksData booksData;
|
||
for ( const auto& bookId : bookIds ) {
|
||
try {
|
||
const Book book = library->getBookByIdThreadSafe(bookId);
|
||
const std::string contentId = nameMapper->getNameForId(bookId);
|
||
const auto entryXML = partial
|
||
? partialEntryXML(book, rootLocation)
|
||
: fullEntryXML(book, rootLocation, contentId);
|
||
booksData.push_back(kainjow::mustache::object{ {"entry", entryXML} });
|
||
} catch ( const std::out_of_range& ) {
|
||
// the book was removed from the library since its id was obtained
|
||
// ignore it
|
||
}
|
||
}
|
||
|
||
return booksData;
|
||
}
|
||
|
||
std::map<std::string, std::string> iso639_3 = {
|
||
{"atj", "atikamekw"},
|
||
{"azb", "آذربایجان دیلی"},
|
||
{"bcl", "central bikol"},
|
||
{"bgs", "tagabawa"},
|
||
{"bxr", "буряад хэлэн"},
|
||
{"cbk", "chavacano"},
|
||
{"cdo", "閩東語"},
|
||
{"dag", "Dagbani"},
|
||
{"diq", "dimli"},
|
||
{"dty", "डोटेली"},
|
||
{"eml", "emiliân-rumagnōl"},
|
||
{"fbs", "српскохрватски"},
|
||
{"hbs", "srpskohrvatski"},
|
||
{"ido", "ido"},
|
||
{"kbp", "kabɩyɛ"},
|
||
{"kld", "Gamilaraay"},
|
||
{"lbe", "лакку маз"},
|
||
{"lbj", "ལ་དྭགས་སྐད་"},
|
||
{"map", "Austronesian"},
|
||
{"mhr", "марий йылме"},
|
||
{"mnw", "ဘာသာမန်"},
|
||
{"myn", "mayan"},
|
||
{"nah", "nahuatl"},
|
||
{"nai", "north American Indian"},
|
||
{"nds", "plattdütsch"},
|
||
{"nrm", "bhasa narom"},
|
||
{"olo", "livvi"},
|
||
{"pih", "Pitcairn-Norfolk"},
|
||
{"pnb", "Western Panjabi"},
|
||
{"rmr", "Caló"},
|
||
{"rmy", "romani shib"},
|
||
{"roa", "romance languages"},
|
||
{"twi", "twi"},
|
||
};
|
||
|
||
std::once_flag fillLanguagesFlag;
|
||
|
||
void fillLanguagesMap()
|
||
{
|
||
for (auto icuLangPtr = icu::Locale::getISOLanguages(); *icuLangPtr != NULL; ++icuLangPtr) {
|
||
const ICULanguageInfo lang(*icuLangPtr);
|
||
iso639_3.insert({lang.iso3Code(), lang.selfName()});
|
||
}
|
||
}
|
||
|
||
std::string getLanguageSelfName(const std::string& lang) {
|
||
const auto itr = iso639_3.find(lang);
|
||
if (itr != iso639_3.end()) {
|
||
return itr->second;
|
||
}
|
||
return lang;
|
||
};
|
||
|
||
} // unnamed namespace
|
||
|
||
string OPDSDumper::dumpOPDSFeed(const std::vector<std::string>& bookIds, const std::string& query) const
|
||
{
|
||
const auto booksData = getBooksData(library, nameMapper, bookIds, rootLocation, false);
|
||
const kainjow::mustache::object template_data{
|
||
{"date", gen_date_str()},
|
||
{"root", rootLocation},
|
||
{"feed_id", gen_uuid(libraryId + "/catalog/search?"+query)},
|
||
{"filter", onlyAsNonEmptyMustacheValue(query)},
|
||
{"totalResults", to_string(m_totalResults)},
|
||
{"startIndex", to_string(m_startIndex)},
|
||
{"itemsPerPage", to_string(m_count)},
|
||
{"books", booksData }
|
||
};
|
||
|
||
return render_template(RESOURCE::templates::catalog_entries_xml, template_data);
|
||
}
|
||
|
||
string OPDSDumper::dumpOPDSFeedV2(const std::vector<std::string>& bookIds, const std::string& query, bool partial) const
|
||
{
|
||
const auto endpointRoot = rootLocation + "/catalog/v2";
|
||
const auto booksData = getBooksData(library, nameMapper, bookIds, rootLocation, partial);
|
||
|
||
const char* const endpoint = partial ? "/partial_entries" : "/entries";
|
||
const kainjow::mustache::object template_data{
|
||
{"date", gen_date_str()},
|
||
{"endpoint_root", endpointRoot},
|
||
{"feed_id", gen_uuid(libraryId + endpoint + "?" + query)},
|
||
{"filter", onlyAsNonEmptyMustacheValue(query)},
|
||
{"query", query.empty() ? "" : "?" + query},
|
||
{"totalResults", to_string(m_totalResults)},
|
||
{"startIndex", to_string(m_startIndex)},
|
||
{"itemsPerPage", to_string(m_count)},
|
||
{"books", booksData },
|
||
{"dump_partial_entries", MustacheData(partial)}
|
||
};
|
||
|
||
return render_template(RESOURCE::templates::catalog_v2_entries_xml, template_data);
|
||
}
|
||
|
||
std::string OPDSDumper::dumpOPDSCompleteEntry(const std::string& bookId) const
|
||
{
|
||
const auto book = library->getBookById(bookId);
|
||
const std::string contentId = nameMapper->getNameForId(bookId);
|
||
return XML_HEADER
|
||
+ "\n"
|
||
+ fullEntryXML(book, rootLocation, contentId);
|
||
}
|
||
|
||
std::string OPDSDumper::categoriesOPDSFeed() const
|
||
{
|
||
const auto now = gen_date_str();
|
||
kainjow::mustache::list categoryData;
|
||
for ( const auto& category : library->getBooksCategories() ) {
|
||
const auto urlencodedCategoryName = urlEncode(category);
|
||
categoryData.push_back(kainjow::mustache::object{
|
||
{"name", category},
|
||
{"urlencoded_name", urlencodedCategoryName},
|
||
{"updated", now},
|
||
{"id", gen_uuid(libraryId + "/categories/" + urlencodedCategoryName)}
|
||
});
|
||
}
|
||
|
||
return render_template(
|
||
RESOURCE::templates::catalog_v2_categories_xml,
|
||
kainjow::mustache::object{
|
||
{"date", now},
|
||
{"endpoint_root", rootLocation + "/catalog/v2"},
|
||
{"feed_id", gen_uuid(libraryId + "/categories")},
|
||
{"categories", categoryData }
|
||
}
|
||
);
|
||
}
|
||
|
||
std::string OPDSDumper::languagesOPDSFeed() const
|
||
{
|
||
const auto now = gen_date_str();
|
||
kainjow::mustache::list languageData;
|
||
std::call_once(fillLanguagesFlag, fillLanguagesMap);
|
||
for ( const auto& langAndBookCount : library->getBooksLanguagesWithCounts() ) {
|
||
const std::string languageCode = langAndBookCount.first;
|
||
const int bookCount = langAndBookCount.second;
|
||
const auto languageSelfName = getLanguageSelfName(languageCode);
|
||
languageData.push_back(kainjow::mustache::object{
|
||
{"lang_code", languageCode},
|
||
{"lang_self_name", languageSelfName},
|
||
{"book_count", to_string(bookCount)},
|
||
{"updated", now},
|
||
{"id", gen_uuid(libraryId + "/languages/" + languageCode)}
|
||
});
|
||
}
|
||
|
||
return render_template(
|
||
RESOURCE::templates::catalog_v2_languages_xml,
|
||
kainjow::mustache::object{
|
||
{"date", now},
|
||
{"endpoint_root", rootLocation + "/catalog/v2"},
|
||
{"feed_id", gen_uuid(libraryId + "/languages")},
|
||
{"languages", languageData }
|
||
}
|
||
);
|
||
}
|
||
|
||
}
|