diff --git a/include/name_mapper.h b/include/name_mapper.h index 28706350..374c9d5d 100644 --- a/include/name_mapper.h +++ b/include/name_mapper.h @@ -50,7 +50,7 @@ class HumanReadableNameMapper : public NameMapper { std::map m_nameToId; public: - HumanReadableNameMapper(kiwix::Library& library, bool withAlias); + HumanReadableNameMapper(const kiwix::Library& library, bool withAlias); virtual ~HumanReadableNameMapper() = default; virtual std::string getNameForId(const std::string& id) const; virtual std::string getIdForName(const std::string& name) const; diff --git a/include/server.h b/include/server.h index 2825c4fc..0fbdfab6 100644 --- a/include/server.h +++ b/include/server.h @@ -63,6 +63,8 @@ namespace kiwix { m_withTaskbar = withTaskbar; m_withLibraryButton = withLibraryButton; } 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; @@ -83,6 +85,8 @@ namespace kiwix bool m_blockExternalLinks = false; IpMode m_ipMode = IpMode::AUTO; int m_ipConnectionLimit = 0; + bool m_catalogOnlyMode = false; + std::string m_contentServerUrl; std::unique_ptr mp_server; }; } diff --git a/src/html_dumper.cpp b/src/html_dumper.cpp index d07d7375..ce610dc4 100644 --- a/src/html_dumper.cpp +++ b/src/html_dumper.cpp @@ -30,7 +30,7 @@ std::string humanFriendlyTitle(std::string title) kainjow::mustache::object getLangTag(const std::vector& bookLanguages) { std::string langShortString = ""; std::string langFullString = "???"; - + //if more than 1 languages then show "mul" else show the language if(bookLanguages.size() > 1) { std::vector mulLanguages; @@ -44,7 +44,7 @@ kainjow::mustache::object getLangTag(const std::vector& bookLanguag langShortString = bookLanguages[0]; langFullString = getLanguageSelfName(langShortString); } - + kainjow::mustache::object langTag; langTag["langShortString"] = langShortString; langTag["langFullString"] = langFullString; @@ -130,6 +130,7 @@ std::string HTMLDumper::dumpPlainHTML(kiwix::Filter filter) const RESOURCE::templates::no_js_library_page_html, kainjow::mustache::object{ {"root", rootLocation}, + {"contentServerUrl", onlyAsNonEmptyMustacheValue(contentServerUrl)}, {"books", booksData }, {"searchQuery", searchQuery}, {"languages", languages}, diff --git a/src/library_dumper.h b/src/library_dumper.h index 3438ef71..880836c7 100644 --- a/src/library_dumper.h +++ b/src/library_dumper.h @@ -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; diff --git a/src/name_mapper.cpp b/src/name_mapper.cpp index 3b3f789d..1fce5178 100644 --- a/src/name_mapper.cpp +++ b/src/name_mapper.cpp @@ -24,8 +24,8 @@ namespace kiwix { -HumanReadableNameMapper::HumanReadableNameMapper(kiwix::Library& library, bool withAlias) { - for (auto& bookId: library.filter(kiwix::Filter().local(true).valid(true))) { +HumanReadableNameMapper::HumanReadableNameMapper(const kiwix::Library& library, bool withAlias) { + for (auto& bookId: library.filter(kiwix::Filter())) { auto& currentBook = library.getBookById(bookId); auto bookName = currentBook.getHumanReadableIdFromPath(); m_idToName[bookId] = bookName; diff --git a/src/opds_dumper.cpp b/src/opds_dumper.cpp index bdb21be7..bd4c1573 100644 --- a/src/opds_dumper.cpp +++ b/src/opds_dumper.cpp @@ -51,24 +51,26 @@ 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} - }); - } + 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) +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()}, @@ -105,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& bookIds, const std::string& rootLocation, bool partial) +BooksData getBooksData(const Library* library, + const NameMapper* nameMapper, + const std::vector& bookIds, + const std::string& rootLocation, + const std::string& contentServerUrl, + bool partial) { BooksData booksData; for ( const auto& bookId : bookIds ) { @@ -114,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 @@ -129,7 +136,7 @@ BooksData getBooksData(const Library* library, const NameMapper* nameMapper, con string OPDSDumper::dumpOPDSFeed(const std::vector& 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}, @@ -147,7 +154,7 @@ string OPDSDumper::dumpOPDSFeed(const std::vector& bookIds, const s string OPDSDumper::dumpOPDSFeedV2(const std::vector& 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); @@ -172,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 diff --git a/src/server.cpp b/src/server.cpp index 065cefd1..82dc1b54 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -53,7 +53,9 @@ bool Server::start() { m_blockExternalLinks, m_ipMode, m_indexTemplateString, - m_ipConnectionLimit)); + m_ipConnectionLimit, + m_catalogOnlyMode, + m_contentServerUrl)); return mp_server->start(); } diff --git a/src/server/internalServer.cpp b/src/server/internalServer.cpp index ab82273f..7e599458 100644 --- a/src/server/internalServer.cpp +++ b/src/server/internalServer.cpp @@ -125,9 +125,12 @@ std::string getSearchComponent(const RequestContext& request) return query.empty() ? query : "?" + query; } -Filter get_search_filter(const RequestContext& request, const std::string& prefix="") +Filter get_search_filter(const RequestContext& request, const std::string& prefix="", bool catalogOnlyMode = false) { - auto filter = kiwix::Filter().valid(true).local(true); + auto filter = kiwix::Filter(); + if ( !catalogOnlyMode ) { + filter.valid(true).local(true); + } try { filter.query(request.get_argument(prefix+"q")); } catch (const std::out_of_range&) {} @@ -432,7 +435,9 @@ InternalServer::InternalServer(LibraryPtr library, bool blockExternalLinks, IpMode ipMode, std::string indexTemplateString, - int ipConnectionLimit) : + int ipConnectionLimit, + bool catalogOnlyMode, + std::string contentServerUrl) : m_addr(addr), m_port(port), m_root(normalizeRootUrl(root)), @@ -451,7 +456,9 @@ InternalServer::InternalServer(LibraryPtr library, mp_nameMapper(nameMapper ? nameMapper : std::shared_ptr(&defaultNameMapper, NoDelete())), searchCache(getEnvVar("KIWIX_SEARCH_CACHE_SIZE", DEFAULT_CACHE_SIZE)), suggestionSearcherCache(getEnvVar("KIWIX_SUGGESTION_SEARCHER_CACHE_SIZE", std::max((unsigned int) (mp_library->getBookCount(true, true)*0.1), 1U))), - m_customizedResources(new CustomizedResources) + m_customizedResources(new CustomizedResources), + m_catalogOnlyMode(catalogOnlyMode), + m_contentServerUrl(contentServerUrl) { m_root = urlEncode(m_root); } @@ -850,12 +857,17 @@ std::unique_ptr InternalServer::handle_no_js(const RequestContext& req HTMLDumper htmlDumper(mp_library.get(), mp_nameMapper.get()); htmlDumper.setRootLocation(m_root); htmlDumper.setLibraryId(getLibraryId()); + if ( !m_contentServerUrl.empty() ) { + htmlDumper.setContentServerUrl(m_contentServerUrl); + } else if ( !m_catalogOnlyMode ) { + htmlDumper.setContentServerUrl(m_root); + } auto userLang = request.get_user_language(); htmlDumper.setUserLanguage(userLang); std::string content; if (urlParts.size() == 1) { - auto filter = get_search_filter(request); + auto filter = get_search_filter(request, "", m_catalogOnlyMode); try { if (request.get_argument("category") == "") { filter.clearCategory(); @@ -1103,7 +1115,7 @@ std::vector InternalServer::search_catalog(const RequestContext& request, kiwix::OPDSDumper& opdsDumper) { - const auto filter = get_search_filter(request); + const auto filter = get_search_filter(request, "", m_catalogOnlyMode); std::vector bookIdsToDump = mp_library->filter(filter); const auto totalResults = bookIdsToDump.size(); const long count = request.get_optional_param("count", 10L); diff --git a/src/server/internalServer.h b/src/server/internalServer.h index b0831fef..af8f7bc1 100644 --- a/src/server/internalServer.h +++ b/src/server/internalServer.h @@ -106,7 +106,9 @@ class InternalServer { bool blockExternalLinks, IpMode ipMode, std::string indexTemplateString, - int ipConnectionLimit); + int ipConnectionLimit, + bool catalogOnlyMode, + std::string zimViewerURL); virtual ~InternalServer(); MHD_Result handlerCallback(struct MHD_Connection* connection, @@ -160,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; @@ -192,6 +195,9 @@ class InternalServer { class CustomizedResources; std::unique_ptr m_customizedResources; + + const bool m_catalogOnlyMode; + const std::string m_contentServerUrl; }; } diff --git a/src/server/internalServer_catalog.cpp b/src/server/internalServer_catalog.cpp index 1c48117f..70754975 100644 --- a/src/server/internalServer_catalog.cpp +++ b/src/server/internalServer_catalog.cpp @@ -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 InternalServer::handle_catalog(const RequestContext& request) { if (m_verbose.load()) { @@ -80,9 +93,7 @@ std::unique_ptr 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 bookIdsToDump; if (url == "root.xml") { uuid = zim::Uuid::generate(host); @@ -158,9 +169,7 @@ std::unique_ptr InternalServer::handle_catalog_v2_root(const RequestCo std::unique_ptr 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 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 InternalServer::handle_catalog_v2_complete_entry(const std::unique_ptr 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 InternalServer::handle_catalog_v2_categories(const Req std::unique_ptr 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] diff --git a/static/skin/index.js b/static/skin/index.js index 082d3428..7aff5555 100644 --- a/static/skin/index.js +++ b/static/skin/index.js @@ -152,8 +152,21 @@ : ''; } + function addBookPreviewLink(html, bookXml) { + const bookContentLink = bookXml.querySelector('link[type="text/html"]'); + if ( !bookContentLink ) + return html; + + const urlComponents = bookContentLink.getAttribute('href').split('/'); + // bookContentLink URL = ROOT_URL/content/BOOK_NAME + const bookName = urlComponents.pop(); + urlComponents.pop(); // drop 'content' component + const viewerLink = urlComponents.join('/') + `/viewer#${bookName}`; + + return `${html}`; + } + function generateBookHtml(book, sort = false) { - const link = book.querySelector('link[type="text/html"]').getAttribute('href'); let iconUrl; book.querySelectorAll('link[rel="http://opds-spec.org/image/thumbnail"]').forEach(link => { if (link.getAttribute('type').split(';')[1] == 'width=48' && !iconUrl) { @@ -183,9 +196,6 @@ } catch { downloadLink = ''; } - const bookName = link.split('/').pop(); - const viewerLink = `${root}/viewer#${bookName}`; - const humanFriendlyZimSize = humanFriendlySize(zimSize); const divTag = document.createElement('div'); @@ -197,9 +207,7 @@ const faviconAttr = iconUrl != undefined ? `style="background-image: url('${iconUrl}')"` : ''; const languageAttr = langCode != '' ? `title="${language}" aria-label="${language}"` : 'style="background-color: transparent"'; - divTag.innerHTML = ` -
- + let bookLinkWrapper = ` - + `; + + divTag.innerHTML = ` +
+ ${addBookPreviewLink(bookLinkWrapper, book)}
${getLanguageCodeToDisplay(langCode)}
${tagHtml}
diff --git a/static/templates/catalog_v2_entry.xml b/static/templates/catalog_v2_entry.xml index f7c11f1f..f2ede5a7 100644 --- a/static/templates/catalog_v2_entry.xml +++ b/static/templates/catalog_v2_entry.xml @@ -13,7 +13,8 @@ {{#icons}} - {{/icons}} + {{/icons}}{{#contentServerUrl}} + {{/contentServerUrl}} {{author_name}} diff --git a/static/templates/index.html b/static/templates/index.html index c029f247..2c666ed7 100644 --- a/static/templates/index.html +++ b/static/templates/index.html @@ -33,7 +33,6 @@ - diff --git a/static/templates/no_js_library_page.html b/static/templates/no_js_library_page.html index aaf61fa7..fdaece84 100644 --- a/static/templates/no_js_library_page.html +++ b/static/templates/no_js_library_page.html @@ -108,7 +108,7 @@

{{translations.count-of-matching-books}}

{{#books}}
- + {{#contentServerUrl}}{{/contentServerUrl}} - + {{#contentServerUrl}}{{/contentServerUrl}}
{{langTag.langShortString}}
diff --git a/test/data/library.xml b/test/data/library.xml index 8508b97f..bf32f250 100644 --- a/test/data/library.xml +++ b/test/data/library.xml @@ -51,4 +51,21 @@ size="556" favicon="faviconMimeType_attribute_is_absent" > + diff --git a/test/library_server.cpp b/test/library_server.cpp index 720b8b18..03da70a6 100644 --- a/test/library_server.cpp +++ b/test/library_server.cpp @@ -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 { @@ -150,6 +152,30 @@ std::string maskVariableOPDSFeedData(std::string s) "125952"\ ) +#define INACCESSIBLEZIMFILE_CATALOG_ENTRY \ +" \n" \ +" urn:uuid:inaccessiblezim\n" \ +" Catalog of all catalogs\n" \ +" YYYY-MM-DDThh:mm:ssZ\n" \ +" Testing that running kiwix-serve without access to ZIM files doesn't lead to a catastrophe\n" \ +" cat\n" \ +" catalog_of_all_catalogs\n" \ +" \n" \ +" cats\n" \ +" unittest;_category:cats\n" \ +" 12107\n" \ +" 8\n" \ +" \n" \ +" \n" \ +" Catherine of Catalonia\n" \ +" \n" \ +" \n" \ +" Caterpillar\n" \ +" \n" \ +" 2025-09-04T00:00:00Z\n" \ +" \n" \ +" \n" + TEST_F(LibraryServerTest, catalog_root_xml) { const auto r = zfs1_->GET("/ROOT%23%3F/catalog/root.xml"); @@ -560,6 +586,15 @@ TEST_F(LibraryServerTest, catalog_v2_categories) List of categories YYYY-MM-DDThh:mm:ssZ + + cats + + YYYY-MM-DDThh:mm:ssZ + 12345678-90ab-cdef-1234-567890abcdef + All entries with category of 'cats'. + jazz List of languages YYYY-MM-DDThh:mm:ssZ + + català + cat + 1 + + YYYY-MM-DDThh:mm:ssZ + 12345678-90ab-cdef-1234-567890abcdef + English eng @@ -678,6 +723,29 @@ TEST_F(LibraryServerTest, catalog_v2_entries) ); } +TEST_F(LibraryServerTest, catalog_v2_entries_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), + CATALOG_V2_ENTRIES_PREAMBLE("") + " All Entries\n" + " YYYY-MM-DDThh:mm:ssZ\n" + "\n" + + fixContentLinks(CHARLES_RAY_CATALOG_ENTRY) + + fixContentLinks(INACCESSIBLEZIMFILE_CATALOG_ENTRY) + + fixContentLinks(RAY_CHARLES_CATALOG_ENTRY) + + fixContentLinks(UNCATEGORIZED_RAY_CHARLES_CATALOG_ENTRY) + + "\n" + ); +} + TEST_F(LibraryServerTest, catalog_v2_entries_filtered_by_range) { { @@ -1320,6 +1388,32 @@ TEST_F(LibraryServerTest, no_name_mapper_catalog_v2_individual_entry_access) "
\n" \ "
\n" +#define INACCESSIBLEZIMFILE_BOOK_HTML \ + " \n" + #define FINAL_HTML_TEXT \ "
\n" \ "
\n" \ @@ -1332,6 +1426,7 @@ TEST_F(LibraryServerTest, no_name_mapper_catalog_v2_individual_entry_access) "
\n" \ " \n" \ " \n" \ + " \n" \ " \n" \ " \n" \ " \n" \ @@ -1453,4 +1549,25 @@ TEST_F(LibraryServerTest, noJS) { EXPECT_EQ(r->body, RAY_CHARLES_UNCTZ_DOWNLOAD); } +TEST_F(LibraryServerTest, noJS_catalogOnlyMode) { + 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); + + auto r = zfs1_->GET("/ROOT%23%3F/nojs"); + EXPECT_EQ(r->status, 200); + EXPECT_EQ(r->body, + HTML_PREAMBLE + FILTERS_HTML("") + HOME_BODY_TEXT("4") + + fixContentLinks(CHARLES_RAY_BOOK_HTML) + + fixContentLinks(INACCESSIBLEZIMFILE_BOOK_HTML) + + fixContentLinks(RAY_CHARLES_BOOK_HTML) + + fixContentLinks(RAY_CHARLES_UNCTZ_BOOK_HTML) + + FINAL_HTML_TEXT); +} + #undef EXPECT_SEARCH_RESULTS diff --git a/test/manager.cpp b/test/manager.cpp index b67c0cfc..c7018852 100644 --- a/test/manager.cpp +++ b/test/manager.cpp @@ -88,6 +88,7 @@ TEST(Manager, reload) manager.reload({ "./test/library.xml" }); EXPECT_EQ(lib->getBooksIds(), (kiwix::Library::BookIdCollection{ "charlesray", + "inaccessiblezim", "raycharles", "raycharles_uncategorized" })); @@ -95,12 +96,14 @@ TEST(Manager, reload) lib->removeBookById("raycharles"); EXPECT_EQ(lib->getBooksIds(), (kiwix::Library::BookIdCollection{ "charlesray", + "inaccessiblezim", "raycharles_uncategorized" })); manager.reload({ "./test/library.xml" }); EXPECT_EQ(lib->getBooksIds(), kiwix::Library::BookIdCollection({ "charlesray", + "inaccessiblezim", "raycharles", "raycharles_uncategorized" })); diff --git a/test/server.cpp b/test/server.cpp index 40a4f73e..a9c766c5 100644 --- a/test/server.cpp +++ b/test/server.cpp @@ -65,7 +65,7 @@ const ResourceCollection resources200Compressible{ { DYNAMIC_CONTENT, "/ROOT%23%3F/skin/index.css" }, { 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=cc456f1f" }, + { STATIC_CONTENT, "/ROOT%23%3F/skin/index.js?cacheid=4e232c58" }, { 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" }, @@ -304,7 +304,7 @@ R"EXPECTEDRESULT( href="/ROOT%23%3F/skin/kiwix.css?cacheid=b4e29e64" - + setTaskbar(cfg.options & WITH_TASKBAR, cfg.options & WITH_LIBRARY_BUTTON); 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); }