Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use libzim for reading ZIM contents #1160

Merged
merged 13 commits into from
Jan 15, 2024
4 changes: 4 additions & 0 deletions i18n/en.jsonp.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions i18n/es.jsonp.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions i18n/fr.jsonp.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions www/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,23 @@ <h3 data-i18n="configure-expert-settings-title">Expert settings</h3>
<span data-i18n="configure-expert-bypassappcache"><b>Bypass AppCache</b> (<i>disables offline use of PWA!</i>)</span>
</label>
</div>
<div class="checkbox" id="">
<label data-i18n-tip="configure-expert-useLibzim-tip" title="Uses the selected version of libzim to access the ZIM contents (ServiceWorker mode only).">
<input type="checkbox" name="useLibzim" id="useLibzim">
<span><strong data-i18n="configure-expert-useLibzim">Use libzim for loading zim</strong> (<i data-i18n="configure-expert-useLibzim-warning">uses unstable libzim API for reading ZIM</i>)</span>
</label>
</div>
<div class="checkbox" id="libzimMode" style="display: none;">
<label>
<span><strong data-i18n="configure-expert-libzimMode">Select libzim version to use:</strong></span>
<select id="libzimModeSelect">
<option value="wasm">WASM</option>
<option value="asm">ASM</option>
<option value="wasm.dev">WASM Debug</option>
<option value="asm.dev">ASM Debug</option>
</select>
</label>
</div>
<p data-i18n="configure-expert-resetapp-description" class="pt-2">Reset the app to default settings and erase all caches:</p>
<div class="button">
<label data-i18n-tip="configure-expert-resetapp-tip" title="This will return the app to its original settings on launch, and will empty all caches and settings and deregister Service Workers. The app will then reload.">
Expand Down
62 changes: 59 additions & 3 deletions www/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const ASSETS_CACHE = 'kiwixjs-assetsCache';
var appstate = {};

/**
* @type ZIMArchive
* @type ZIMArchive | null
*/
var selectedArchive = null;

Expand Down Expand Up @@ -474,6 +474,18 @@ document.getElementById('bypassAppCacheCheck').addEventListener('change', functi
// This will also send any new values to Service Worker
refreshCacheStatus();
});

if (params.useLibzim) document.getElementById('libzimMode').style.display = '';
document.getElementById('libzimModeSelect').addEventListener('change', function (e) {
settingsStore.setItem('libzimMode', e.target.value);
window.location.reload();
});

document.getElementById('useLibzim').addEventListener('click', function (e) {
settingsStore.setItem('useLibzim', !params.useLibzim);
window.location.reload();
});

document.getElementById('disableDragAndDropCheck').addEventListener('change', function () {
params.disableDragAndDrop = !!this.checked;
settingsStore.setItem('disableDragAndDrop', params.disableDragAndDrop, Infinity);
Expand Down Expand Up @@ -841,7 +853,8 @@ function initServiceWorkerMessaging () {
// Until we find a way to tell where it is coming from, we allow the request through on all controlled clients and try to load the content
console.warn('>>> Allowing passthrough of SW request to process Zimit video <<<');
}
handleMessageChannelMessage(event);
if (params.useLibzim) handleMessageChannelByLibzim(event);
else handleMessageChannelMessage(event);
}
} else if (event.data.msg_type) {
// Messages received from the ReplayWorker
Expand Down Expand Up @@ -886,6 +899,49 @@ function initServiceWorkerMessaging () {
}
}

/**
* Function that handles a message of the messageChannel.
* This function will deal with the messages if useLibzim is set to true
*
* @param {Event} event The event object of the message channel
*/
async function handleMessageChannelByLibzim (event) {
// We received a message from the ServiceWorker
// The ServiceWorker asks for some content
const title = event.data.title;
const messagePort = event.ports[0];
try {
const ret = await selectedArchive.callLibzimWorker({ action: 'getEntryByPath', path: title })
if (ret === null) {
console.error('Title ' + title + ' not found in archive.');
messagePort.postMessage({ action: 'giveContent', title: title, content: '' });
return;
}

if (ret.mimetype === 'unknown') {
Rishabhg71 marked this conversation as resolved.
Show resolved Hide resolved
// We have a redirect to follow
// this is still a bit flawed, as we do not check if it's a redirect or the file doesn't exist
// We have no way to know if the file exists or not, so we have to assume it does and its just a redirect

const dirEntry = await new Promise((resolve, _reject) => selectedArchive.getMainPageDirEntry((value) => resolve(value)));
if (dirEntry.redirect) {
const redirect = await new Promise((resolve, _reject) => selectedArchive.resolveRedirect(dirEntry, (v) => resolve(v)));
const ret = await selectedArchive.callLibzimWorker({ action: 'getEntryByPath', path: redirect.namespace + '/' + redirect.url })
const message = { action: 'giveContent', title: title, content: ret.content, mimetype: ret.mimetype };
messagePort.postMessage(message);
return;
}
}

// Let's send the content to the ServiceWorker
const message = { action: 'giveContent', title: title, content: ret.content, mimetype: ret.mimetype };
messagePort.postMessage(message);
} catch (error) {
const message = { action: 'giveContent', title: title, content: new Uint8Array(), mimetype: '' };
messagePort.postMessage(message);
}
}

