Skip to content

Commit 08750c2

Browse files
Add support for accessing books using a metadata-based ID on /viewer#{bookId} and /content/{bookId}
1 parent 050906c commit 08750c2

File tree

5 files changed

+66
-15
lines changed

5 files changed

+66
-15
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 & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,7 @@ void Library::updateBookDB(const Book& book)
580580
indexer.index_text(title);
581581
indexer.increase_termpos();
582582
indexer.index_text(desc);
583-
583+
584584
// Index all fields for field-based search
585585
indexer.index_text(title, 1, "S");
586586
indexer.index_text(desc, 1, "XD");
@@ -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;
@@ -1039,6 +1051,10 @@ bool Filter::hasLang() const
10391051
{
10401052
return ACTIVE(LANG);
10411053
}
1054+
bool Filter::hasId() const
1055+
{
1056+
return ACTIVE(ID);
1057+
}
10421058

10431059
bool Filter::hasPublisher() const
10441060
{

src/server/internalServer.cpp

Lines changed: 18 additions & 5 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>
@@ -152,6 +153,9 @@ Filter get_search_filter(const RequestContext& request, const std::string& prefi
152153
try {
153154
filter.rejectTags(kiwix::split(request.get_argument(prefix+"notag"), ";"));
154155
} catch (...) {}
156+
try {
157+
filter.id(request.get_argument(prefix+"id"));
158+
} catch (const std::out_of_range&) {}
155159
return filter;
156160
}
157161

@@ -1179,11 +1183,20 @@ std::unique_ptr<Response> InternalServer::handle_content(const RequestContext& r
11791183
const std::string contentPrefix = "/content/";
11801184
const bool isContentPrefixedUrl = startsWith(url, contentPrefix);
11811185
const size_t prefixLength = isContentPrefixedUrl ? contentPrefix.size() : 1;
1182-
const std::string bookName = request.get_url_part(isContentPrefixedUrl);
1183-
1186+
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}");
1187+
1188+
std::string requestedBookNameOrUUID = request.get_url_part(isContentPrefixedUrl);
11841189
std::shared_ptr<zim::Archive> archive;
1190+
std::string bookName;
11851191
try {
1186-
const std::string bookId = mp_nameMapper->getIdForName(bookName);
1192+
std::string bookId;
1193+
if(std::regex_match(requestedBookNameOrUUID,uuid_regex)){
1194+
bookId=requestedBookNameOrUUID;
1195+
}else{
1196+
bookId = mp_nameMapper->getIdForName(requestedBookNameOrUUID);
1197+
}
1198+
1199+
bookName=mp_nameMapper->getNameForId(bookId);
11871200
archive = mp_library->getArchiveById(bookId);
11881201
} catch (const std::out_of_range& e) {}
11891202

@@ -1196,11 +1209,11 @@ std::unique_ptr<Response> InternalServer::handle_content(const RequestContext& r
11961209
if ( etag )
11971210
return Response::build_304(etag);
11981211

1199-
auto urlStr = url.substr(prefixLength + bookName.size());
1212+
auto urlStr = url.substr(prefixLength + requestedBookNameOrUUID.size());
12001213
if (urlStr[0] == '/') {
12011214
urlStr = urlStr.substr(1);
12021215
}
1203-
1216+
12041217
try {
12051218
auto entry = getEntryFromPath(*archive, urlStr);
12061219
if (entry.isRedirect() || urlStr != entry.getPath()) {

static/skin/viewer.js

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,9 +252,28 @@ 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+
if(hash.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/)!=null){
275+
hash= await getBookNameFromUUID(hash)
276+
}
258277
console.log("handle_location_hash_change: " + hash);
259278
updateCurrentBookIfNeeded(hash);
260279
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=26cafc44" },
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=26cafc44" 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)