Skip to content

Commit 39c08b7

Browse files
vighnesh-sawantkelson42
authored andcommitted
Add support for accessing books using a metadata-based ID on /viewer#{bookId} and /content/{bookId}
1 parent 0a9ba9b commit 39c08b7

File tree

5 files changed

+65
-12
lines changed

5 files changed

+65
-12
lines changed

include/library.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ class Filter {
7979
uint64_t activeFilters;
8080
Tags _acceptTags;
8181
Tags _rejectTags;
82+
std::string _id;
8283
std::string _category;
8384
std::string _lang;
8485
std::string _publisher;
@@ -141,7 +142,7 @@ class Filter {
141142
* which case a book in any of those languages will match).
142143
*/
143144
Filter& lang(std::string lang);
144-
145+
Filter& id(std::string id);
145146
Filter& publisher(std::string publisher);
146147
Filter& creator(std::string creator);
147148
Filter& maxSize(size_t size);
@@ -154,7 +155,8 @@ class Filter {
154155
bool hasQuery() const;
155156
const std::string& getQuery() const { return _query; }
156157
bool queryIsPartial() const { return _queryIsPartial; }
157-
158+
bool hasId() const;
159+
const std::string& getId() const { return _id; }
158160
bool hasName() const;
159161
const std::string& getName() const { return _name; }
160162

src/library.cpp

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -686,7 +686,10 @@ Xapian::Query langQuery(const std::string& commaSeparatedLanguageList)
686686
{
687687
return multipleParamQuery(commaSeparatedLanguageList, "L");
688688
}
689-
689+
Xapian::Query idQuery(const std::string& commaSeparatedIdList)
690+
{
691+
return multipleParamQuery(commaSeparatedIdList, "Q");
692+
}
690693
Xapian::Query publisherQuery(const std::string& publisher)
691694
{
692695
Xapian::QueryParser queryParser;
@@ -743,6 +746,9 @@ Xapian::Query buildXapianQuery(const Filter& filter)
743746
if ( filter.hasCreator() ) {
744747
q = Xapian::Query(Xapian::Query::OP_AND, q, creatorQuery(filter.getCreator()));
745748
}
749+
if (filter.hasId()) {
750+
q = Xapian::Query(Xapian::Query::OP_AND, q, idQuery(filter.getId()));
751+
}
746752
if ( !filter.getAcceptTags().empty() || !filter.getRejectTags().empty() ) {
747753
const auto tq = tagsQuery(filter.getAcceptTags(), filter.getRejectTags());
748754
q = Xapian::Query(Xapian::Query::OP_AND, q, tq);;
@@ -897,6 +903,7 @@ enum filterTypes {
897903
NAME = FLAG(13),
898904
CATEGORY = FLAG(14),
899905
FLAVOUR = FLAG(15),
906+
ID = FLAG(16)
900907
};
901908

902909
Filter& Filter::local(bool accept)
@@ -962,7 +969,12 @@ Filter& Filter::lang(std::string lang)
962969
activeFilters |= LANG;
963970
return *this;
964971
}
965-
972+
Filter& Filter::id(std::string id)
973+
{
974+
_id = id;
975+
activeFilters |= ID;
976+
return *this;
977+
}
966978
Filter& Filter::publisher(std::string publisher)
967979
{
968980
_publisher = publisher;
@@ -1040,6 +1052,11 @@ bool Filter::hasLang() const
10401052
return ACTIVE(LANG);
10411053
}
10421054

1055+
bool Filter::hasId() const
1056+
{
1057+
return ACTIVE(ID);
1058+
}
1059+
10431060
bool Filter::hasPublisher() const
10441061
{
10451062
return ACTIVE(_PUBLISHER);

src/server/internalServer.cpp

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ extern "C" {
6666

6767
#include <atomic>
6868
#include <string>
69+
#include <regex>
6970
#include <vector>
7071
#include <chrono>
7172
#include <fstream>
@@ -142,6 +143,9 @@ Filter get_search_filter(const RequestContext& request, const std::string& prefi
142143
try {
143144
filter.rejectTags(kiwix::split(request.get_argument(prefix+"notag"), ";"));
144145
} catch (...) {}
146+
try {
147+
filter.id(request.get_argument(prefix + "id"));
148+
} catch (const std::out_of_range&) {}
145149
return filter;
146150
}
147151

@@ -1169,11 +1173,20 @@ std::unique_ptr<Response> InternalServer::handle_content(const RequestContext& r
11691173
const std::string contentPrefix = "/content/";
11701174
const bool isContentPrefixedUrl = startsWith(url, contentPrefix);
11711175
const size_t prefixLength = isContentPrefixedUrl ? contentPrefix.size() : 1;
1172-
const std::string bookName = request.get_url_part(isContentPrefixedUrl);
1176+
const std::regex uuid_regex("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}");
11731177

1178+
const std::string requestedBookNameOrUUID = request.get_url_part(isContentPrefixedUrl);
11741179
std::shared_ptr<zim::Archive> archive;
1180+
std::string bookName;
11751181
try {
1176-
const std::string bookId = mp_nameMapper->getIdForName(bookName);
1182+
std::string bookId;
1183+
if (std::regex_match(requestedBookNameOrUUID, uuid_regex)) {
1184+
bookId = requestedBookNameOrUUID;
1185+
} else {
1186+
bookId = mp_nameMapper->getIdForName(requestedBookNameOrUUID);
1187+
}
1188+
1189+
bookName = mp_nameMapper->getNameForId(bookId);
11771190
archive = mp_library->getArchiveById(bookId);
11781191
} catch (const std::out_of_range& e) {}
11791192

@@ -1186,7 +1199,7 @@ std::unique_ptr<Response> InternalServer::handle_content(const RequestContext& r
11861199
if ( etag )
11871200
return Response::build_304(etag);
11881201

1189-
auto urlStr = url.substr(prefixLength + bookName.size());
1202+
auto urlStr = url.substr(prefixLength + requestedBookNameOrUUID.size());
11901203
if (urlStr[0] == '/') {
11911204
urlStr = urlStr.substr(1);
11921205
}

static/skin/viewer.js

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,9 +252,29 @@ function setIframeUrl(path) {
252252
}
253253
contentIframe.contentWindow.location.replace(path);
254254
}
255+
async function getBookNameFromUUID(uuid) {
256+
const url = `${root}/catalog/v2/entries?id=${uuid}`;
257+
return await fetch(url).then(async (resp) => {
258+
const data = new window.DOMParser().parseFromString(await resp.text(), 'application/xml');
259+
const result = parseInt(data.querySelector('totalResults').innerHTML);
260+
let bookName;
261+
if (result > 0) {
262+
const bookContentLink = data.querySelector('link[type="text/html"]');
263+
const urlComponents = bookContentLink.getAttribute('href').split('/');
264+
bookName = urlComponents.pop();
265+
} else {
266+
bookName = null
267+
}
268+
return bookName;
269+
});
270+
}
255271

256-
function handle_location_hash_change() {
257-
const hash = window.location.hash.slice(1);
272+
async function handle_location_hash_change() {
273+
hash = window.location.hash.slice(1);
274+
//this regex exact matches zim UUIDS
275+
if (hash.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/) != null) {
276+
hash = await getBookNameFromUUID(hash)
277+
}
258278
console.log("handle_location_hash_change: " + hash);
259279
updateCurrentBookIfNeeded(hash);
260280
setIframeUrl(userUrl2IframeUrl(hash));

test/server.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ const ResourceCollection resources200Compressible{
7777
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/taskbar.css" },
7878
{ STATIC_CONTENT, "/ROOT%23%3F/skin/taskbar.css?cacheid=42e90cb9" },
7979
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/viewer.js" },
80-
{ STATIC_CONTENT, "/ROOT%23%3F/skin/viewer.js?cacheid=3208c3ed" },
80+
{ STATIC_CONTENT, "/ROOT%23%3F/skin/viewer.js?cacheid=a754e1ec" },
8181
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/fonts/Poppins.ttf" },
8282
{ STATIC_CONTENT, "/ROOT%23%3F/skin/fonts/Poppins.ttf?cacheid=af705837" },
8383
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/fonts/Roboto.ttf" },
@@ -242,7 +242,8 @@ TEST_F(ServerTest, 200)
242242

243243
TEST_F(ServerTest, 200_IdNameMapper)
244244
{
245-
EXPECT_EQ(404, zfs1_->GET("/ROOT%23%3F/content/6f1d19d0-633f-087b-fb55-7ac324ff9baf/A/index")->status);
245+
//both uuid and name should work but after NO_NAME_MAPPER only uuid should work
246+
EXPECT_EQ(200, zfs1_->GET("/ROOT%23%3F/content/6f1d19d0-633f-087b-fb55-7ac324ff9baf/A/index")->status);
246247
EXPECT_EQ(200, zfs1_->GET("/ROOT%23%3F/content/zimfile/A/index")->status);
247248
resetServer(ZimFileServer::NO_NAME_MAPPER);
248249
EXPECT_EQ(200, zfs1_->GET("/ROOT%23%3F/content/6f1d19d0-633f-087b-fb55-7ac324ff9baf/A/index")->status);
@@ -338,7 +339,7 @@ R"EXPECTEDRESULT( <link type="text/css" href="./skin/kiwix.css?cacheid=b4e29e
338339
<script type="text/javascript" src="./skin/polyfills.js?cacheid=a0e0343d"></script>
339340
<script type="module" src="./skin/i18n.js?cacheid=e9a10ac1" defer></script>
340341
<script type="text/javascript" src="./skin/languages.js?cacheid=08955948" defer></script>
341-
<script type="text/javascript" src="./skin/viewer.js?cacheid=3208c3ed" defer></script>
342+
<script type="text/javascript" src="./skin/viewer.js?cacheid=a754e1ec" defer></script>
342343
<script type="text/javascript" src="./skin/autoComplete/autoComplete.min.js?cacheid=1191aaaf"></script>
343344
const blankPageUrl = root + "/skin/blank.html?cacheid=6b1fa032";
344345
<label for="kiwix_button_show_toggle"><img src="./skin/caret.png?cacheid=22b942b4" alt=""></label>

0 commit comments

Comments
 (0)