/**
* Sets the given injection mode.
* This involves registering (or re-enabling) the Service Worker if necessary
Expand Down Expand Up @@ -2475,7 +2531,7 @@ function displayArticleContentInIframe (dirEntry, htmlArticle) {
// Get the url and the resolution from the srcset entry
var urlMatch = imgAndResolutionUrl.match(/^\s*([^\s]+)\s+([0-9.]+\w+)\s*$/);
var url = urlMatch ? urlMatch[1] : '';
var resolution = urlMatch ? urlMatch[2]: '';
var resolution = urlMatch ? urlMatch[2] : '';
selectedArchive.getDirEntryByPath(url).then(function (srcEntry) {
selectedArchive.readBinaryFile(srcEntry, function (fileDirEntry, content) {
var mimetype = srcEntry.getMimetype();
Expand Down
6 changes: 6 additions & 0 deletions www/js/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@
* @property {string} cacheIDB - Name of the Indexed DB database
* @property {boolean} isFileSystemApiSupported - A boolean indicating whether the FileSystem API is supported.
* @property {boolean} isWebkitDirApiSupported - A boolean indicating whether the Webkit Directory API is supported.
* @property {boolean} useLibzim - A boolean indicating weather to use the libzim to load zim files.
* @property {"wasm-dev" | 'wasm' | 'asm' | 'asm-dev' | 'default'} libzimMode - A value indicating which libzim mode is selected.
* @property {DecompressorAPI} decompressorAPI

/**
Expand Down Expand Up @@ -123,6 +125,8 @@ params['cacheAPI'] = 'kiwix-js'; // Sets name of the prefix used to identify the
params['cacheIDB'] = 'kiwix-zim'; // Sets name of the Indexed DB database
params['isFileSystemApiSupported'] = typeof window.showOpenFilePicker === 'function'; // Sets a boolean indicating whether the FileSystem API is supported
params['isWebkitDirApiSupported'] = 'webkitdirectory' in document.createElement('input'); // Sets a boolean indicating whether the Webkit Directory API is supported
params['libzimMode'] = getSetting('libzimMode') || 'wasm'; // Sets a value indicating which libzim mode is selected
params['useLibzim'] = !!getSetting('useLibzim'); // Sets a value indicating which libzim mode is selected

/**
* Apply any override parameters that might be in the querystring.
Expand Down Expand Up @@ -185,6 +189,8 @@ document.getElementById('useHomeKeyToFocusSearchBarCheck').checked = params.useH
document.getElementById('openExternalLinksInNewTabsCheck').checked = params.openExternalLinksInNewTabs;
document.getElementById('languageSelector').value = params.overrideBrowserLanguage || 'default';
document.getElementById('bypassAppCacheCheck').checked = !params.appCache;
document.getElementById('libzimModeSelect').value = params.libzimMode;
document.getElementById('useLibzim').checked = params.useLibzim;
document.getElementById('appVersion').textContent = 'Kiwix ' + params.appVersion;

// This is a simplified version of code in settingsStore, because that module is not available in init.js
Expand Down
9 changes: 6 additions & 3 deletions www/js/lib/zimArchive.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,16 +142,19 @@ function ZIMArchive (storage, path, callbackReady, callbackError) {
]).then(function () {
that.libzimReady = null;
// There is currently an exception thrown in the libzim wasm if we attempt to load a split ZIM archive, so we work around
// In case of a split ZIM, It will not be loaded properly by libzim if libzim is enabled
var isSplitZim = /\.zima.$/i.test(that.file._files[0].name);
if (that.file.fullTextIndex && (params.debugLibzimASM || !isSplitZim && typeof Atomics !== 'undefined' &&
// Note that Android and NWJS currently throw due to problems with Web Worker context
!/Android/.test(params.appType) && !(window.nw && that.file._files[0].readMode === 'electron'))) {
var libzimReaderType = params.debugLibzimASM || ('WebAssembly' in self ? 'wasm' : 'asm');
console.log('Instantiating libzim ' + libzimReaderType + ' Web Worker...');
!/Android/.test(params.appType) && !(window.nw && that.file._files[0].readMode === 'electron')) || params.useLibzim) {
var libzimReaderType = params.libzimMode;
if (libzimReaderType === 'default') libzimReaderType = 'WebAssembly' in self ? 'wasm.dev' : 'asm.dev';
console.log('[DEBUG] Instantiating libzim ' + libzimReaderType + ' Web Worker...');
LZ = new Worker('js/lib/libzim-' + libzimReaderType + '.js');
that.callLibzimWorker({ action: 'init', files: that.file._files }).then(function () {
that.libzimReady = 'ready';
params.searchProvider = 'fulltext: ' + libzimReaderType;
if (params.useLibzim) whenZimReady();
// Update the API panel
uiUtil.reportSearchProviderToAPIStatusPanel(params.searchProvider);
}).catch(function (err) {
Expand Down