Support for setting the content server URL

This commit is contained in:
Veloman Yunkan 2025-09-06 17:56:22 +04:00
parent c205a4703b
commit 968d1c1067
10 changed files with 73 additions and 35 deletions

View File

@ -64,6 +64,7 @@ namespace kiwix
void setBlockExternalLinks(bool blockExternalLinks)
{ m_blockExternalLinks = blockExternalLinks; }
void setCatalogOnlyMode(bool enable) { m_catalogOnlyMode = enable; }
void setContentServerUrl(std::string url) { m_contentServerUrl = url; }
void setIpMode(IpMode mode) { m_ipMode = mode; }
int getPort() const;
IpAddress getAddress() const;
@ -85,6 +86,7 @@ namespace kiwix
IpMode m_ipMode = IpMode::AUTO;
int m_ipConnectionLimit = 0;
bool m_catalogOnlyMode = false;
std::string m_contentServerUrl;
std::unique_ptr<InternalServer> mp_server;
};
}

View File

@ -50,6 +50,13 @@ class LibraryDumper
*/
void setRootLocation(const std::string& rootLocation) { this->rootLocation = rootLocation; }
/**
* Set the URL of the content server.
*
* @param url the URL of the content server to use.
*/
void setContentServerUrl(const std::string& url) { this->contentServerUrl = url; }
/**
* Set some informations about the search results.
*
@ -58,10 +65,10 @@ class LibraryDumper
* @param count the number of result of the current set (or page).
*/
void setOpenSearchInfo(int totalResult, int startIndex, int count);
/**
* Sets user default language
*
*
* @param userLang the user language to be set
*/
void setUserLanguage(std::string userLang) { this->m_userLang = userLang; }
@ -81,6 +88,7 @@ class LibraryDumper
const kiwix::NameMapper* const nameMapper;
std::string libraryId;
std::string rootLocation;
std::string contentServerUrl;
std::string m_userLang;
int m_totalResults;
int m_startIndex;

View File

@ -62,11 +62,15 @@ IllustrationInfo getBookIllustrationInfo(const Book& book)
return illustrations;
}
std::string fullEntryXML(const Book& book, const std::string& rootLocation, const std::string& contentId)
std::string fullEntryXML(const Book& book,
const std::string& rootLocation,
const std::string& contentServerUrl,
const std::string& contentId)
{
const auto bookDate = book.getDate() + "T00:00:00Z";
const kainjow::mustache::object data{
{"root", rootLocation},
{"contentServerUrl", onlyAsNonEmptyMustacheValue(contentServerUrl)},
{"id", book.getId()},
{"name", book.getName()},
{"title", book.getTitle()},
@ -103,7 +107,12 @@ std::string partialEntryXML(const Book& book, const std::string& rootLocation)
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 getBooksData(const Library* library,
const NameMapper* nameMapper,
const std::vector<std::string>& bookIds,
const std::string& rootLocation,
const std::string& contentServerUrl,
bool partial)
{
BooksData booksData;
for ( const auto& bookId : bookIds ) {
@ -112,7 +121,7 @@ BooksData getBooksData(const Library* library, const NameMapper* nameMapper, con
const std::string contentId = nameMapper->getNameForId(bookId);
const auto entryXML = partial
? partialEntryXML(book, rootLocation)
: fullEntryXML(book, rootLocation, contentId);
: fullEntryXML(book, rootLocation, contentServerUrl, 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
@ -127,7 +136,7 @@ BooksData getBooksData(const Library* library, const NameMapper* nameMapper, con
string OPDSDumper::dumpOPDSFeed(const std::vector<std::string>& bookIds, const std::string& query) const
{
const auto booksData = getBooksData(library, nameMapper, bookIds, rootLocation, false);
const auto booksData = getBooksData(library, nameMapper, bookIds, rootLocation, contentServerUrl, false);
const kainjow::mustache::object template_data{
{"date", gen_date_str()},
{"root", rootLocation},
@ -145,7 +154,7 @@ string OPDSDumper::dumpOPDSFeed(const std::vector<std::string>& bookIds, const s
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 auto booksData = getBooksData(library, nameMapper, bookIds, rootLocation, contentServerUrl, partial);
const char* const endpoint = partial ? "/partial_entries" : "/entries";
const std::string url = endpoint + (query.empty() ? "" : "?" + query);
@ -170,7 +179,7 @@ std::string OPDSDumper::dumpOPDSCompleteEntry(const std::string& bookId) const
const std::string contentId = nameMapper->getNameForId(bookId);
return XML_HEADER
+ "\n"
+ fullEntryXML(book, rootLocation, contentId);
+ fullEntryXML(book, rootLocation, contentServerUrl, contentId);
}
std::string OPDSDumper::categoriesOPDSFeed() const

View File

@ -54,7 +54,8 @@ bool Server::start() {
m_ipMode,
m_indexTemplateString,
m_ipConnectionLimit,
m_catalogOnlyMode));
m_catalogOnlyMode,
m_contentServerUrl));
return mp_server->start();
}

View File

@ -436,7 +436,8 @@ InternalServer::InternalServer(LibraryPtr library,
IpMode ipMode,
std::string indexTemplateString,
int ipConnectionLimit,
bool catalogOnlyMode) :
bool catalogOnlyMode,
std::string contentServerUrl) :
m_addr(addr),
m_port(port),
m_root(normalizeRootUrl(root)),
@ -456,7 +457,8 @@ InternalServer::InternalServer(LibraryPtr library,
searchCache(getEnvVar<int>("KIWIX_SEARCH_CACHE_SIZE", DEFAULT_CACHE_SIZE)),
suggestionSearcherCache(getEnvVar<int>("KIWIX_SUGGESTION_SEARCHER_CACHE_SIZE", std::max((unsigned int) (mp_library->getBookCount(true, true)*0.1), 1U))),
m_customizedResources(new CustomizedResources),
m_catalogOnlyMode(catalogOnlyMode)
m_catalogOnlyMode(catalogOnlyMode),
m_contentServerUrl(contentServerUrl)
{
m_root = urlEncode(m_root);
}

View File

@ -107,7 +107,8 @@ class InternalServer {
IpMode ipMode,
std::string indexTemplateString,
int ipConnectionLimit,
bool catalogOnlyMode);
bool catalogOnlyMode,
std::string zimViewerURL);
virtual ~InternalServer();
MHD_Result handlerCallback(struct MHD_Connection* connection,
@ -161,6 +162,7 @@ class InternalServer {
std::string getLibraryId() const;
std::string getNoJSDownloadPageHTML(const std::string& bookId, const std::string& userLang) const;
OPDSDumper getOPDSDumper() const;
private: // types
class LockableSuggestionSearcher;
@ -195,6 +197,7 @@ class InternalServer {
std::unique_ptr<CustomizedResources> m_customizedResources;
const bool m_catalogOnlyMode;
const std::string m_contentServerUrl;
};
}

View File

@ -51,6 +51,19 @@ const std::string opdsMimeType[] = {
} // unnamed namespace
OPDSDumper InternalServer::getOPDSDumper() const
{
kiwix::OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get());
opdsDumper.setRootLocation(m_root);
opdsDumper.setLibraryId(getLibraryId());
if ( !m_contentServerUrl.empty() ) {
opdsDumper.setContentServerUrl(m_contentServerUrl);
} else if ( !m_catalogOnlyMode ) {
opdsDumper.setContentServerUrl(m_root);
}
return opdsDumper;
}
std::unique_ptr<Response> InternalServer::handle_catalog(const RequestContext& request)
{
if (m_verbose.load()) {
@ -80,9 +93,7 @@ std::unique_ptr<Response> InternalServer::handle_catalog(const RequestContext& r
}
zim::Uuid uuid;
kiwix::OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get());
opdsDumper.setRootLocation(m_root);
opdsDumper.setLibraryId(getLibraryId());
kiwix::OPDSDumper opdsDumper = getOPDSDumper();
std::vector<std::string> bookIdsToDump;
if (url == "root.xml") {
uuid = zim::Uuid::generate(host);
@ -158,9 +169,7 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_root(const RequestCo
std::unique_ptr<Response> InternalServer::handle_catalog_v2_entries(const RequestContext& request, bool partial)
{
OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get());
opdsDumper.setRootLocation(m_root);
opdsDumper.setLibraryId(getLibraryId());
kiwix::OPDSDumper opdsDumper = getOPDSDumper();
const auto bookIds = search_catalog(request, opdsDumper);
const auto opdsFeed = opdsDumper.dumpOPDSFeedV2(bookIds, request.get_query(), partial);
return ContentResponse::build(
@ -177,9 +186,7 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_complete_entry(const
return UrlNotFoundResponse(request);
}
OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get());
opdsDumper.setRootLocation(m_root);
opdsDumper.setLibraryId(getLibraryId());
kiwix::OPDSDumper opdsDumper = getOPDSDumper();
const auto opdsFeed = opdsDumper.dumpOPDSCompleteEntry(entryId);
return ContentResponse::build(
opdsFeed,
@ -189,9 +196,7 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_complete_entry(const
std::unique_ptr<Response> InternalServer::handle_catalog_v2_categories(const RequestContext& request)
{
OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get());
opdsDumper.setRootLocation(m_root);
opdsDumper.setLibraryId(getLibraryId());
kiwix::OPDSDumper opdsDumper = getOPDSDumper();
return ContentResponse::build(
opdsDumper.categoriesOPDSFeed(),
opdsMimeType[OPDS_NAVIGATION_FEED]
@ -200,9 +205,7 @@ std::unique_ptr<Response> InternalServer::handle_catalog_v2_categories(const Req
std::unique_ptr<Response> InternalServer::handle_catalog_v2_languages(const RequestContext& request)
{
OPDSDumper opdsDumper(mp_library.get(), mp_nameMapper.get());
opdsDumper.setRootLocation(m_root);
opdsDumper.setLibraryId(getLibraryId());
kiwix::OPDSDumper opdsDumper = getOPDSDumper();
return ContentResponse::build(
opdsDumper.languagesOPDSFeed(),
opdsMimeType[OPDS_NAVIGATION_FEED]

View File

@ -13,7 +13,8 @@
{{#icons}}<link rel="http://opds-spec.org/image/thumbnail"
href="{{root}}/catalog/v2/illustration/{{{id}}}/?size={{icon_size}}"
type="{{icon_mimetype}};width={{icon_size}};height={{icon_size}};scale=1"/>
{{/icons}}<link type="text/html" href="{{root}}/content/{{{content_id}}}" />
{{/icons}}{{#contentServerUrl}}<link type="text/html" href="{{contentServerUrl}}/content/{{{content_id}}}" />
{{/contentServerUrl}}
<author>
<name>{{author_name}}</name>
</author>

View File

@ -18,9 +18,11 @@ protected:
const int PORT = 8002;
protected:
void resetServer(ZimFileServer::Options options) {
void resetServer(ZimFileServer::Options options, std::string contentServerUrl="") {
ZimFileServer::Cfg cfg(options);
cfg.contentServerUrl = contentServerUrl;
zfs1_.reset();
zfs1_.reset(new ZimFileServer(PORT, options, "./test/library.xml"));
zfs1_.reset(new ZimFileServer(PORT, cfg, "./test/library.xml"));
}
void SetUp() override {
@ -723,7 +725,12 @@ TEST_F(LibraryServerTest, catalog_v2_entries)
TEST_F(LibraryServerTest, catalog_v2_entries_catalog_only_mode)
{
resetServer(ZimFileServer::CATALOG_ONLY_MODE);
const std::string contentServerUrl = "https://demo.kiwix.org";
const auto fixContentLinks = [=](std::string s) -> std::string {
s = replace(s, "/ROOT%23%3F/content", contentServerUrl + "/content");
return s;
};
resetServer(ZimFileServer::CATALOG_ONLY_MODE, contentServerUrl);
const auto r = zfs1_->GET("/ROOT%23%3F/catalog/v2/entries");
EXPECT_EQ(r->status, 200);
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
@ -731,10 +738,10 @@ TEST_F(LibraryServerTest, catalog_v2_entries_catalog_only_mode)
" <title>All Entries</title>\n"
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
"\n"
CHARLES_RAY_CATALOG_ENTRY
INACCESSIBLEZIMFILE_CATALOG_ENTRY
RAY_CHARLES_CATALOG_ENTRY
UNCATEGORIZED_RAY_CHARLES_CATALOG_ENTRY
+ fixContentLinks(CHARLES_RAY_CATALOG_ENTRY)
+ fixContentLinks(INACCESSIBLEZIMFILE_CATALOG_ENTRY)
+ fixContentLinks(RAY_CHARLES_CATALOG_ENTRY)
+ fixContentLinks(UNCATEGORIZED_RAY_CHARLES_CATALOG_ENTRY) +
"</feed>\n"
);
}

View File

@ -72,6 +72,7 @@ public: // types
struct Cfg
{
std::string root = "ROOT#?";
std::string contentServerUrl = "";
Options options = DEFAULT_OPTIONS;
Cfg(Options opts = DEFAULT_OPTIONS) : options(opts) {}
@ -151,6 +152,7 @@ void ZimFileServer::run(int serverPort, std::string indexTemplateString)
server->setBlockExternalLinks(cfg.options & BLOCK_EXTERNAL_LINKS);
server->setMultiZimSearchLimit(3);
server->setCatalogOnlyMode(cfg.options & CATALOG_ONLY_MODE);
server->setContentServerUrl(cfg.contentServerUrl);
if (!indexTemplateString.empty()) {
server->setIndexTemplateString(indexTemplateString);
}