diff --git a/.eslintrc.cjs b/.eslintrc.cjs
index e3c53898c..c00a10fef 100644
--- a/.eslintrc.cjs
+++ b/.eslintrc.cjs
@@ -19,6 +19,8 @@ module.exports = {
'no-unused-vars': 1,
'n/no-callback-literal': 0,
'object-shorthand': 0,
- 'multiline-ternary': 0
+ 'multiline-ternary': 0,
+ 'no-extend-native': 0,
+ 'no-global-assign': 0
}
}
diff --git a/nightwatch.js b/nightwatch.js
index 56c1628b4..9d8035003 100644
--- a/nightwatch.js
+++ b/nightwatch.js
@@ -1,119 +1,123 @@
/**
* nightwatch.js : Configuration of nightwatch.
* Global settings of NightWatch.
- *
+ *
* Copyright 2017 Mossroy and contributors
* License GPL v3:
- *
+ *
* This file is part of Kiwix.
- *
+ *
* Kiwix is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
- *
+ *
* Kiwix is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+ *
* You should have received a copy of the GNU General Public License
* along with Kiwix (file LICENSE-GPLv3.txt). If not, see
*/
+
'use strict';
+
+/* eslint-disable no-template-curly-in-string */
+
const build = `${process.env.GITHUB_REPOSITORY} run #${process.env.GITHUB_RUN_ID}`;
module.exports = {
- "src_folders" : ["browser-tests"],
- "output_folder" : "reports",
- "custom_commands_path" : "",
- "custom_assertions_path" : "",
- "page_objects_path" : "",
- "globals_path" : "",
+ src_folders: ['browser-tests'],
+ output_folder: 'reports',
+ custom_commands_path: '',
+ custom_assertions_path: '',
+ page_objects_path: '',
+ globals_path: '',
- "test_settings" : {
- "default" : {
- "launch_url": "http://ondemand.saucelabs.com:80",
- "selenium_port": 80,
- "selenium_host": "ondemand.saucelabs.com",
- "silent": true,
- "username": "${SAUCE_USERNAME}",
- "access_key": "${SAUCE_ACCESS_KEY}",
- "screenshots" : {
- "enabled" : false
- },
- "globals": {
- "waitForConditionTimeout": 600
- }
- },
- "firefox52" : {
- "desiredCapabilities": {
- "browserName": "firefox",
- "version": "52.0",
- "javascriptEnabled": true,
- "acceptSslCerts": true,
- "build": build
- }
- },
- "firefox" : {
- "desiredCapabilities": {
- "browserName": "firefox",
- "version": "latest",
- "javascriptEnabled": true,
- "acceptSslCerts": true,
- "build": build
- }
- },
- "chrome58" : {
- "desiredCapabilities": {
- "browserName": "chrome",
- "version": "58.0",
- "javascriptEnabled": true,
- "acceptSslCerts": true,
- "build": build
- }
- },
- "chrome" : {
- "desiredCapabilities": {
- "browserName": "chrome",
- "javascriptEnabled": true,
- "acceptSslCerts": true,
- "build": build
- }
- },
- "edge" : {
- "desiredCapabilities": {
- "browserName": "MicrosoftEdge",
- "javascriptEnabled": true,
- "acceptSslCerts": true,
- "build": build
- }
- },
- "edge40" : {
- "desiredCapabilities": {
- "browserName": "MicrosoftEdge",
- "version": "15.15063",
- "javascriptEnabled": true,
- "acceptSslCerts": true,
- "build": build
- }
- },
- "edge44" : {
- "desiredCapabilities": {
- "browserName": "MicrosoftEdge",
- "version": "18.17763",
- "javascriptEnabled": true,
- "acceptSslCerts": true,
- "build": build
- }
- },
- "ie11" : {
- "desiredCapabilities": {
- "browserName": "internet explorer",
- "javascriptEnabled": true,
- "acceptSslCerts": true,
- "build": build
- }
+ test_settings: {
+ default: {
+ launch_url: 'http://ondemand.saucelabs.com:80',
+ selenium_port: 80,
+ selenium_host: 'ondemand.saucelabs.com',
+ silent: true,
+ username: '${SAUCE_USERNAME}',
+ access_key: '${SAUCE_ACCESS_KEY}',
+ screenshots: {
+ enabled: false
+ },
+ globals: {
+ waitForConditionTimeout: 600
+ }
+ },
+ firefox52: {
+ desiredCapabilities: {
+ browserName: 'firefox',
+ version: '52.0',
+ javascriptEnabled: true,
+ acceptSslCerts: true,
+ build: build
+ }
+ },
+ firefox: {
+ desiredCapabilities: {
+ browserName: 'firefox',
+ version: 'latest',
+ javascriptEnabled: true,
+ acceptSslCerts: true,
+ build: build
+ }
+ },
+ chrome58: {
+ desiredCapabilities: {
+ browserName: 'chrome',
+ version: '58.0',
+ javascriptEnabled: true,
+ acceptSslCerts: true,
+ build: build
+ }
+ },
+ chrome: {
+ desiredCapabilities: {
+ browserName: 'chrome',
+ javascriptEnabled: true,
+ acceptSslCerts: true,
+ build: build
+ }
+ },
+ edge: {
+ desiredCapabilities: {
+ browserName: 'MicrosoftEdge',
+ javascriptEnabled: true,
+ acceptSslCerts: true,
+ build: build
+ }
+ },
+ edge40: {
+ desiredCapabilities: {
+ browserName: 'MicrosoftEdge',
+ version: '15.15063',
+ javascriptEnabled: true,
+ acceptSslCerts: true,
+ build: build
+ }
+ },
+ edge44: {
+ desiredCapabilities: {
+ browserName: 'MicrosoftEdge',
+ version: '18.17763',
+ javascriptEnabled: true,
+ acceptSslCerts: true,
+ build: build
+ }
+ },
+ ie11: {
+ desiredCapabilities: {
+ browserName: 'internet explorer',
+ javascriptEnabled: true,
+ acceptSslCerts: true,
+ build: build
+ }
+ }
}
- }
};
diff --git a/tests/init.js b/tests/init.js
index 0cca17e04..cc3366bd1 100644
--- a/tests/init.js
+++ b/tests/init.js
@@ -1,27 +1,30 @@
/**
* init.js : Configuration for the library require.js
* This file handles the dependencies between javascript libraries, for the unit tests
- *
+ *
* Copyright 2013-2014 Mossroy and contributors
* License GPL v3:
- *
+ *
* This file is part of Kiwix.
- *
+ *
* Kiwix is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
- *
+ *
* Kiwix is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+ *
* You should have received a copy of the GNU General Public License
* along with Kiwix (file LICENSE-GPLv3.txt). If not, see
*/
+
'use strict';
+/* global requirejs */
+
// Define global params needed for tests to run on existing app code
var params = {};
var webpMachine = true;
@@ -29,11 +32,11 @@ var webpMachine = true;
require.config({
baseUrl: (window.__karma__ ? 'base/' : '') + 'www/js/lib/',
paths: {
- 'jquery': 'jquery-3.7.0.slim.min',
- 'webpHeroBundle': 'webpHeroBundle_0.0.2'
+ jquery: 'jquery-3.7.0.slim.min',
+ webpHeroBundle: 'webpHeroBundle_0.0.2'
},
shim: {
- 'webpHeroBundle': ''
+ webpHeroBundle: ''
}
});
diff --git a/tests/karma.conf.local.js b/tests/karma.conf.local.js
index ad4d27cb0..ab9ff59a6 100644
--- a/tests/karma.conf.local.js
+++ b/tests/karma.conf.local.js
@@ -1,36 +1,36 @@
module.exports = function (config) {
- config.set({
- basePath: '../',
- // https://karma-runner.github.io/5.2/config/browsers.html
- browsers: [
- 'FirefoxHeadless',
- // During local development, consider Chrome and Chromium to be similar enough
- // and pick whichever the developer is most likely to have.
- // In general, Linux distros provide and update Chromium only,
- // whereas Windows and macOS users tend to have auto-updating Google Chrome.
- //
- // See package.json for commands to run tests in a single browser only.
- //
- // The CHROME_BIN check is to temporarily accomodate GitHub Actions
- // which oddly uses Ubuntu but replaces the standard Chromium distribution
- // with a custom install of Google Chrome. Being fixed in the next release:
- // https://github.com/actions/virtual-environments/issues/2388
- process.platform === 'linux' && !process.env.CHROME_BIN ? 'ChromiumHeadless' : 'ChromeHeadless'
- ],
- frameworks: ['qunit'],
- client: {
- qunit: {
- autostart: false
- }
- },
- // logLevel: 'DEBUG',
- files: [
- 'www/js/lib/require.js',
- 'tests/init.js',
- { pattern: 'www/**/*', included: false },
- { pattern: 'tests/**/*', included: false }
- ],
- singleRun: true,
- autoWatch: false
- });
+ config.set({
+ basePath: '../',
+ // https://karma-runner.github.io/5.2/config/browsers.html
+ browsers: [
+ 'FirefoxHeadless',
+ // During local development, consider Chrome and Chromium to be similar enough
+ // and pick whichever the developer is most likely to have.
+ // In general, Linux distros provide and update Chromium only,
+ // whereas Windows and macOS users tend to have auto-updating Google Chrome.
+ //
+ // See package.json for commands to run tests in a single browser only.
+ //
+ // The CHROME_BIN check is to temporarily accomodate GitHub Actions
+ // which oddly uses Ubuntu but replaces the standard Chromium distribution
+ // with a custom install of Google Chrome. Being fixed in the next release:
+ // https://github.com/actions/virtual-environments/issues/2388
+ process.platform === 'linux' && !process.env.CHROME_BIN ? 'ChromiumHeadless' : 'ChromeHeadless'
+ ],
+ frameworks: ['qunit'],
+ client: {
+ qunit: {
+ autostart: false
+ }
+ },
+ // logLevel: 'DEBUG',
+ files: [
+ 'www/js/lib/require.js',
+ 'tests/init.js',
+ { pattern: 'www/**/*', included: false },
+ { pattern: 'tests/**/*', included: false }
+ ],
+ singleRun: true,
+ autoWatch: false
+ });
};
diff --git a/tests/karma.conf.saucelabs.js b/tests/karma.conf.saucelabs.js
index 6e25ce66c..e87df588c 100644
--- a/tests/karma.conf.saucelabs.js
+++ b/tests/karma.conf.saucelabs.js
@@ -1,85 +1,85 @@
module.exports = function (config) {
- config.set({
- basePath: '../',
- // https://karma-runner.github.io/5.2/config/browsers.html
- // https://github.com/karma-runner/karma-sauce-launcher
- sauceLabs: {
- build: process.env.GITHUB_RUN_ID ? `${process.env.GITHUB_REPOSITORY} run #${process.env.GITHUB_RUN_ID}` : null,
- startConnect: true,
- username: process.env.SAUCE_USERNAME,
- accessKey: process.env.SAUCE_ACCESS_KEY
- },
- customLaunchers: {
- firefox52: {
- base: 'SauceLabs',
- browserName: 'firefox',
- version: '52.0'
- },
- firefox: {
- base: 'SauceLabs',
- browserName: 'firefox',
- version: 'latest'
- },
- chrome58: {
- base: 'SauceLabs',
- browserName: 'chrome',
- version: '58.0'
- },
- chrome: {
- base: 'SauceLabs',
- browserName: 'chrome',
- version: 'latest'
- },
- edge: {
- base: 'SauceLabs',
- browserName: 'MicrosoftEdge',
- version: 'latest'
- },
- edge40: {
- base: 'SauceLabs',
- browserName: 'MicrosoftEdge',
- version: '15.15063'
- },
- edge44: {
- base: 'SauceLabs',
- browserName: 'MicrosoftEdge',
- version: '18.17763'
- },
- ie11: {
- base: 'SauceLabs',
- browserName: 'internet explorer',
- version: 'latest'
- }
- },
- // The free account on Sauce does not allow more than 5 concurrent sessions
- concurrency: 4,
+ config.set({
+ basePath: '../',
+ // https://karma-runner.github.io/5.2/config/browsers.html
+ // https://github.com/karma-runner/karma-sauce-launcher
+ sauceLabs: {
+ build: process.env.GITHUB_RUN_ID ? `${process.env.GITHUB_REPOSITORY} run #${process.env.GITHUB_RUN_ID}` : null,
+ startConnect: true,
+ username: process.env.SAUCE_USERNAME,
+ accessKey: process.env.SAUCE_ACCESS_KEY
+ },
+ customLaunchers: {
+ firefox52: {
+ base: 'SauceLabs',
+ browserName: 'firefox',
+ version: '52.0'
+ },
+ firefox: {
+ base: 'SauceLabs',
+ browserName: 'firefox',
+ version: 'latest'
+ },
+ chrome58: {
+ base: 'SauceLabs',
+ browserName: 'chrome',
+ version: '58.0'
+ },
+ chrome: {
+ base: 'SauceLabs',
+ browserName: 'chrome',
+ version: 'latest'
+ },
+ edge: {
+ base: 'SauceLabs',
+ browserName: 'MicrosoftEdge',
+ version: 'latest'
+ },
+ edge40: {
+ base: 'SauceLabs',
+ browserName: 'MicrosoftEdge',
+ version: '15.15063'
+ },
+ edge44: {
+ base: 'SauceLabs',
+ browserName: 'MicrosoftEdge',
+ version: '18.17763'
+ },
+ ie11: {
+ base: 'SauceLabs',
+ browserName: 'internet explorer',
+ version: 'latest'
+ }
+ },
+ // The free account on Sauce does not allow more than 5 concurrent sessions
+ concurrency: 4,
- // REMINDER: Keep this list in sync with the UI tests, in .github/workflows/CI.yml.
- browsers: [
- // 'firefox', // Disable latest Firefox due to #894
- 'chrome',
- 'edge',
- 'edge40',
- 'edge44',
- 'firefox52',
- 'chrome58'
- // 'ie11' // Disable IE11 due to Promise error
- ],
- frameworks: ['qunit'],
- client: {
- qunit: {
- autostart: false
- }
- },
- reporters: ['dots'],
- logLevel: 'WARN',
- files: [
- 'www/js/lib/require.js',
- 'tests/init.js',
- { pattern: 'www/**/*', included: false },
- { pattern: 'tests/**/*', included: false }
- ],
- singleRun: true,
- autoWatch: false
- });
+ // REMINDER: Keep this list in sync with the UI tests, in .github/workflows/CI.yml.
+ browsers: [
+ // 'firefox', // Disable latest Firefox due to #894
+ 'chrome',
+ 'edge',
+ 'edge40',
+ 'edge44',
+ 'firefox52',
+ 'chrome58'
+ // 'ie11' // Disable IE11 due to Promise error
+ ],
+ frameworks: ['qunit'],
+ client: {
+ qunit: {
+ autostart: false
+ }
+ },
+ reporters: ['dots'],
+ logLevel: 'WARN',
+ files: [
+ 'www/js/lib/require.js',
+ 'tests/init.js',
+ { pattern: 'www/**/*', included: false },
+ { pattern: 'tests/**/*', included: false }
+ ],
+ singleRun: true,
+ autoWatch: false
+ });
};
diff --git a/tests/tests.js b/tests/tests.js
index fca69ab90..bd491652c 100644
--- a/tests/tests.js
+++ b/tests/tests.js
@@ -19,374 +19,373 @@
* You should have received a copy of the GNU General Public License
* along with Kiwix (file LICENSE-GPLv3.txt). If not, see
*/
-define(['jquery', 'zimArchive', 'zimDirEntry', 'util', 'uiUtil', 'utf8'],
- function($, zimArchive, zimDirEntry, util, uiUtil, utf8) {
- var localZimArchive;
+/* global define, require, QUnit, Promise */
- /**
- * Make an HTTP request for a Blob and return a Promise
- *
- * @param {String} url URL to download from
- * @param {String} name Name to give to the Blob instance
- * @returns {Promise} A Promise for the Blob
- */
- function makeBlobRequest(url, name) {
- return new Promise(function (resolve, reject) {
- var xhr = new XMLHttpRequest();
- xhr.open('GET', url);
- xhr.onreadystatechange = function () {
- if (xhr.readyState === XMLHttpRequest.DONE) {
- if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 0 ) {
- var blob = new Blob([xhr.response], {type: 'application/octet-stream'});
- blob.name = name;
- resolve(blob);
- }
- else {
- console.error("Error reading file " + url + " status:" + xhr.status + ", statusText:" + xhr.statusText);
- reject({
- status: xhr.status,
- statusText: xhr.statusText
- });
+define(['jquery', 'zimArchive', 'zimDirEntry', 'util', 'uiUtil', 'utf8'],
+ function ($, zimArchive, zimDirEntry, util, uiUtil, utf8) {
+ var localZimArchive;
+
+ /**
+ * Make an HTTP request for a Blob and return a Promise
+ *
+ * @param {String} url URL to download from
+ * @param {String} name Name to give to the Blob instance
+ * @returns {Promise} A Promise for the Blob
+ */
+ function makeBlobRequest (url, name) {
+ return new Promise(function (resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', url);
+ xhr.onreadystatechange = function () {
+ if (xhr.readyState === XMLHttpRequest.DONE) {
+ if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 0) {
+ var blob = new Blob([xhr.response], { type: 'application/octet-stream' });
+ blob.name = name;
+ resolve(blob);
+ } else {
+ console.error('Error reading file ' + url + ' status:' + xhr.status + ', statusText:' + xhr.statusText);
+ reject(new Error({
+ status: xhr.status,
+ statusText: xhr.statusText
+ }));
+ }
}
- }
- };
- xhr.onerror = function () {
- console.error("Error reading file " + url + " status:" + xhr.status + ", statusText:" + xhr.statusText);
- reject({
- status: xhr.status,
- statusText: xhr.statusText
- });
- };
- xhr.responseType = 'blob';
- xhr.send();
- });
- }
+ };
+ xhr.onerror = function () {
+ console.error('Error reading file ' + url + ' status:' + xhr.status + ', statusText:' + xhr.statusText);
+ reject(new Error({
+ status: xhr.status,
+ statusText: xhr.statusText
+ }));
+ };
+ xhr.responseType = 'blob';
+ xhr.send();
+ });
+ }
- // Let's try to download the ZIM files
- var zimArchiveFiles = new Array();
+ // Let's try to download the ZIM files
+ var zimArchiveFiles = [];
- var splitBlobs = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o'].map(function(c) {
- var filename = 'wikipedia_en_ray_charles_2015-06.zima' + c;
- return makeBlobRequest(require.toUrl('../../../tests/' + filename), filename);
- });
- Promise.all(splitBlobs)
- .then(function(values) {
- zimArchiveFiles = values;
- }).then(function() {
- // Create a localZimArchive from selected files, in order to run the following tests
- localZimArchive = new zimArchive.ZIMArchive(zimArchiveFiles, null, function (zimArchive) {
- runTests();
+ var splitBlobs = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o'].map(function (c) {
+ var filename = 'wikipedia_en_ray_charles_2015-06.zima' + c;
+ return makeBlobRequest(require.toUrl('../../../tests/' + filename), filename);
});
- });
-
- var runTests = function() {
+ Promise.all(splitBlobs)
+ .then(function (values) {
+ zimArchiveFiles = values;
+ }).then(function () {
+ // Create a localZimArchive from selected files, in order to run the following tests
+ localZimArchive = new zimArchive.ZIMArchive(zimArchiveFiles, null, function (zimArchive) {
+ runTests();
+ });
+ });
- QUnit.module("environment");
- QUnit.test("qunit test", function(assert) {
- assert.equal("test", "test", "QUnit is properly configured");
- });
+ var runTests = function () {
+ QUnit.module('environment');
+ QUnit.test('qunit test', function (assert) {
+ assert.equal('test', 'test', 'QUnit is properly configured');
+ });
- QUnit.test("check archive files are read", function(assert) {
- assert.ok(zimArchiveFiles && zimArchiveFiles[0] && zimArchiveFiles[0].size > 0, "ZIM file read and not empty");
- });
+ QUnit.test('check archive files are read', function (assert) {
+ assert.ok(zimArchiveFiles && zimArchiveFiles[0] && zimArchiveFiles[0].size > 0, 'ZIM file read and not empty');
+ });
- QUnit.module("utils");
- QUnit.test("check reading an IEEE_754 float from 4 bytes" ,function(assert) {
- var byteArray = new Uint8Array(4);
- // This example is taken from https://fr.wikipedia.org/wiki/IEEE_754#Un_exemple_plus_complexe
- // 1100 0010 1110 1101 0100 0000 0000 0000
- byteArray[0] = 194;
- byteArray[1] = 237;
- byteArray[2] = 64;
- byteArray[3] = 0;
- var float = util.readFloatFrom4Bytes(byteArray, 0);
- assert.equal(float, -118.625, "the IEEE_754 float should be converted as -118.625");
- });
- QUnit.test("check upper/lower case variations", function (assert) {
- var testString1 = "téléphone";
- var testString2 = "Paris";
- var testString3 = "le Couvre-chef Est sur le porte-manteaux";
- var testString4 = "épée";
- var testString5 = '$¥€“«xριστός» †¡Ἀνέστη!”';
- var testString6 = "Καλά Νερά Μαγνησία žižek";
- assert.equal(util.allCaseFirstLetters(testString1).indexOf("Téléphone") >= 0, true, "The first letter should be uppercase");
- assert.equal(util.allCaseFirstLetters(testString2).indexOf("paris") >= 0, true, "The first letter should be lowercase");
- assert.equal(util.allCaseFirstLetters(testString3).indexOf("Le Couvre-Chef Est Sur Le Porte-Manteaux") >= 0, true, "The first letter of every word should be uppercase");
- assert.equal(util.allCaseFirstLetters(testString4).indexOf("Épée") >= 0, true, "The first letter should be uppercase (with accent)");
- assert.equal(util.allCaseFirstLetters(testString5).indexOf('$¥€“«Xριστός» †¡ἀνέστη!”') >= 0, true, "First non-punctuation/non-currency Unicode letter should be uppercase, second (with breath mark) lowercase");
- assert.equal(util.allCaseFirstLetters(testString6, "full").indexOf("ΚΑΛΆ ΝΕΡΆ ΜΑΓΝΗΣΊΑ ŽIŽEK") >= 0, true, "All Unicode letters should be uppercase");
- });
- QUnit.test("check removal of parameters in URL", function(assert) {
- var baseUrl = "A/Che cosa è l'amore?.html";
- var testUrls = [
- "A/Che%20cosa%20%C3%A8%20l'amore%3F.html?param1=toto¶m2=titi",
- "A/Che%20cosa%20%C3%A8%20l'amore%3F.html?param1=toto¶m2=titi#anchor",
- "A/Che%20cosa%20%C3%A8%20l'amore%3F.html#anchor"
- ];
- testUrls.forEach(function (testUrl) {
- assert.equal(decodeURIComponent(
- uiUtil.removeUrlParameters(testUrl)
- ), baseUrl);
+ QUnit.module('utils');
+ QUnit.test('check reading an IEEE_754 float from 4 bytes', function (assert) {
+ var byteArray = new Uint8Array(4);
+ // This example is taken from https://fr.wikipedia.org/wiki/IEEE_754#Un_exemple_plus_complexe
+ // 1100 0010 1110 1101 0100 0000 0000 0000
+ byteArray[0] = 194;
+ byteArray[1] = 237;
+ byteArray[2] = 64;
+ byteArray[3] = 0;
+ var float = util.readFloatFrom4Bytes(byteArray, 0);
+ assert.equal(float, -118.625, 'the IEEE_754 float should be converted as -118.625');
+ });
+ QUnit.test('check upper/lower case variations', function (assert) {
+ var testString1 = 'téléphone';
+ var testString2 = 'Paris';
+ var testString3 = 'le Couvre-chef Est sur le porte-manteaux';
+ var testString4 = 'épée';
+ var testString5 = '$¥€“«xριστός» †¡Ἀνέστη!”';
+ var testString6 = 'Καλά Νερά Μαγνησία žižek';
+ assert.equal(util.allCaseFirstLetters(testString1).indexOf('Téléphone') >= 0, true, 'The first letter should be uppercase');
+ assert.equal(util.allCaseFirstLetters(testString2).indexOf('paris') >= 0, true, 'The first letter should be lowercase');
+ assert.equal(util.allCaseFirstLetters(testString3).indexOf('Le Couvre-Chef Est Sur Le Porte-Manteaux') >= 0, true, 'The first letter of every word should be uppercase');
+ assert.equal(util.allCaseFirstLetters(testString4).indexOf('Épée') >= 0, true, 'The first letter should be uppercase (with accent)');
+ assert.equal(util.allCaseFirstLetters(testString5).indexOf('$¥€“«Xριστός» †¡ἀνέστη!”') >= 0, true, 'First non-punctuation/non-currency Unicode letter should be uppercase, second (with breath mark) lowercase');
+ assert.equal(util.allCaseFirstLetters(testString6, 'full').indexOf('ΚΑΛΆ ΝΕΡΆ ΜΑΓΝΗΣΊΑ ŽIŽEK') >= 0, true, 'All Unicode letters should be uppercase');
+ });
+ QUnit.test('check removal of parameters in URL', function (assert) {
+ var baseUrl = "A/Che cosa è l'amore?.html";
+ var testUrls = [
+ "A/Che%20cosa%20%C3%A8%20l'amore%3F.html?param1=toto¶m2=titi",
+ "A/Che%20cosa%20%C3%A8%20l'amore%3F.html?param1=toto¶m2=titi#anchor",
+ "A/Che%20cosa%20%C3%A8%20l'amore%3F.html#anchor"
+ ];
+ testUrls.forEach(function (testUrl) {
+ assert.equal(decodeURIComponent(
+ uiUtil.removeUrlParameters(testUrl)
+ ), baseUrl);
+ });
});
- });
- QUnit.module("ZIM initialisation");
- QUnit.test("ZIM archive is ready", function(assert) {
- assert.ok(localZimArchive.isReady() === true, "ZIM archive should be set as ready");
- });
+ QUnit.module('ZIM initialisation');
+ QUnit.test('ZIM archive is ready', function (assert) {
+ assert.ok(localZimArchive.isReady() === true, 'ZIM archive should be set as ready');
+ });
- QUnit.module("ZIM metadata");
- QUnit.test("read ZIM language", function(assert) {
- var done = assert.async();
- assert.expect(1);
- var callbackFunction = function(language) {
- assert.equal(language , 'eng', 'The language read inside the Metadata should be "eng" for "English"');
- done();
- };
- localZimArchive.getMetadata("Language", callbackFunction);
- });
- QUnit.test("try to read a missing metadata", function(assert) {
- var done = assert.async();
- assert.expect(1);
- var callbackFunction = function(string) {
- assert.equal(string, undefined, 'The metadata zzz should not be found inside the ZIM');
- done();
- };
- localZimArchive.getMetadata("zzz", callbackFunction);
- });
+ QUnit.module('ZIM metadata');
+ QUnit.test('read ZIM language', function (assert) {
+ var done = assert.async();
+ assert.expect(1);
+ var callbackFunction = function (language) {
+ assert.equal(language, 'eng', 'The language read inside the Metadata should be "eng" for "English"');
+ done();
+ };
+ localZimArchive.getMetadata('Language', callbackFunction);
+ });
+ QUnit.test('try to read a missing metadata', function (assert) {
+ var done = assert.async();
+ assert.expect(1);
+ var callbackFunction = function (string) {
+ assert.equal(string, undefined, 'The metadata zzz should not be found inside the ZIM');
+ done();
+ };
+ localZimArchive.getMetadata('zzz', callbackFunction);
+ });
- QUnit.module("zim_direntry_search_and_read");
- QUnit.test("check DirEntry.fromStringId 'A Fool for You'", function(assert) {
- var done = assert.async();
- var aFoolForYouDirEntry = zimDirEntry.DirEntry.fromStringId(localZimArchive._file, "5856|7|A|0|2|A_Fool_for_You.html|A Fool for You|false|undefined");
+ QUnit.module('zim_direntry_search_and_read');
+ QUnit.test("check DirEntry.fromStringId 'A Fool for You'", function (assert) {
+ var done = assert.async();
+ var aFoolForYouDirEntry = zimDirEntry.DirEntry.fromStringId(localZimArchive._file, '5856|7|A|0|2|A_Fool_for_You.html|A Fool for You|false|undefined');
- assert.expect(2);
- var callbackFunction = function(dirEntry, htmlArticle) {
- assert.ok(htmlArticle && htmlArticle.length > 0, "Article not empty");
- // Remove new lines
- htmlArticle = htmlArticle.replace(/[\r\n]/g, " ");
- assert.ok(htmlArticle.match("^.*]*>A Fool for You
"), "'A Fool for You' title somewhere in the article");
- done();
- };
- localZimArchive.readUtf8File(aFoolForYouDirEntry, callbackFunction);
- });
- QUnit.test("check findDirEntriesWithPrefix 'A'", function(assert) {
- var done = assert.async();
- assert.expect(2);
- var callbackFunction = function(dirEntryList) {
- assert.ok(dirEntryList && dirEntryList.length === 5, "Article list with 5 results");
- var firstDirEntry = dirEntryList[0];
- assert.equal(firstDirEntry.getTitleOrUrl() , 'A Fool for You', 'First result should be "A Fool for You"');
- done();
- };
- localZimArchive.findDirEntriesWithPrefix({prefix: 'A', size: 5}, callbackFunction, true);
- });
- QUnit.test("check findDirEntriesWithPrefix 'a'", function(assert) {
- var done = assert.async();
- assert.expect(2);
- var callbackFunction = function(dirEntryList) {
- assert.ok(dirEntryList && dirEntryList.length === 5, "Article list with 5 results");
- var firstDirEntry = dirEntryList[0];
- assert.equal(firstDirEntry.getTitleOrUrl() , 'A Fool for You', 'First result should be "A Fool for You"');
- done();
- };
- localZimArchive.findDirEntriesWithPrefix({prefix: 'a', size: 5}, callbackFunction, true);
- });
- QUnit.test("check findDirEntriesWithPrefix 'blues brothers'", function(assert) {
- var done = assert.async();
- assert.expect(2);
- var callbackFunction = function(dirEntryList) {
- assert.ok(dirEntryList && dirEntryList.length === 3, "Article list with 3 result");
- var firstDirEntry = dirEntryList[0];
- assert.equal(firstDirEntry.getTitleOrUrl() , 'Blues Brothers (film)', 'First result should be "Blues Brothers (film)"');
- done();
- };
- localZimArchive.findDirEntriesWithPrefix({prefix: 'blues brothers', size: 5}, callbackFunction, true);
- });
- QUnit.test("article '(The Night Time Is) The Right Time' correctly redirects to 'Night Time Is the Right Time'", function(assert) {
- var done = assert.async();
- assert.expect(6);
- localZimArchive.getDirEntryByPath("A/(The_Night_Time_Is)_The_Right_Time.html").then(function(dirEntry) {
- assert.ok(dirEntry !== null, "DirEntry found");
- if (dirEntry !== null) {
- assert.ok(dirEntry.isRedirect(), "DirEntry is a redirect.");
- assert.equal(dirEntry.getTitleOrUrl(), "(The Night Time Is) The Right Time", "Correct redirect title name.");
- localZimArchive.resolveRedirect(dirEntry, function(dirEntry) {
- assert.ok(dirEntry !== null, "DirEntry found");
- assert.ok(!dirEntry.isRedirect(), "DirEntry is not a redirect.");
- assert.equal(dirEntry.getTitleOrUrl(), "Night Time Is the Right Time", "Correct redirected title name.");
- done();
- });
- } else {
+ assert.expect(2);
+ var callbackFunction = function (dirEntry, htmlArticle) {
+ assert.ok(htmlArticle && htmlArticle.length > 0, 'Article not empty');
+ // Remove new lines
+ htmlArticle = htmlArticle.replace(/[\r\n]/g, ' ');
+ assert.ok(htmlArticle.match('^.*]*>A Fool for You
'), "'A Fool for You' title somewhere in the article");
done();
- }
+ };
+ localZimArchive.readUtf8File(aFoolForYouDirEntry, callbackFunction);
});
- });
- QUnit.test("article 'Raelettes' correctly redirects to 'The Raelettes'", function(assert) {
- var done = assert.async();
- assert.expect(6);
- localZimArchive.getDirEntryByPath("A/Raelettes.html").then(function(dirEntry) {
- assert.ok(dirEntry !== null, "DirEntry found");
- if (dirEntry !== null) {
- assert.ok(dirEntry.isRedirect(), "DirEntry is a redirect.");
- assert.equal(dirEntry.getTitleOrUrl(), "Raelettes", "Correct redirect title name.");
- localZimArchive.resolveRedirect(dirEntry, function(dirEntry) {
- assert.ok(dirEntry !== null, "DirEntry found");
- assert.ok(!dirEntry.isRedirect(), "DirEntry is not a redirect.");
- assert.equal(dirEntry.getTitleOrUrl(), "The Raelettes", "Correct redirected title name.");
- done();
- });
- } else {
+ QUnit.test("check findDirEntriesWithPrefix 'A'", function (assert) {
+ var done = assert.async();
+ assert.expect(2);
+ var callbackFunction = function (dirEntryList) {
+ assert.ok(dirEntryList && dirEntryList.length === 5, 'Article list with 5 results');
+ var firstDirEntry = dirEntryList[0];
+ assert.equal(firstDirEntry.getTitleOrUrl(), 'A Fool for You', 'First result should be "A Fool for You"');
done();
- }
+ };
+ localZimArchive.findDirEntriesWithPrefix({ prefix: 'A', size: 5 }, callbackFunction, true);
});
- });
- QUnit.test("article 'Bein Green' correctly redirects to 'Bein' Green", function(assert) {
- var done = assert.async();
- assert.expect(6);
- localZimArchive.getDirEntryByPath("A/Bein_Green.html").then(function(dirEntry) {
- assert.ok(dirEntry !== null, "DirEntry found");
- if (dirEntry !== null) {
- assert.ok(dirEntry.isRedirect(), "DirEntry is a redirect.");
- assert.equal(dirEntry.getTitleOrUrl(), "Bein Green", "Correct redirect title name.");
- localZimArchive.resolveRedirect(dirEntry, function(dirEntry) {
- assert.ok(dirEntry !== null, "DirEntry found");
- assert.ok(!dirEntry.isRedirect(), "DirEntry is not a redirect.");
- assert.equal(dirEntry.getTitleOrUrl(), "Bein' Green", "Correct redirected title name.");
- done();
- });
- } else {
+ QUnit.test("check findDirEntriesWithPrefix 'a'", function (assert) {
+ var done = assert.async();
+ assert.expect(2);
+ var callbackFunction = function (dirEntryList) {
+ assert.ok(dirEntryList && dirEntryList.length === 5, 'Article list with 5 results');
+ var firstDirEntry = dirEntryList[0];
+ assert.equal(firstDirEntry.getTitleOrUrl(), 'A Fool for You', 'First result should be "A Fool for You"');
done();
- }
+ };
+ localZimArchive.findDirEntriesWithPrefix({ prefix: 'a', size: 5 }, callbackFunction, true);
});
- });
- QUnit.test("article 'America, the Beautiful' correctly redirects to 'America the Beautiful'", function(assert) {
- var done = assert.async();
- assert.expect(6);
- localZimArchive.getDirEntryByPath("A/America,_the_Beautiful.html").then(function(dirEntry) {
- assert.ok(dirEntry !== null, "DirEntry found");
- if (dirEntry !== null) {
- assert.ok(dirEntry.isRedirect(), "DirEntry is a redirect.");
- assert.equal(dirEntry.getTitleOrUrl(), "America, the Beautiful", "Correct redirect title name.");
- localZimArchive.resolveRedirect(dirEntry, function(dirEntry) {
- assert.ok(dirEntry !== null, "DirEntry found");
- assert.ok(!dirEntry.isRedirect(), "DirEntry is not a redirect.");
- assert.equal(dirEntry.getTitleOrUrl(), "America the Beautiful", "Correct redirected title name.");
- done();
- });
- } else {
+ QUnit.test("check findDirEntriesWithPrefix 'blues brothers'", function (assert) {
+ var done = assert.async();
+ assert.expect(2);
+ var callbackFunction = function (dirEntryList) {
+ assert.ok(dirEntryList && dirEntryList.length === 3, 'Article list with 3 result');
+ var firstDirEntry = dirEntryList[0];
+ assert.equal(firstDirEntry.getTitleOrUrl(), 'Blues Brothers (film)', 'First result should be "Blues Brothers (film)"');
done();
- }
+ };
+ localZimArchive.findDirEntriesWithPrefix({ prefix: 'blues brothers', size: 5 }, callbackFunction, true);
});
- });
- QUnit.test("Image 'm/RayCharles_AManAndHisSoul.jpg' can be loaded", function(assert) {
- var done = assert.async();
- assert.expect(5);
- localZimArchive.getDirEntryByPath("I/m/RayCharles_AManAndHisSoul.jpg").then(function(dirEntry) {
- assert.ok(dirEntry !== null, "DirEntry found");
- if (dirEntry !== null) {
- assert.equal(dirEntry.namespace +"/"+ dirEntry.url, "I/m/RayCharles_AManAndHisSoul.jpg", "URL is correct.");
- assert.equal(dirEntry.getMimetype(), "image/jpeg", "MIME type is correct.");
- localZimArchive.readBinaryFile(dirEntry, function(title, data) {
- assert.equal(data.length, 4951, "Data length is correct.");
- var beginning = new Uint8Array([255, 216, 255, 224, 0, 16, 74, 70,
- 73, 70, 0, 1, 1, 0, 0, 1]);
- assert.equal(data.slice(0, beginning.length).toString(), beginning.toString(), "Data beginning is correct.");
+ QUnit.test("article '(The Night Time Is) The Right Time' correctly redirects to 'Night Time Is the Right Time'", function (assert) {
+ var done = assert.async();
+ assert.expect(6);
+ localZimArchive.getDirEntryByPath('A/(The_Night_Time_Is)_The_Right_Time.html').then(function (dirEntry) {
+ assert.ok(dirEntry !== null, 'DirEntry found');
+ if (dirEntry !== null) {
+ assert.ok(dirEntry.isRedirect(), 'DirEntry is a redirect.');
+ assert.equal(dirEntry.getTitleOrUrl(), '(The Night Time Is) The Right Time', 'Correct redirect title name.');
+ localZimArchive.resolveRedirect(dirEntry, function (dirEntry) {
+ assert.ok(dirEntry !== null, 'DirEntry found');
+ assert.ok(!dirEntry.isRedirect(), 'DirEntry is not a redirect.');
+ assert.equal(dirEntry.getTitleOrUrl(), 'Night Time Is the Right Time', 'Correct redirected title name.');
+ done();
+ });
+ } else {
done();
- });
- } else {
- done();
- }
+ }
+ });
});
- });
- QUnit.test("Stylesheet '-/s/style.css' can be loaded", function(assert) {
- var done = assert.async();
+ QUnit.test("article 'Raelettes' correctly redirects to 'The Raelettes'", function (assert) {
+ var done = assert.async();
+ assert.expect(6);
+ localZimArchive.getDirEntryByPath('A/Raelettes.html').then(function (dirEntry) {
+ assert.ok(dirEntry !== null, 'DirEntry found');
+ if (dirEntry !== null) {
+ assert.ok(dirEntry.isRedirect(), 'DirEntry is a redirect.');
+ assert.equal(dirEntry.getTitleOrUrl(), 'Raelettes', 'Correct redirect title name.');
+ localZimArchive.resolveRedirect(dirEntry, function (dirEntry) {
+ assert.ok(dirEntry !== null, 'DirEntry found');
+ assert.ok(!dirEntry.isRedirect(), 'DirEntry is not a redirect.');
+ assert.equal(dirEntry.getTitleOrUrl(), 'The Raelettes', 'Correct redirected title name.');
+ done();
+ });
+ } else {
+ done();
+ }
+ });
+ });
+ QUnit.test("article 'Bein Green' correctly redirects to 'Bein' Green", function (assert) {
+ var done = assert.async();
+ assert.expect(6);
+ localZimArchive.getDirEntryByPath('A/Bein_Green.html').then(function (dirEntry) {
+ assert.ok(dirEntry !== null, 'DirEntry found');
+ if (dirEntry !== null) {
+ assert.ok(dirEntry.isRedirect(), 'DirEntry is a redirect.');
+ assert.equal(dirEntry.getTitleOrUrl(), 'Bein Green', 'Correct redirect title name.');
+ localZimArchive.resolveRedirect(dirEntry, function (dirEntry) {
+ assert.ok(dirEntry !== null, 'DirEntry found');
+ assert.ok(!dirEntry.isRedirect(), 'DirEntry is not a redirect.');
+ assert.equal(dirEntry.getTitleOrUrl(), "Bein' Green", 'Correct redirected title name.');
+ done();
+ });
+ } else {
+ done();
+ }
+ });
+ });
+ QUnit.test("article 'America, the Beautiful' correctly redirects to 'America the Beautiful'", function (assert) {
+ var done = assert.async();
+ assert.expect(6);
+ localZimArchive.getDirEntryByPath('A/America,_the_Beautiful.html').then(function (dirEntry) {
+ assert.ok(dirEntry !== null, 'DirEntry found');
+ if (dirEntry !== null) {
+ assert.ok(dirEntry.isRedirect(), 'DirEntry is a redirect.');
+ assert.equal(dirEntry.getTitleOrUrl(), 'America, the Beautiful', 'Correct redirect title name.');
+ localZimArchive.resolveRedirect(dirEntry, function (dirEntry) {
+ assert.ok(dirEntry !== null, 'DirEntry found');
+ assert.ok(!dirEntry.isRedirect(), 'DirEntry is not a redirect.');
+ assert.equal(dirEntry.getTitleOrUrl(), 'America the Beautiful', 'Correct redirected title name.');
+ done();
+ });
+ } else {
+ done();
+ }
+ });
+ });
+ QUnit.test("Image 'm/RayCharles_AManAndHisSoul.jpg' can be loaded", function (assert) {
+ var done = assert.async();
+ assert.expect(5);
+ localZimArchive.getDirEntryByPath('I/m/RayCharles_AManAndHisSoul.jpg').then(function (dirEntry) {
+ assert.ok(dirEntry !== null, 'DirEntry found');
+ if (dirEntry !== null) {
+ assert.equal(dirEntry.namespace + '/' + dirEntry.url, 'I/m/RayCharles_AManAndHisSoul.jpg', 'URL is correct.');
+ assert.equal(dirEntry.getMimetype(), 'image/jpeg', 'MIME type is correct.');
+ localZimArchive.readBinaryFile(dirEntry, function (title, data) {
+ assert.equal(data.length, 4951, 'Data length is correct.');
+ var beginning = new Uint8Array([255, 216, 255, 224, 0, 16, 74, 70,
+ 73, 70, 0, 1, 1, 0, 0, 1]);
+ assert.equal(data.slice(0, beginning.length).toString(), beginning.toString(), 'Data beginning is correct.');
+ done();
+ });
+ } else {
+ done();
+ }
+ });
+ });
+ QUnit.test("Stylesheet '-/s/style.css' can be loaded", function (assert) {
+ var done = assert.async();
- assert.expect(5);
- localZimArchive.getDirEntryByPath("-/s/style.css").then(function(dirEntry) {
- assert.ok(dirEntry !== null, "DirEntry found");
- if (dirEntry !== null) {
- assert.equal(dirEntry.namespace +"/"+ dirEntry.url, "-/s/style.css", "URL is correct.");
- assert.equal(dirEntry.getMimetype(), "text/css", "MIME type is correct.");
- localZimArchive.readBinaryFile(dirEntry, function(dirEntry, data) {
- assert.equal(data.length, 104495, "Data length is correct.");
- data = utf8.parse(data);
- var beginning = "\n/* start http://en.wikipedia.org/w/load.php?debug=false&lang=en&modules=site&only=styles&skin=vector";
- assert.equal(data.slice(0, beginning.length), beginning, "Content starts correctly.");
+ assert.expect(5);
+ localZimArchive.getDirEntryByPath('-/s/style.css').then(function (dirEntry) {
+ assert.ok(dirEntry !== null, 'DirEntry found');
+ if (dirEntry !== null) {
+ assert.equal(dirEntry.namespace + '/' + dirEntry.url, '-/s/style.css', 'URL is correct.');
+ assert.equal(dirEntry.getMimetype(), 'text/css', 'MIME type is correct.');
+ localZimArchive.readBinaryFile(dirEntry, function (dirEntry, data) {
+ assert.equal(data.length, 104495, 'Data length is correct.');
+ data = utf8.parse(data);
+ var beginning = '\n/* start http://en.wikipedia.org/w/load.php?debug=false&lang=en&modules=site&only=styles&skin=vector';
+ assert.equal(data.slice(0, beginning.length), beginning, 'Content starts correctly.');
+ done();
+ });
+ } else {
done();
- });
- } else {
- done();
- }
+ }
+ });
});
- });
- QUnit.test("Javascript '-/j/local.js' can be loaded", function(assert) {
- var done = assert.async();
- assert.expect(5);
- localZimArchive.getDirEntryByPath("-/j/local.js").then(function(dirEntry) {
- assert.ok(dirEntry !== null, "DirEntry found");
- if (dirEntry !== null) {
- assert.equal(dirEntry.namespace +"/"+ dirEntry.url, "-/j/local.js", "URL is correct.");
- assert.equal(dirEntry.getMimetype(), "application/javascript", "MIME type is correct.");
- localZimArchive.readBinaryFile(dirEntry, function(dirEntry, data) {
- assert.equal(data.length, 41, "Data length is correct.");
- data = utf8.parse(data);
- var beginning = "console.log( \"mw.loader";
- assert.equal(data.slice(0, beginning.length), beginning, "Content starts correctly.");
+ QUnit.test("Javascript '-/j/local.js' can be loaded", function (assert) {
+ var done = assert.async();
+ assert.expect(5);
+ localZimArchive.getDirEntryByPath('-/j/local.js').then(function (dirEntry) {
+ assert.ok(dirEntry !== null, 'DirEntry found');
+ if (dirEntry !== null) {
+ assert.equal(dirEntry.namespace + '/' + dirEntry.url, '-/j/local.js', 'URL is correct.');
+ assert.equal(dirEntry.getMimetype(), 'application/javascript', 'MIME type is correct.');
+ localZimArchive.readBinaryFile(dirEntry, function (dirEntry, data) {
+ assert.equal(data.length, 41, 'Data length is correct.');
+ data = utf8.parse(data);
+ var beginning = 'console.log( "mw.loader';
+ assert.equal(data.slice(0, beginning.length), beginning, 'Content starts correctly.');
+ done();
+ });
+ } else {
done();
- });
- }
- else {
- done();
- }
+ }
+ });
});
- });
- QUnit.test("Split article 'A/Ray_Charles.html' can be loaded", function(assert) {
- var done = assert.async();
- assert.expect(7);
- localZimArchive.getDirEntryByPath("A/Ray_Charles.html").then(function(dirEntry) {
- assert.ok(dirEntry !== null, "Title found");
- if (dirEntry !== null) {
- assert.equal(dirEntry.namespace +"/"+ dirEntry.url, "A/Ray_Charles.html", "URL is correct.");
- assert.equal(dirEntry.getMimetype(), "text/html", "MIME type is correct.");
- localZimArchive.readUtf8File(dirEntry, function(dirEntry2, data) {
- assert.equal(dirEntry2.getTitleOrUrl(), "Ray Charles", "Title is correct.");
- assert.equal(data.length, 157186, "Data length is correct.");
- assert.equal(data.indexOf("the only true genius in show business"), 5535, "Specific substring at beginning found.");
- assert.equal(data.indexOf("Random Access Memories"), 154107, "Specific substring at end found.");
+ QUnit.test("Split article 'A/Ray_Charles.html' can be loaded", function (assert) {
+ var done = assert.async();
+ assert.expect(7);
+ localZimArchive.getDirEntryByPath('A/Ray_Charles.html').then(function (dirEntry) {
+ assert.ok(dirEntry !== null, 'Title found');
+ if (dirEntry !== null) {
+ assert.equal(dirEntry.namespace + '/' + dirEntry.url, 'A/Ray_Charles.html', 'URL is correct.');
+ assert.equal(dirEntry.getMimetype(), 'text/html', 'MIME type is correct.');
+ localZimArchive.readUtf8File(dirEntry, function (dirEntry2, data) {
+ assert.equal(dirEntry2.getTitleOrUrl(), 'Ray Charles', 'Title is correct.');
+ assert.equal(data.length, 157186, 'Data length is correct.');
+ assert.equal(data.indexOf('the only true genius in show business'), 5535, 'Specific substring at beginning found.');
+ assert.equal(data.indexOf('Random Access Memories'), 154107, 'Specific substring at end found.');
+ done();
+ });
+ } else {
done();
- });
- } else {
- done();
- }
+ }
+ });
});
- });
- QUnit.module("zim_random_and_main_article");
- QUnit.test("check that a random article is found", function(assert) {
- var done = assert.async();
- assert.expect(2);
- var callbackRandomArticleFound = function(dirEntry) {
- assert.ok(dirEntry !== null, "One DirEntry should be found");
- assert.ok(dirEntry.getTitleOrUrl() !== null, "The random DirEntry should have a title" );
+ QUnit.module('zim_random_and_main_article');
+ QUnit.test('check that a random article is found', function (assert) {
+ var done = assert.async();
+ assert.expect(2);
+ var callbackRandomArticleFound = function (dirEntry) {
+ assert.ok(dirEntry !== null, 'One DirEntry should be found');
+ assert.ok(dirEntry.getTitleOrUrl() !== null, 'The random DirEntry should have a title');
- done();
- };
- localZimArchive.getRandomDirEntry(callbackRandomArticleFound);
- });
- QUnit.test("check that the main article is found", function(assert) {
- var done = assert.async();
- assert.expect(2);
- var callbackMainPageArticleFound = function(dirEntry) {
- assert.ok(dirEntry !== null, "Main DirEntry should be found");
- assert.equal(dirEntry.getTitleOrUrl(), "Summary", "The main DirEntry should be called Summary" );
+ done();
+ };
+ localZimArchive.getRandomDirEntry(callbackRandomArticleFound);
+ });
+ QUnit.test('check that the main article is found', function (assert) {
+ var done = assert.async();
+ assert.expect(2);
+ var callbackMainPageArticleFound = function (dirEntry) {
+ assert.ok(dirEntry !== null, 'Main DirEntry should be found');
+ assert.equal(dirEntry.getTitleOrUrl(), 'Summary', 'The main DirEntry should be called Summary');
- done();
- };
- localZimArchive.getMainPageDirEntry(callbackMainPageArticleFound);
- });
+ done();
+ };
+ localZimArchive.getMainPageDirEntry(callbackMainPageArticleFound);
+ });
- QUnit.start();
- };
-});
+ QUnit.start();
+ };
+ });
diff --git a/www/js/app.js b/www/js/app.js
index 84020f1e8..8a4650813 100644
--- a/www/js/app.js
+++ b/www/js/app.js
@@ -1,4 +1,3 @@
-/* eslint-disable indent */
/**
* app.js : User Interface implementation
* This file handles the interaction between the application and the user
@@ -26,6 +25,7 @@
// The global parameters object is defined in init.js
/* global params, define, webpMachine */
+/* eslint-disable indent */
// This uses require.js to structure javascript:
// http://requirejs.org/docs/api.html#define
diff --git a/www/js/lib/abstractFilesystemAccess.js b/www/js/lib/abstractFilesystemAccess.js
index b48eb010e..f0396209b 100644
--- a/www/js/lib/abstractFilesystemAccess.js
+++ b/www/js/lib/abstractFilesystemAccess.js
@@ -5,41 +5,44 @@
* filesystem.
* It is unfortunately not possible to do that inside a standard browser
* (even inside an extension).
- *
+ *
* Copyright 2014 Kiwix developers
* License GPL v3:
- *
+ *
* This file is part of Kiwix.
- *
+ *
* Kiwix is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
- *
+ *
* Kiwix is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+ *
* You should have received a copy of the GNU General Public License
* along with Kiwix (file LICENSE-GPLv3.txt). If not, see
*/
+
'use strict';
-define([], function() {
-
+
+/* global define */
+
+define([], function () {
/**
* Storage implemented by Firefox OS
- *
+ *
* @typedef StorageFirefoxOS
* @property {DeviceStorage} _storage DeviceStorage
* @property {String} storageName Name of the storage
*/
-
+
/**
* Creates an abstraction layer around the FirefoxOS storage.
* @param storage FirefoxOS DeviceStorage object
*/
- function StorageFirefoxOS(storage) {
+ function StorageFirefoxOS (storage) {
this._storage = storage;
this.storageName = storage.storageName;
};
@@ -49,27 +52,27 @@ define([], function() {
* @return {Promise} Promise which is resolved with a HTML5 file object and
* rejected with an error message.
*/
- StorageFirefoxOS.prototype.get = function(path) {
+ StorageFirefoxOS.prototype.get = function (path) {
var that = this;
- return new Promise(function (resolve, reject){
+ return new Promise(function (resolve, reject) {
var request = that._storage.get(path);
- request.onsuccess = function() { resolve(this.result); };
- request.onerror = function() { reject(this.error.name); };
+ request.onsuccess = function () { resolve(this.result); };
+ request.onerror = function () { reject(this.error.name); };
});
};
-
+
// We try to match both a standalone ZIM file (.zim) or
// the first file of a split ZIM files collection (.zimaa)
var regexpZIMFileName = /\.zim(aa)?$/i;
-
+
/**
* Searches for archive files or directories.
* @return {Promise} Promise which is resolved with an array of
* paths and rejected with an error message.
*/
- StorageFirefoxOS.prototype.scanForArchives = function() {
+ StorageFirefoxOS.prototype.scanForArchives = function () {
var that = this;
- return new Promise(function (resolve, reject){
+ return new Promise(function (resolve, reject) {
var directories = [];
var cursor = that._storage.enumerate();
cursor.onerror = function () {
@@ -90,17 +93,16 @@ define([], function() {
};
});
};
-
+
/**
* Browse a path through DeviceStorage API
* @param path Path where to look for files
* @return {DOMCursor} Cursor of files found in given path
*/
- StorageFirefoxOS.prototype.enumerate = function(path) {
+ StorageFirefoxOS.prototype.enumerate = function (path) {
return this._storage.enumerate();
};
-
return {
StorageFirefoxOS: StorageFirefoxOS
};
diff --git a/www/js/lib/filecache.js b/www/js/lib/filecache.js
index 040d3bf27..247506282 100644
--- a/www/js/lib/filecache.js
+++ b/www/js/lib/filecache.js
@@ -21,8 +21,11 @@
* You should have received a copy of the GNU General Public License
* along with Kiwix JS (file LICENSE). If not, see
*/
+
'use strict';
+/* global define */
+
define([], function () {
/**
* Set maximum number of cache blocks of BLOCK_SIZE bytes each
@@ -42,7 +45,7 @@ define([], function () {
/**
* A Block Cache employing a Least Recently Used caching strategy
* @typedef {Object} BlockCache
- * @property {Number} capacity The maximum number of entries in the cache
+ * @property {Number} capacity The maximum number of entries in the cache
* @property {Map} cache A map to store the cache keys and data
*/
@@ -50,7 +53,7 @@ define([], function () {
* Creates a new cache with max size limit of MAX_CACHE_SIZE blocks
* LRUCache implemnentation with Map adapted from https://markmurray.co/blog/lru-cache/
*/
- function LRUCache() {
+ function LRUCache () {
/** CACHE TUNING **/
// console.log('Creating cache of size ' + MAX_CACHE_SIZE + ' * ' + BLOCK_SIZE + ' bytes');
// Initialize persistent Cache properties
@@ -62,7 +65,7 @@ define([], function () {
* Tries to retrieve an element by its id. If it is not present in the cache, returns undefined; if it is present,
* then the value is returned and the entry is moved to the bottom of the cache
* @param {String} key The block cache entry key (file.id + ':' + byte offset)
- * @returns {Uint8Array | undefined} The requested cache data or undefined
+ * @returns {Uint8Array | undefined} The requested cache data or undefined
*/
LRUCache.prototype.get = function (key) {
var entry = this.cache.get(key);
@@ -78,7 +81,7 @@ define([], function () {
/**
* Stores a value in the cache by id and prunes the least recently used entry if the cache is larger than MAX_CACHE_SIZE
* @param {String} key The key under which to store the value (file.id + ':' + byte offset from start of ZIM archive)
- * @param {Uint8Array} value The value to store in the cache
+ * @param {Uint8Array} value The value to store in the cache
*/
LRUCache.prototype.store = function (key, value) {
// We get the existing entry's object for memory-management purposes; if it exists, it will contain identical data
@@ -90,7 +93,7 @@ define([], function () {
if (entry) this.cache.delete(key);
else entry = value;
this.cache.set(key, entry);
- // If we've exceeded the cache capacity, then delete the least recently accessed value,
+ // If we've exceeded the cache capacity, then delete the least recently accessed value,
// which will be the item at the top of the Map, i.e the first position
if (this.cache.size > this.capacity) {
if (this.cache.keys) {
@@ -117,7 +120,7 @@ define([], function () {
*/
var cache = new LRUCache();
- /** CACHE TUNING **/
+ /** CACHE TUNING **/
// DEV: Uncomment this block and blocks below marked 'CACHE TUNING' to measure Cache hit and miss rates for different Cache sizes
// var hits = 0;
// var misses = 0;
@@ -128,7 +131,7 @@ define([], function () {
* @param {Object} file The requested ZIM archive to read from
* @param {Number} begin The byte from which to start reading
* @param {Number} end The byte at which to stop reading (end will not be read)
- * @return {Promise} A Promise that resolves to the correctly concatenated data from the cache
+ * @return {Promise} A Promise that resolves to the correctly concatenated data from the cache
* or from the ZIM archive
*/
var read = function (file, begin, end) {
@@ -144,7 +147,7 @@ define([], function () {
// Data not in cache, so read from archive
/** CACHE TUNING **/
// misses++;
- // DEV: This is a self-calling function, i.e. the function is called with an argument of which then
+ // DEV: This is a self-calling function, i.e. the function is called with an argument of which then
// becomes the parameter
readRequests.push(function (offset) {
return file._readSplitSlice(offset, offset + BLOCK_SIZE).then(function (result) {
@@ -160,7 +163,7 @@ define([], function () {
}
/** CACHE TUNING **/
// if (misses + hits > 2000) {
- // console.log('** Block cache hit rate: ' + Math.round(hits / (hits + misses) * 1000) / 10 + '% [ hits:' + hits +
+ // console.log('** Block cache hit rate: ' + Math.round(hits / (hits + misses) * 1000) / 10 + '% [ hits:' + hits +
// ' / misses:' + misses + ' ] Size: ' + cache.cache.size);
// hits = 0;
// misses = 0;
@@ -183,4 +186,4 @@ define([], function () {
return {
read: read
};
-});
\ No newline at end of file
+});
diff --git a/www/js/lib/settingsStore.js b/www/js/lib/settingsStore.js
index c7391c741..5d82d1551 100644
--- a/www/js/lib/settingsStore.js
+++ b/www/js/lib/settingsStore.js
@@ -1,304 +1,309 @@
'use strict';
+
+/* global define, params */
+
define([], function () {
- /**
- * settingsStore.js
- *
- * A reader/writer framework for cookies or localStorage with full unicode support based on the Mozilla cookies framework.
- * The Mozilla code has been adapted to test for the availability of the localStorage API, and to use it in preference to cookies.
- *
- * Mozilla version information:
- *
- * Revision #1 - September 4, 2014
- *
- * https://developer.mozilla.org/en-US/docs/Web/API/document.cookie
- * https://developer.mozilla.org/User:fusionchess
- *
- * This framework is released under the GNU Public License, version 3 or later.
- * http://www.gnu.org/licenses/gpl-3.0-standalone.html
- *
- * Syntaxes:
- *
- * * settingsStore.setItem(name, value[, end[, path[, domain[, secure]]]])
- * * settingsStore.getItem(name)
- * * settingsStore.removeItem(name[, path[, domain]])
- * * settingsStore.hasItem(name)
- *
- */
+ /**
+ * settingsStore.js
+ *
+ * A reader/writer framework for cookies or localStorage with full unicode support based on the Mozilla cookies framework.
+ * The Mozilla code has been adapted to test for the availability of the localStorage API, and to use it in preference to cookies.
+ *
+ * Mozilla version information:
+ *
+ * Revision #1 - September 4, 2014
+ *
+ * https://developer.mozilla.org/en-US/docs/Web/API/document.cookie
+ * https://developer.mozilla.org/User:fusionchess
+ *
+ * This framework is released under the GNU Public License, version 3 or later.
+ * http://www.gnu.org/licenses/gpl-3.0-standalone.html
+ *
+ * Syntaxes:
+ *
+ * * settingsStore.setItem(name, value[, end[, path[, domain[, secure]]]])
+ * * settingsStore.getItem(name)
+ * * settingsStore.removeItem(name[, path[, domain]])
+ * * settingsStore.hasItem(name)
+ *
+ */
- /**
- * A RegExp of the settings keys used in the cookie that should be migrated to localStorage if the API is available
- * DEV: It should not be necessary to keep this list up-to-date because any keys added after this list was created
- * (April 2020) will already be stored in localStorage if it is available to the client's browser or platform and
- * will not need to be migrated
- * @type {RegExp}
- */
+ /**
+ * A RegExp of the settings keys used in the cookie that should be migrated to localStorage if the API is available
+ * DEV: It should not be necessary to keep this list up-to-date because any keys added after this list was created
+ * (April 2020) will already be stored in localStorage if it is available to the client's browser or platform and
+ * will not need to be migrated
+ * @type {RegExp}
+ */
- var regexpCookieKeysToMigrate = new RegExp([
- 'hideActiveContentWarning', 'showUIAnimations', 'appTheme', 'useCache',
- 'contentInjectionMode', 'listOfArchives', 'lastSelectedArchive'
- ].join('|'));
+ var regexpCookieKeysToMigrate = new RegExp([
+ 'hideActiveContentWarning', 'showUIAnimations', 'appTheme', 'useCache',
+ 'contentInjectionMode', 'listOfArchives', 'lastSelectedArchive'
+ ].join('|'));
- /**
- * A list of deprecated keys that should be removed. Add any further keys to the list of strings separated by a comma.
- * @type {Array}
- */
- var deprecatedKeys = [
- 'lastContentInjectionMode',
- 'useCache'
- ];
+ /**
+ * A list of deprecated keys that should be removed. Add any further keys to the list of strings separated by a comma.
+ * @type {Array}
+ */
+ var deprecatedKeys = [
+ 'lastContentInjectionMode',
+ 'useCache'
+ ];
- /**
- * The prefix that will be added to keys when stored in localStorage: this is used to prevent
- * potential collision of key names with localStorage keys used by code inside ZIM archives
- * It is set in init.js because it is needed early in app loading
- * @type {String}
- */
- var keyPrefix = params.keyPrefix;
+ /**
+ * The prefix that will be added to keys when stored in localStorage: this is used to prevent
+ * potential collision of key names with localStorage keys used by code inside ZIM archives
+ * It is set in init.js because it is needed early in app loading
+ * @type {String}
+ */
+ var keyPrefix = params.keyPrefix;
- // Tests for available Storage APIs (document.cookie or localStorage) and returns the best available of these
- function getBestAvailableStorageAPI() {
- // DEV: In FF extensions, cookies are blocked since at least FF 68.6 but possibly since FF 55 [kiwix-js #612]
- var type = 'none';
- // First test for localStorage API support
- var localStorageTest;
- try {
- localStorageTest = 'localStorage' in window && window['localStorage'] !== null;
- // DEV: Above test returns true in IE11 running from file:// protocol, but attempting to write a key to
- // localStorage causes an exception; so to test fully, we must now attempt to write and remove a test key
- if (localStorageTest) {
- localStorage.setItem('tempKiwixStorageTest', '');
- localStorage.removeItem('tempKiwixStorageTest');
- }
- } catch (e) {
- localStorageTest = false;
+ // Tests for available Storage APIs (document.cookie or localStorage) and returns the best available of these
+ function getBestAvailableStorageAPI () {
+ // DEV: In FF extensions, cookies are blocked since at least FF 68.6 but possibly since FF 55 [kiwix-js #612]
+ var type = 'none';
+ // First test for localStorage API support
+ var localStorageTest;
+ try {
+ localStorageTest = 'localStorage' in window && window['localStorage'] !== null;
+ // DEV: Above test returns true in IE11 running from file:// protocol, but attempting to write a key to
+ // localStorage causes an exception; so to test fully, we must now attempt to write and remove a test key
+ if (localStorageTest) {
+ localStorage.setItem('tempKiwixStorageTest', '');
+ localStorage.removeItem('tempKiwixStorageTest');
+ }
+ } catch (e) {
+ localStorageTest = false;
+ }
+ // Now test for document.cookie API support
+ document.cookie = 'tempKiwixCookieTest=working; expires=Fri, 31 Dec 9999 23:59:59 GMT; SameSite=Strict';
+ var kiwixCookieTest = /tempKiwixCookieTest=working/.test(document.cookie);
+ // Remove test value by expiring the key
+ document.cookie = 'tempKiwixCookieTest=; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Strict';
+ if (kiwixCookieTest) type = 'cookie';
+ // Prefer localStorage if supported due to some platforms removing cookies once the session ends in some contexts
+ if (localStorageTest) type = 'local_storage';
+ // If both cookies and localStorage are supported, and document.cookie contains keys to migrate,
+ // migrate settings to use localStorage
+ if (kiwixCookieTest && localStorageTest && regexpCookieKeysToMigrate.test(document.cookie)) _migrateStorageSettings();
+ // Remove any deprecated keys
+ deprecatedKeys.forEach(function (key) {
+ if (localStorageTest) localStorage.removeItem(keyPrefix + key);
+ settingsStore.removeItem(key); // Because this runs before we have returned a store type, this will remove from cookie too
+ });
+ // Note that if this function returns 'none', the cookie implementations below will run anyway. This is because storing a cookie
+ // does not cause an exception even if cookies are blocked in some contexts, whereas accessing localStorage may cause an exception
+ return type;
}
- // Now test for document.cookie API support
- document.cookie = 'tempKiwixCookieTest=working; expires=Fri, 31 Dec 9999 23:59:59 GMT; SameSite=Strict';
- var kiwixCookieTest = /tempKiwixCookieTest=working/.test(document.cookie);
- // Remove test value by expiring the key
- document.cookie = 'tempKiwixCookieTest=; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Strict';
- if (kiwixCookieTest) type = 'cookie';
- // Prefer localStorage if supported due to some platforms removing cookies once the session ends in some contexts
- if (localStorageTest) type = 'local_storage';
- // If both cookies and localStorage are supported, and document.cookie contains keys to migrate,
- // migrate settings to use localStorage
- if (kiwixCookieTest && localStorageTest && regexpCookieKeysToMigrate.test(document.cookie)) _migrateStorageSettings();
- // Remove any deprecated keys
- deprecatedKeys.forEach(function (key) {
- if (localStorageTest) localStorage.removeItem(keyPrefix + key);
- settingsStore.removeItem(key); // Because this runs before we have returned a store type, this will remove from cookie too
- });
- // Note that if this function returns 'none', the cookie implementations below will run anyway. This is because storing a cookie
- // does not cause an exception even if cookies are blocked in some contexts, whereas accessing localStorage may cause an exception
- return type;
- }
- /**
- * Performs a full app reset, deleting all caches and settings
- * Or, if a parameter is supplied, deletes or disables the object
- * @param {String} object Optional name of the object to disable or delete ('cookie', 'localStorage', 'cacheAPI')
- */
- function reset(object) {
- // 1. Clear any cookie entries
- if (!object || object === 'cookie') {
- var regexpCookieKeys = /(?:^|;)\s*([^=]+)=([^;]*)/ig;
- var currentCookie = document.cookie;
- var foundCrumb = false;
- var cookieCrumb = regexpCookieKeys.exec(currentCookie);
- while (cookieCrumb !== null) {
- // DEV: Note that we don't use the keyPrefix in legacy cookie support
- foundCrumb = true;
- // This expiry date will cause the browser to delete the cookie crumb on next page refresh
- document.cookie = cookieCrumb[1] + '=;expires=Thu, 21 Sep 1979 00:00:01 UTC;';
- cookieCrumb = regexpCookieKeys.exec(currentCookie);
- }
- if (foundCrumb) console.debug('All cookie keys were expired...');
- }
+ /**
+ * Performs a full app reset, deleting all caches and settings
+ * Or, if a parameter is supplied, deletes or disables the object
+ * @param {String} object Optional name of the object to disable or delete ('cookie', 'localStorage', 'cacheAPI')
+ */
+ function reset (object) {
+ // 1. Clear any cookie entries
+ if (!object || object === 'cookie') {
+ var regexpCookieKeys = /(?:^|;)\s*([^=]+)=([^;]*)/ig;
+ var currentCookie = document.cookie;
+ var foundCrumb = false;
+ var cookieCrumb = regexpCookieKeys.exec(currentCookie);
+ while (cookieCrumb !== null) {
+ // DEV: Note that we don't use the keyPrefix in legacy cookie support
+ foundCrumb = true;
+ // This expiry date will cause the browser to delete the cookie crumb on next page refresh
+ document.cookie = cookieCrumb[1] + '=;expires=Thu, 21 Sep 1979 00:00:01 UTC;';
+ cookieCrumb = regexpCookieKeys.exec(currentCookie);
+ }
+ if (foundCrumb) console.debug('All cookie keys were expired...');
+ }
- // 2. Clear any localStorage settings
- if (!object || object === 'localStorage') {
- if (params.storeType === 'local_storage') {
- localStorage.clear();
- console.debug('All Local Storage settings were deleted...');
- }
- }
+ // 2. Clear any localStorage settings
+ if (!object || object === 'localStorage') {
+ if (params.storeType === 'local_storage') {
+ localStorage.clear();
+ console.debug('All Local Storage settings were deleted...');
+ }
+ }
- // 3. Clear any Cache API caches
- if (!object || object === 'cacheAPI') {
- getCacheNames(function (cacheNames) {
- if (cacheNames && !cacheNames.error) {
- var cnt = 0;
- for (var cacheName in cacheNames) {
- cnt++;
- caches.delete(cacheNames[cacheName]).then(function () {
- cnt--;
- if (!cnt) {
- // All caches deleted
- console.debug('All Cache API caches were deleted...');
- // Reload if user performed full reset or if appCache is needed
- if (!object || params.appCache) _reloadApp();
- }
+ // 3. Clear any Cache API caches
+ if (!object || object === 'cacheAPI') {
+ getCacheNames(function (cacheNames) {
+ if (cacheNames && !cacheNames.error) {
+ var cnt = 0;
+ for (var cacheName in cacheNames) {
+ cnt++;
+ caches.delete(cacheNames[cacheName]).then(function () {
+ cnt--;
+ if (!cnt) {
+ // All caches deleted
+ console.debug('All Cache API caches were deleted...');
+ // Reload if user performed full reset or if appCache is needed
+ if (!object || params.appCache) _reloadApp();
+ }
+ });
+ }
+ } else {
+ console.debug('No Cache API caches were in use (or we do not have access to the names).');
+ // All operations complete, reload if user performed full reset or if appCache is needed
+ if (!object || params.appCache) _reloadApp();
+ }
});
- }
- } else {
- console.debug('No Cache API caches were in use (or we do not have access to the names).');
- // All operations complete, reload if user performed full reset or if appCache is needed
- if (!object || params.appCache) _reloadApp();
}
- });
- }
- }
-
- // Gets cache names from Service Worker, as we cannot rely on having them in params.cacheNames
- function getCacheNames(callback) {
- if (navigator.serviceWorker && navigator.serviceWorker.controller) {
- var channel = new MessageChannel();
- channel.port1.onmessage = function (event) {
- var names = event.data;
- callback(names);
- };
- navigator.serviceWorker.controller.postMessage({
- action: 'getCacheNames'
- }, [channel.port2]);
- } else {
- callback(null);
}
- }
- // Deregisters all Service Workers and reboots the app
- function _reloadApp() {
- var reboot = function () {
- console.debug('Performing app reload...');
- setTimeout(function () {
- window.location.href = location.origin + location.pathname + uriParams
- }, 300);
- };
- // Blank the querystring, so that parameters are not set on reload
- var uriParams = '';
- if (~window.location.href.indexOf(params.PWAServer) && params.referrerExtensionURL) {
- // However, if we're in a PWA that was called from local code, then by definition we must remain in SW mode and we need to
- // ensure the user still has access to the referrerExtensionURL (so they can get back to local code from the UI)
- uriParams = '?allowInternetAccess=truee&contentInjectionMode=serviceworker';
- uriParams += '&referrerExtensionURL=' + encodeURIComponent(params.referrerExtensionURL);
- }
- if (navigator && navigator.serviceWorker) {
- console.debug('Deregistering Service Workers...');
- var cnt = 0;
- navigator.serviceWorker.getRegistrations().then(function (registrations) {
- if (!registrations.length) {
- reboot();
- return;
+ // Gets cache names from Service Worker, as we cannot rely on having them in params.cacheNames
+ function getCacheNames (callback) {
+ if (navigator.serviceWorker && navigator.serviceWorker.controller) {
+ var channel = new MessageChannel();
+ channel.port1.onmessage = function (event) {
+ var names = event.data;
+ callback(names);
+ };
+ navigator.serviceWorker.controller.postMessage({
+ action: 'getCacheNames'
+ }, [channel.port2]);
+ } else {
+ callback(null);
}
- cnt++;
- registrations.forEach(function (registration) {
- registration.unregister().then(function () {
- cnt--;
- if (!cnt) {
- console.debug('All Service Workers unregistered...');
- reboot();
- }
- });
- });
- }).catch(function (err) {
- console.error(err);
- reboot();
- });
- } else {
- console.debug('Performing app reload...');
- reboot();
}
- }
- var settingsStore = {
- getItem: function (sKey) {
- if (!sKey) {
- return null;
- }
- if (params.storeType !== 'local_storage') {
- return decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(sKey).replace(/[-.+*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null;
- } else {
- return localStorage.getItem(keyPrefix + sKey);
- }
- },
- setItem: function (sKey, sValue, vEnd, sPath, sDomain, bSecure) {
- if (params.storeType !== 'local_storage') {
- if (!sKey || /^(?:expires|max-age|path|domain|secure)$/i.test(sKey)) {
- return false;
+ // Deregisters all Service Workers and reboots the app
+ function _reloadApp () {
+ var reboot = function () {
+ console.debug('Performing app reload...');
+ setTimeout(function () {
+ window.location.href = location.origin + location.pathname + uriParams
+ }, 300);
+ };
+ // Blank the querystring, so that parameters are not set on reload
+ var uriParams = '';
+ if (~window.location.href.indexOf(params.PWAServer) && params.referrerExtensionURL) {
+ // However, if we're in a PWA that was called from local code, then by definition we must remain in SW mode and we need to
+ // ensure the user still has access to the referrerExtensionURL (so they can get back to local code from the UI)
+ uriParams = '?allowInternetAccess=truee&contentInjectionMode=serviceworker';
+ uriParams += '&referrerExtensionURL=' + encodeURIComponent(params.referrerExtensionURL);
}
- var sExpires = "";
- if (vEnd) {
- switch (vEnd.constructor) {
- case Number:
- sExpires = vEnd === Infinity ? "; expires=Fri, 31 Dec 9999 23:59:59 GMT" : "; max-age=" + vEnd;
- break;
- case String:
- sExpires = "; expires=" + vEnd;
- break;
- case Date:
- sExpires = "; expires=" + vEnd.toUTCString();
- break;
- }
+ if (navigator && navigator.serviceWorker) {
+ console.debug('Deregistering Service Workers...');
+ var cnt = 0;
+ navigator.serviceWorker.getRegistrations().then(function (registrations) {
+ if (!registrations.length) {
+ reboot();
+ return;
+ }
+ cnt++;
+ registrations.forEach(function (registration) {
+ registration.unregister().then(function () {
+ cnt--;
+ if (!cnt) {
+ console.debug('All Service Workers unregistered...');
+ reboot();
+ }
+ });
+ });
+ }).catch(function (err) {
+ console.error(err);
+ reboot();
+ });
+ } else {
+ console.debug('Performing app reload...');
+ reboot();
}
- document.cookie = encodeURIComponent(sKey) + "=" + encodeURIComponent(sValue) + sExpires + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "") + (bSecure ? "; secure" : "");
- } else {
- localStorage.setItem(keyPrefix + sKey, sValue);
- }
- return true;
- },
- removeItem: function (sKey, sPath, sDomain) {
- if (!this.hasItem(sKey)) {
- return false;
- }
- if (params.storeType !== 'local_storage') {
- document.cookie = encodeURIComponent(sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT" + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "");
- } else {
- localStorage.removeItem(keyPrefix + sKey);
- }
- return true;
- },
- hasItem: function (sKey) {
- if (!sKey) {
- return false;
- }
- if (params.storeType !== 'local_storage') {
- return (new RegExp("(?:^|;\\s*)" + encodeURIComponent(sKey).replace(/[-.+*]/g, "\\$&") + "\\s*\\=")).test(document.cookie);
- } else {
- return localStorage.getItem(keyPrefix + sKey) === null ? false : true;
- }
- },
- _cookieKeys: function () {
- var aKeys = document.cookie.replace(/((?:^|\s*;)[^=]+)(?=;|$)|^\s*|\s*(?:=[^;]*)?(?:\1|$)/g, "").split(/\s*(?:=[^;]*)?;\s*/);
- for (var nLen = aKeys.length, nIdx = 0; nIdx < nLen; nIdx++) {
- aKeys[nIdx] = decodeURIComponent(aKeys[nIdx]);
- }
- return aKeys;
}
- };
- // One-off migration of storage settings from cookies to localStorage
- function _migrateStorageSettings() {
- console.log('Migrating Settings Store from cookies to localStorage...');
- var cookieKeys = settingsStore._cookieKeys();
- // Note that because migration occurs before setting params.storeType, settingsStore.getItem() will get the item from
- // document.cookie instead of localStorage, which is the intended behaviour
- for (var i = 0; i < cookieKeys.length; i++) {
- if (regexpCookieKeysToMigrate.test(cookieKeys[i])) {
- var migratedKey = keyPrefix + cookieKeys[i];
- localStorage.setItem(migratedKey, settingsStore.getItem(cookieKeys[i]));
- settingsStore.removeItem(cookieKeys[i]);
- console.log('- ' + migratedKey);
- }
+ var settingsStore = {
+ getItem: function (sKey) {
+ if (!sKey) {
+ return null;
+ }
+ if (params.storeType !== 'local_storage') {
+ return decodeURIComponent(document.cookie.replace(new RegExp('(?:(?:^|.*;)\\s*' + encodeURIComponent(sKey).replace(/[-.+*\\]/g, '\\$&') + '\\s*\\=\\s*([^;]*).*$)|^.*$'), '$1')) || null;
+ } else {
+ return localStorage.getItem(keyPrefix + sKey);
+ }
+ },
+ setItem: function (sKey, sValue, vEnd, sPath, sDomain, bSecure) {
+ if (params.storeType !== 'local_storage') {
+ if (!sKey || /^(?:expires|max-age|path|domain|secure)$/i.test(sKey)) {
+ return false;
+ }
+ var sExpires = '';
+ if (vEnd) {
+ switch (vEnd.constructor) {
+ case Number:
+ sExpires = vEnd === Infinity ? '; expires=Fri, 31 Dec 9999 23:59:59 GMT' : '; max-age=' + vEnd;
+ break;
+ case String:
+ sExpires = '; expires=' + vEnd;
+ break;
+ case Date:
+ sExpires = '; expires=' + vEnd.toUTCString();
+ break;
+ }
+ }
+ document.cookie = encodeURIComponent(sKey) + '=' + encodeURIComponent(sValue) + sExpires + (sDomain ? '; domain=' + sDomain : '') + (sPath ? '; path=' + sPath : '') + (bSecure ? '; secure' : '');
+ } else {
+ localStorage.setItem(keyPrefix + sKey, sValue);
+ }
+ return true;
+ },
+ removeItem: function (sKey, sPath, sDomain) {
+ if (!this.hasItem(sKey)) {
+ return false;
+ }
+ if (params.storeType !== 'local_storage') {
+ document.cookie = encodeURIComponent(sKey) + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT' + (sDomain ? '; domain=' + sDomain : '') + (sPath ? '; path=' + sPath : '');
+ } else {
+ localStorage.removeItem(keyPrefix + sKey);
+ }
+ return true;
+ },
+ hasItem: function (sKey) {
+ if (!sKey) {
+ return false;
+ }
+ if (params.storeType !== 'local_storage') {
+ return (new RegExp('(?:^|;\\s*)' + encodeURIComponent(sKey).replace(/[-.+*\\]/g, '\\$&') + '\\s*\\=')).test(document.cookie);
+ } else {
+ return localStorage.getItem(keyPrefix + sKey) !== null;
+ }
+ },
+ _cookieKeys: function () {
+ // Disabling linter check because this is library code
+ // eslint-disable-next-line no-useless-backreference
+ var aKeys = document.cookie.replace(/((?:^|\s*;)[^=]+)(?=;|$)|^\s*|\s*(?:=[^;]*)?(?:\1|$)/g, '').split(/\s*(?:=[^;]*)?;\s*/);
+ for (var nLen = aKeys.length, nIdx = 0; nIdx < nLen; nIdx++) {
+ aKeys[nIdx] = decodeURIComponent(aKeys[nIdx]);
+ }
+ return aKeys;
+ }
+ };
+
+ // One-off migration of storage settings from cookies to localStorage
+ function _migrateStorageSettings () {
+ console.log('Migrating Settings Store from cookies to localStorage...');
+ var cookieKeys = settingsStore._cookieKeys();
+ // Note that because migration occurs before setting params.storeType, settingsStore.getItem() will get the item from
+ // document.cookie instead of localStorage, which is the intended behaviour
+ for (var i = 0; i < cookieKeys.length; i++) {
+ if (regexpCookieKeysToMigrate.test(cookieKeys[i])) {
+ var migratedKey = keyPrefix + cookieKeys[i];
+ localStorage.setItem(migratedKey, settingsStore.getItem(cookieKeys[i]));
+ settingsStore.removeItem(cookieKeys[i]);
+ console.log('- ' + migratedKey);
+ }
+ }
+ console.log('Migration done.');
}
- console.log('Migration done.');
- }
- return {
- getItem: settingsStore.getItem,
- setItem: settingsStore.setItem,
- removeItem: settingsStore.removeItem,
- hasItem: settingsStore.hasItem,
- getCacheNames: getCacheNames,
- reset: reset,
- getBestAvailableStorageAPI: getBestAvailableStorageAPI
- };
+ return {
+ getItem: settingsStore.getItem,
+ setItem: settingsStore.setItem,
+ removeItem: settingsStore.removeItem,
+ hasItem: settingsStore.hasItem,
+ getCacheNames: getCacheNames,
+ reset: reset,
+ getBestAvailableStorageAPI: getBestAvailableStorageAPI
+ };
});
diff --git a/www/js/lib/utf8.js b/www/js/lib/utf8.js
index 0f5ca8189..dfc9cee56 100644
--- a/www/js/lib/utf8.js
+++ b/www/js/lib/utf8.js
@@ -2,28 +2,32 @@
* utf8.js : UTF8 conversion functions
* Original code from http://stackoverflow.com/users/553542/kadm , taken from
* http://stackoverflow.com/questions/1240408/reading-bytes-from-a-javascript-string
- *
+ *
* Copyright 2013-2014 Mossroy and contributors
* License GPL v3:
- *
+ *
* This file is part of Kiwix.
- *
+ *
* Kiwix is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
- *
+ *
* Kiwix is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+ *
* You should have received a copy of the GNU General Public License
* along with Kiwix (file LICENSE-GPLv3.txt). If not, see
*/
+
'use strict';
-define([], function() {
+/* global define */
+/* eslint-disable eqeqeq */
+
+define([], function () {
var utf8 = {};
/**
@@ -31,16 +35,18 @@ define([], function() {
* @param {String} str
* @returns {Array}
*/
- utf8.toByteArray = function(str) {
+ utf8.toByteArray = function (str) {
var byteArray = [];
- for (var i = 0; i < str.length; i++)
- if (str.charCodeAt(i) <= 0x7F)
+ for (var i = 0; i < str.length; i++) {
+ if (str.charCodeAt(i) <= 0x7F) {
byteArray.push(str.charCodeAt(i));
- else {
+ } else {
var h = encodeURIComponent(str.charAt(i)).substr(1).split('%');
- for (var j = 0; j < h.length; j++)
+ for (var j = 0; j < h.length; j++) {
byteArray.push(parseInt(h[j], 16));
+ }
}
+ }
return byteArray;
};
@@ -50,50 +56,44 @@ define([], function() {
* @param {Boolean} zeroTerminated
* @returns {String}
*/
- utf8.parse = function(data, zeroTerminated)
- {
+ utf8.parse = function (data, zeroTerminated) {
var u0, u1, u2, u3, u4, u5;
var str = '';
- for (var idx = 0; idx < data.length; ) {
+ for (var idx = 0; idx < data.length;) {
u0 = data[idx++];
- if (u0 === 0 && zeroTerminated)
+ if (u0 === 0 && zeroTerminated) {
break;
- if (!(u0 & 0x80))
- {
+ }
+ if (!(u0 & 0x80)) {
str += String.fromCharCode(u0);
continue;
}
u1 = data[idx++] & 63;
- if ((u0 & 0xe0) == 0xc0)
- {
+ if ((u0 & 0xe0) == 0xc0) {
str += String.fromCharCode(((u0 & 31) << 6) | u1);
continue;
}
u2 = data[idx++] & 63;
- if ((u0 & 0xf0) == 0xe0)
+ if ((u0 & 0xf0) == 0xe0) {
u0 = ((u0 & 15) << 12) | (u1 << 6) | u2;
- else
- {
+ } else {
u3 = data[idx++] & 63;
- if ((u0 & 0xF8) == 0xF0)
+ if ((u0 & 0xF8) == 0xF0) {
u0 = ((u0 & 7) << 18) | (u1 << 12) | (u2 << 6) | u3;
- else
- {
+ } else {
u4 = data[idx++] & 63;
- if ((u0 & 0xFC) == 0xF8)
+ if ((u0 & 0xFC) == 0xF8) {
u0 = ((u0 & 3) << 24) | (u1 << 18) | (u2 << 12) | (u3 << 6) | u4;
- else
- {
+ } else {
u5 = data[idx++] & 63;
u0 = ((u0 & 1) << 30) | (u1 << 24) | (u2 << 18) | (u3 << 12) | (u4 << 6) | u5;
}
}
}
- if (u0 < 0x10000)
+ if (u0 < 0x10000) {
str += String.fromCharCode(u0);
- else
- {
+ } else {
var ch = u0 - 0x10000;
str += String.fromCharCode(0xD800 | (ch >> 10), 0xDC00 | (ch & 0x3FF));
}
diff --git a/www/js/lib/util.js b/www/js/lib/util.js
index c388a9568..4edd83181 100644
--- a/www/js/lib/util.js
+++ b/www/js/lib/util.js
@@ -20,15 +20,17 @@
* along with Kiwix (file LICENSE-GPLv3.txt). If not, see
*/
'use strict';
-define([], function() {
+/* global define */
+
+define([], function () {
/**
* A Regular Expression to match the first letter of a word even if preceded by Unicode punctuation
* Includes currency signs and mathematical symbols: see https://stackoverflow.com/a/21396529/9727685
* DEV: To maintain the list below, see https://github.com/slevithan/xregexp/blob/master/tools/output/categories.js
* where all the different Unicode punctuation categories can be found (simplify double backspacing before using below)
* Note that the XRegExp punctuation categories begin at !-# in list below
- * @type {RegExp}
+ * @type {RegExp}
*/
var regExpFindStringParts = /(?:^|.+?)(?:[\s$£€\uFFE5^+=`~<>{}[\]|\u3000-\u303F!-#%-\x2A,-/:;\x3F@\x5B-\x5D_\x7B}\u00A1\u00A7\u00AB\u00B6\u00B7\u00BB\u00BF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u0AF0\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E3B\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]+|$)/g;
@@ -38,10 +40,10 @@ define([], function() {
* If caseMatchType is 'full', then all-uppercase combinations of each word are added to the variations array
* NB may produce duplicate strings if string begins with punctuation or if it is in a language with no case
* @param {String} string The string to be converted
- * @param {String} caseMatchType ('basic'|'full') The type (complexity) of case variations to calculate
+ * @param {String} caseMatchType ('basic'|'full') The type (complexity) of case variations to calculate
* @return {Array} An array containing strings with all possible combinations of case types
*/
- function allCaseFirstLetters(string, caseMatchType) {
+ function allCaseFirstLetters (string, caseMatchType) {
if (string) {
var comboArray = [];
// Split string into parts beginning with first word letters
@@ -93,12 +95,13 @@ define([], function() {
* @param {Array} array of String
* @returns {Array} same array of Strings, without duplicates
*/
- function removeDuplicateStringsInSmallArray(array) {
+ function removeDuplicateStringsInSmallArray (array) {
var unique = [];
for (var i = 0; i < array.length; i++) {
var current = array[i];
- if (unique.indexOf(current) < 0)
+ if (unique.indexOf(current) < 0) {
unique.push(current);
+ }
}
return unique;
}
@@ -109,7 +112,7 @@ define([], function() {
* @param {String} suffix
* @returns {Boolean}
*/
- function endsWith(str, suffix) {
+ function endsWith (str, suffix) {
return str.indexOf(suffix, str.length - suffix.length) !== -1;
}
@@ -120,7 +123,7 @@ define([], function() {
* @param {Boolean} littleEndian (optional)
* @returns {Float}
*/
- function readFloatFrom4Bytes(byteArray, firstIndex, littleEndian) {
+ function readFloatFrom4Bytes (byteArray, firstIndex, littleEndian) {
var dataView = new DataView(byteArray.buffer, firstIndex, 4);
var float = dataView.getFloat32(0, littleEndian);
return float;
@@ -131,9 +134,9 @@ define([], function() {
* @param {File} file The file object to be read
* @param {Integer} begin The offset in at which to begin reading
* @param {Integer} end The byte at whcih to stop reading (reads up to and including end - 1)
- * @returns {Promise} A Promise for an array buffer with the read data
+ * @returns {Promise} A Promise for an array buffer with the read data
*/
- function readFileSlice(file, begin, end) {
+ function readFileSlice (file, begin, end) {
if ('arrayBuffer' in Blob.prototype) {
// DEV: This method uses the native arrayBuffer method of Blob, if available, as it eliminates
// the need to use FileReader and set up event listeners; it also uses the method's native Promise
@@ -165,18 +168,19 @@ define([], function() {
* @param {Boolean} lowerBound Determines the type of search
* @returns {Promise} Promise for the lowest dirEntry that fulfils (or fails to fulfil) the query
*/
- function binarySearch(begin, end, query, lowerBound) {
- if (end <= begin)
+ function binarySearch (begin, end, query, lowerBound) {
+ if (end <= begin) {
return lowerBound ? begin : null;
+ }
var mid = Math.floor((begin + end) / 2);
- return query(mid).then(function(decision)
- {
- if (decision < 0)
+ return query(mid).then(function (decision) {
+ if (decision < 0) {
return binarySearch(begin, mid, query, lowerBound);
- else if (decision > 0)
+ } else if (decision > 0) {
return binarySearch(mid + 1, end, query, lowerBound);
- else
+ } else {
return mid;
+ }
});
}
@@ -189,23 +193,23 @@ define([], function() {
* @param {Integer} bits
* @returns {Integer}
*/
- function leftShift(int, bits) {
+ function leftShift (int, bits) {
return int * Math.pow(2, bits);
}
/**
* Queues Promise Factories* to be resolved or rejected sequentially. This helps to avoid overlapping Promise functions.
* Primarily used by uiUtil.systemAlert, to prevent alerts showing while others are being displayed.
- *
+ *
* *A Promise Factory is merely a Promise wrapped in a function to prevent it from executing immediately. E.g. to use
* this function with a Promise, call it like this (or, more likely, use your own pre-wrapped Promise):
- *
+ *
* return util.PromiseQueue.enqueue(function () {
* return new Promise(function (resolve, reject) { ... });
* });
- *
+ *
* Adapted from https://medium.com/@karenmarkosyan/how-to-manage-promises-into-dynamic-queue-with-vanilla-javascript-9d0d1f8d4df5
- *
+ *
* @type {Object} PromiseQueue
* @property {Function} enqueue Queues a Promise Factory. Call this function repeatedly to queue Promises sequentially
* @param {Function} promiseFactory A Promise wrapped in an ordinary function
@@ -217,7 +221,7 @@ define([], function() {
enqueue: function (promiseFactory) {
var that = this;
return new Promise(function (resolve, reject) {
- that._queue.push({promise: promiseFactory, resolve: resolve, reject: reject});
+ that._queue.push({ promise: promiseFactory, resolve: resolve, reject: reject });
if (!that._working) that._dequeue();
});
},
diff --git a/www/js/lib/xzdec_wrapper.js b/www/js/lib/xzdec_wrapper.js
index 292ba7bae..956ae8e8d 100644
--- a/www/js/lib/xzdec_wrapper.js
+++ b/www/js/lib/xzdec_wrapper.js
@@ -19,8 +19,11 @@
* You should have received a copy of the GNU General Public License
* along with Kiwix (file LICENSE-GPLv3.txt). If not, see
*/
+
'use strict';
+/* global params, define, XZ */
+
// DEV: Put your RequireJS definition in the rqDefXZ array below, and any function exports in the function parenthesis of the define statement
// We need to do it this way in order to load the wasm or asm versions of xzdec conditionally. Older browsers can only use the asm version
// because they cannot interpret WebAssembly.
@@ -40,7 +43,7 @@ if ('WebAssembly' in self) {
rqDefXZ.push('xzdec-asm');
}
-define(rqDefXZ, function(uiUtil) {
+define(rqDefXZ, function (uiUtil) {
// DEV: xzdec.js has been compiled with `-s EXPORT_NAME="XZ" -s MODULARIZE=1` to avoid a clash with zstddec.js
// Note that we include xzdec-asm or xzdec-wasm above in requireJS definition, but we cannot change the name in the function list
// There is no longer any need to load it in index.html
@@ -78,19 +81,19 @@ define(rqDefXZ, function(uiUtil) {
});
}
});
-
+
/**
* Number of milliseconds to wait for the decompressor to be available for another chunk
* @type Integer
*/
var DELAY_WAITING_IDLE_DECOMPRESSOR = 50;
-
+
/**
* Is the decompressor already working?
* @type Boolean
*/
var busy = false;
-
+
/**
* @typedef Decompressor
* @property {Integer} _chunkSize
@@ -100,14 +103,14 @@ define(rqDefXZ, function(uiUtil) {
* @property {Integer} _outStreamPos
* @property {Array} _outBuffer
*/
-
+
/**
* @constructor
* @param {FileReader} reader
* @param {Integer} chunkSize
* @returns {Decompressor}
*/
- function Decompressor(reader, chunkSize) {
+ function Decompressor (reader, chunkSize) {
params.decompressorAPI.decompressorLastUsed = 'XZ';
this._chunkSize = chunkSize || 1024 * 5;
this._reader = reader;
@@ -119,7 +122,7 @@ define(rqDefXZ, function(uiUtil) {
* @param {Integer} offset
* @param {Integer} length
*/
- Decompressor.prototype.readSlice = function(offset, length) {
+ Decompressor.prototype.readSlice = function (offset, length) {
busy = true;
var that = this;
this._inStreamPos = 0;
@@ -127,13 +130,13 @@ define(rqDefXZ, function(uiUtil) {
this._decHandle = xzdec._init_decompression(this._chunkSize);
this._outBuffer = new Int8Array(new ArrayBuffer(length));
this._outBufferPos = 0;
- return this._readLoop(offset, length).then(function(data) {
+ return this._readLoop(offset, length).then(function (data) {
xzdec._release(that._decHandle);
busy = false;
return data;
});
};
-
+
/**
* Reads stream of data from file offset for length of bytes to send to the decompresor
* This function ensures that only one decompression runs at a time
@@ -159,14 +162,14 @@ define(rqDefXZ, function(uiUtil) {
};
/**
- *
+ *
* @param {Integer} offset
* @param {Integer} length
* @returns {Array}
*/
- Decompressor.prototype._readLoop = function(offset, length) {
+ Decompressor.prototype._readLoop = function (offset, length) {
var that = this;
- return this._fillInBufferIfNeeded().then(function() {
+ return this._fillInBufferIfNeeded().then(function () {
var ret = xzdec._decompress(that._decHandle);
var finished = false;
if (ret === 0) {
@@ -180,37 +183,41 @@ define(rqDefXZ, function(uiUtil) {
}
var outPos = xzdec._get_out_pos(that._decHandle);
- if (outPos > 0 && that._outStreamPos + outPos >= offset)
- {
+ if (outPos > 0 && that._outStreamPos + outPos >= offset) {
var outBuffer = xzdec._get_out_buffer(that._decHandle);
var copyStart = offset - that._outStreamPos;
- if (copyStart < 0)
+ if (copyStart < 0) {
copyStart = 0;
- for (var i = copyStart; i < outPos && that._outBufferPos < that._outBuffer.length; i++)
+ }
+ for (var i = copyStart; i < outPos && that._outBufferPos < that._outBuffer.length; i++) {
that._outBuffer[that._outBufferPos++] = xzdec.HEAP8[outBuffer + i];
+ }
}
that._outStreamPos += outPos;
- if (outPos > 0)
+ if (outPos > 0) {
xzdec._out_buffer_cleared(that._decHandle);
- if (finished || that._outStreamPos >= offset + length)
+ }
+ if (finished || that._outStreamPos >= offset + length) {
return that._outBuffer;
- else
+ } else {
return that._readLoop(offset, length);
+ }
});
};
-
+
/**
- *
+ *
* @returns {Promise}
*/
- Decompressor.prototype._fillInBufferIfNeeded = function() {
+ Decompressor.prototype._fillInBufferIfNeeded = function () {
if (!xzdec._input_empty(this._decHandle)) {
return Promise.resolve(0);
}
var that = this;
- return this._reader(this._inStreamPos, this._chunkSize).then(function(data) {
- if (data.length > that._chunkSize)
+ return this._reader(this._inStreamPos, this._chunkSize).then(function (data) {
+ if (data.length > that._chunkSize) {
data = data.slice(0, that._chunkSize);
+ }
// For some reason, xzdec.writeArrayToMemory does not seem to be available, and is equivalent to xzdec.HEAP8.set
xzdec.HEAP8.set(data, xzdec._get_in_buffer(that._decHandle));
that._inStreamPos += data.length;
diff --git a/www/js/lib/zimArchive.js b/www/js/lib/zimArchive.js
index 72a14dc11..6476017bf 100644
--- a/www/js/lib/zimArchive.js
+++ b/www/js/lib/zimArchive.js
@@ -19,563 +19,571 @@
* You should have received a copy of the GNU General Public License
* along with Kiwix (file LICENSE-GPLv3.txt). If not, see
*/
+
'use strict';
+
+/* global params, define */
+
define(['zimfile', 'zimDirEntry', 'util', 'uiUtil', 'utf8'],
- function(zimfile, zimDirEntry, util, uiUtil, utf8) {
-
- /**
- * ZIM Archive
- *
- *
- * @typedef ZIMArchive
- * @property {ZIMFile} _file The ZIM file (instance of ZIMFile, that might physically be split into several actual files)
- * @property {String} _language Language of the content
- */
-
- /**
- * @callback callbackZIMArchive
- * @param {ZIMArchive} zimArchive Ready-to-use ZIMArchive
- */
-
- /**
- * @callback callbackMetadata
- * @param {String} data metadata string
- */
+ function (zimfile, zimDirEntry, util, uiUtil, utf8) {
+ /**
+ * ZIM Archive
+ *
+ *
+ * @typedef ZIMArchive
+ * @property {ZIMFile} _file The ZIM file (instance of ZIMFile, that might physically be split into several actual files)
+ * @property {String} _language Language of the content
+ */
- /**
- * @param {Worker} LZ A Web Worker to run the libzim Web Assembly binary
- */
- var LZ;
-
- /**
- * Creates a ZIM archive object to access the ZIM file at the given path in the given storage.
- * This constructor can also be used with a single File parameter.
- *
- * @param {StorageFirefoxOS|Array} storage Storage (in this case, the path must be given) or Array of Files (path parameter must be omitted)
- * @param {String} path The Storage path for an OS that requires this to be specified
- * @param {callbackZIMArchive} callbackReady The function to call when the archive is ready to use
- * @param {callbackZIMArchive} callbackError The function to call when an error occurs
- */
- function ZIMArchive(storage, path, callbackReady, callbackError) {
- var that = this;
- that._file = null;
- that._language = ""; //@TODO
- var createZimfile = function (fileArray) {
- zimfile.fromFileArray(fileArray).then(function (file) {
- that._file = file;
- // Clear the previous libzimWoker
- LZ = null;
- // Set a global parameter to report the search provider type
- params.searchProvider = 'title';
- // File has been created, but we need to add any Listings which extend the archive metadata
- that._file.setListings([
- // Provide here any Listings for which we need to extract metadata as key:value obects to be added to the file
- // 'ptrName' and 'countName' contain the key names to be set in the archive file object
- {
- // This defines the standard v0 (legacy) title index that contains listings for every entry in the ZIM (not just articles)
- // It represents the same index that is referenced in the ZIM archive header
- path: 'X/listing/titleOrdered/v0',
- ptrName: 'titlePtrPos',
- countName: 'entryCount'
- },
- {
- // This defines a new version 1 index that is present in no-namespace ZIMs, and contains a title-ordered list of articles
- path: 'X/listing/titleOrdered/v1',
- ptrName: 'articlePtrPos',
- countName: 'articleCount'
- },
- {
- // This tests for and specifies the existence of any Xapian Full Text Index
- path: 'X/fulltext/xapian',
- ptrName: 'fullTextIndex',
- countName: 'fullTextIndexSize'
- }
- ]).then(function () {
- // There is currently an exception thrown in the libzim wasm if we attempt to load a split ZIM archive, so we work around
- var isSplitZim = /\.zima.$/i.test(that._file._files[0].name);
- if (params.debugLibzimASM || that._file.fullTextIndex && !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...');
- LZ = new Worker('js/lib/libzim-' + libzimReaderType + '.js');
- that.callLibzimWorker({action: "init", files: that._file._files}).then(function (msg) {
- // console.debug(msg);
- params.searchProvider = 'fulltext: ' + libzimReaderType;
- // Update the API panel
+ /**
+ * @callback callbackZIMArchive
+ * @param {ZIMArchive} zimArchive Ready-to-use ZIMArchive
+ */
+
+ /**
+ * @callback callbackMetadata
+ * @param {String} data metadata string
+ */
+
+ /**
+ * @param {Worker} LZ A Web Worker to run the libzim Web Assembly binary
+ */
+ var LZ;
+
+ /**
+ * Creates a ZIM archive object to access the ZIM file at the given path in the given storage.
+ * This constructor can also be used with a single File parameter.
+ *
+ * @param {StorageFirefoxOS|Array} storage Storage (in this case, the path must be given) or Array of Files (path parameter must be omitted)
+ * @param {String} path The Storage path for an OS that requires this to be specified
+ * @param {callbackZIMArchive} callbackReady The function to call when the archive is ready to use
+ * @param {callbackZIMArchive} callbackError The function to call when an error occurs
+ */
+ function ZIMArchive (storage, path, callbackReady, callbackError) {
+ var that = this;
+ that._file = null;
+ that._language = ''; // @TODO
+ var createZimfile = function (fileArray) {
+ zimfile.fromFileArray(fileArray).then(function (file) {
+ that._file = file;
+ // Clear the previous libzimWoker
+ LZ = null;
+ // Set a global parameter to report the search provider type
+ params.searchProvider = 'title';
+ // File has been created, but we need to add any Listings which extend the archive metadata
+ that._file.setListings([
+ // Provide here any Listings for which we need to extract metadata as key:value obects to be added to the file
+ // 'ptrName' and 'countName' contain the key names to be set in the archive file object
+ {
+ // This defines the standard v0 (legacy) title index that contains listings for every entry in the ZIM (not just articles)
+ // It represents the same index that is referenced in the ZIM archive header
+ path: 'X/listing/titleOrdered/v0',
+ ptrName: 'titlePtrPos',
+ countName: 'entryCount'
+ },
+ {
+ // This defines a new version 1 index that is present in no-namespace ZIMs, and contains a title-ordered list of articles
+ path: 'X/listing/titleOrdered/v1',
+ ptrName: 'articlePtrPos',
+ countName: 'articleCount'
+ },
+ {
+ // This tests for and specifies the existence of any Xapian Full Text Index
+ path: 'X/fulltext/xapian',
+ ptrName: 'fullTextIndex',
+ countName: 'fullTextIndexSize'
+ }
+ ]).then(function () {
+ // There is currently an exception thrown in the libzim wasm if we attempt to load a split ZIM archive, so we work around
+ var isSplitZim = /\.zima.$/i.test(that._file._files[0].name);
+ if (params.debugLibzimASM || that._file.fullTextIndex && !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...');
+ LZ = new Worker('js/lib/libzim-' + libzimReaderType + '.js');
+ that.callLibzimWorker({ action: 'init', files: that._file._files }).then(function (msg) {
+ // console.debug(msg);
+ params.searchProvider = 'fulltext: ' + libzimReaderType;
+ // Update the API panel
+ uiUtil.reportSearchProviderToAPIStatusPanel(params.searchProvider);
+ }).catch(function (err) {
+ uiUtil.reportSearchProviderToAPIStatusPanel(params.searchProvider + ': ERROR');
+ console.error('The libzim worker could not be instantiated!', err);
+ });
+ } else {
+ // var message = 'Full text searching is not available because ';
+ if (!that._file.fullTextIndex) {
+ params.searchProvider += ': no_fulltext'; // message += 'this ZIM does not have a full-text index.';
+ } else if (isSplitZim) {
+ params.searchProvider += ': split_zim'; // message += 'the ZIM archive is split.';
+ } else if (typeof Atomics === 'undefined') {
+ params.searchProvider += ': no_atomics'; // message += 'this browser does not support Atomic operations.';
+ } else if (/Android/.test(params.appType)) {
+ params.searchProvider += ': no_sharedArrayBuffer';
+ }
uiUtil.reportSearchProviderToAPIStatusPanel(params.searchProvider);
- }).catch(function (err) {
- uiUtil.reportSearchProviderToAPIStatusPanel(params.searchProvider + ': ERROR');
- console.error('The libzim worker could not be instantiated!', err);
- });
- } else {
- // var message = 'Full text searching is not available because ';
- if (!that._file.fullTextIndex) {
- params.searchProvider += ': no_fulltext'; // message += 'this ZIM does not have a full-text index.';
- } else if (isSplitZim) {
- params.searchProvider += ': split_zim'; // message += 'the ZIM archive is split.';
- } else if (typeof Atomics === 'undefined') {
- params.searchProvider += ': no_atomics'; // message += 'this browser does not support Atomic operations.';
- } else if (/Android/.test(params.appType)) {
- params.searchProvider += ': no_sharedArrayBuffer';
+ // uiUtil.systemAlert(message);
}
- uiUtil.reportSearchProviderToAPIStatusPanel(params.searchProvider);
- // uiUtil.systemAlert(message);
- }
- });
- // Set the archive file type ('open' or 'zimit')
- params.zimType = that.setZimType();
- // DEV: Currently, extended listings are only used for title (=article) listings when the user searches
- // for an article or uses the Random button, by which time the listings will have been extracted.
- // If, in the future, listings are used in a more time-critical manner, consider forcing a wait before
- // declaring the archive to be ready, by chaining the following callback in a .then() function of setListings.
- callbackReady(that);
- });
- };
- if (storage && !path) {
- var fileList = storage;
- // We need to convert the FileList into an Array
- var fileArray = [].slice.call(fileList);
- // The constructor has been called with an array of File/Blob parameter
- createZimfile(fileArray);
- } else {
- if (/.*zim..$/.test(path)) {
- // split archive
- that._searchArchiveParts(storage, path.slice(0, -2)).then(function (fileArray) {
- createZimfile(fileArray);
- }).catch(function (error) {
- callbackError("Error reading files in split archive " + path + ": " + error, "Error reading archive files");
+ });
+ // Set the archive file type ('open' or 'zimit')
+ params.zimType = that.setZimType();
+ // DEV: Currently, extended listings are only used for title (=article) listings when the user searches
+ // for an article or uses the Random button, by which time the listings will have been extracted.
+ // If, in the future, listings are used in a more time-critical manner, consider forcing a wait before
+ // declaring the archive to be ready, by chaining the following callback in a .then() function of setListings.
+ callbackReady(that);
});
+ };
+ if (storage && !path) {
+ var fileList = storage;
+ // We need to convert the FileList into an Array
+ var fileArray = [].slice.call(fileList);
+ // The constructor has been called with an array of File/Blob parameter
+ createZimfile(fileArray);
} else {
- storage.get(path).then(function (file) {
- createZimfile([file]);
- }).catch(function (error) {
- callbackError("Error reading ZIM file " + path + " : " + error, "Error reading archive file");
- });
+ if (/.*zim..$/.test(path)) {
+ // split archive
+ that._searchArchiveParts(storage, path.slice(0, -2)).then(function (fileArray) {
+ createZimfile(fileArray);
+ }).catch(function (error) {
+ callbackError('Error reading files in split archive ' + path + ': ' + error, 'Error reading archive files');
+ });
+ } else {
+ storage.get(path).then(function (file) {
+ createZimfile([file]);
+ }).catch(function (error) {
+ callbackError('Error reading ZIM file ' + path + ' : ' + error, 'Error reading archive file');
+ });
+ }
}
}
- }
- /**
- * Searches the directory for all parts of a split archive.
- * @param {Storage} storage storage interface
- * @param {String} prefixPath path to the split files, missing the "aa" / "ab" / ... suffix.
- * @returns {Promise} that resolves to the array of file objects found.
- */
- ZIMArchive.prototype._searchArchiveParts = function(storage, prefixPath) {
- var fileArray = [];
- var nextFile = function(part) {
- var suffix = String.fromCharCode(0x61 + Math.floor(part / 26)) + String.fromCharCode(0x61 + part % 26);
- return storage.get(prefixPath + suffix)
- .then(function(file) {
- fileArray.push(file);
- return nextFile(part + 1);
- }, function(error) {
- return fileArray;
- });
+ /**
+ * Searches the directory for all parts of a split archive.
+ * @param {Storage} storage storage interface
+ * @param {String} prefixPath path to the split files, missing the "aa" / "ab" / ... suffix.
+ * @returns {Promise} that resolves to the array of file objects found.
+ */
+ ZIMArchive.prototype._searchArchiveParts = function (storage, prefixPath) {
+ var fileArray = [];
+ var nextFile = function (part) {
+ var suffix = String.fromCharCode(0x61 + Math.floor(part / 26)) + String.fromCharCode(0x61 + part % 26);
+ return storage.get(prefixPath + suffix)
+ .then(function (file) {
+ fileArray.push(file);
+ return nextFile(part + 1);
+ }, function (error) {
+ console.error('Error reading split archive file ' + prefixPath + suffix + ': ', error);
+ return fileArray;
+ });
+ };
+ return nextFile(0);
};
- return nextFile(0);
- };
- /**
- *
- * @returns {Boolean}
- */
- ZIMArchive.prototype.isReady = function() {
- return this._file !== null;
- };
+ /**
+ *
+ * @returns {Boolean}
+ */
+ ZIMArchive.prototype.isReady = function () {
+ return this._file !== null;
+ };
- /**
- * Detects whether the supplied archive is a Zimit-style archive or an OpenZIM archive and
- * sets a _file.zimType property accordingly; also returns the detected type. Extends ZIMFile.
- * @returns {String} Either 'zimit' for a Zimit archive, or 'open' for an OpenZIM archive
- */
- ZIMArchive.prototype.setZimType = function () {
- var fileType = null;
- if (this.isReady()) {
- fileType = 'open';
- this._file.mimeTypes.forEach(function (v) {
- if (/warc-headers/i.test(v)) fileType = 'zimit';
- });
- this._file.zimType = fileType;
- console.debug('Archive type set to: ' + fileType);
- } else {
- console.error('ZIMArchive is not ready! Cannot set ZIM type.');
- }
- return fileType;
- };
+ /**
+ * Detects whether the supplied archive is a Zimit-style archive or an OpenZIM archive and
+ * sets a _file.zimType property accordingly; also returns the detected type. Extends ZIMFile.
+ * @returns {String} Either 'zimit' for a Zimit archive, or 'open' for an OpenZIM archive
+ */
+ ZIMArchive.prototype.setZimType = function () {
+ var fileType = null;
+ if (this.isReady()) {
+ fileType = 'open';
+ this._file.mimeTypes.forEach(function (v) {
+ if (/warc-headers/i.test(v)) fileType = 'zimit';
+ });
+ this._file.zimType = fileType;
+ console.debug('Archive type set to: ' + fileType);
+ } else {
+ console.error('ZIMArchive is not ready! Cannot set ZIM type.');
+ }
+ return fileType;
+ };
- /**
- * Looks for the DirEntry of the main page
- * @param {callbackDirEntry} callback
- * @returns {Promise} that resolves to the DirEntry
- */
- ZIMArchive.prototype.getMainPageDirEntry = function(callback) {
- if (this.isReady()) {
- var mainPageUrlIndex = this._file.mainPage;
- this._file.dirEntryByUrlIndex(mainPageUrlIndex).then(callback);
- }
- };
+ /**
+ * Looks for the DirEntry of the main page
+ * @param {callbackDirEntry} callback
+ * @returns {Promise} that resolves to the DirEntry
+ */
+ ZIMArchive.prototype.getMainPageDirEntry = function (callback) {
+ if (this.isReady()) {
+ var mainPageUrlIndex = this._file.mainPage;
+ this._file.dirEntryByUrlIndex(mainPageUrlIndex).then(callback);
+ }
+ };
- /**
- *
- * @param {String} dirEntryId
- * @returns {DirEntry}
- */
- ZIMArchive.prototype.parseDirEntryId = function(dirEntryId) {
- return zimDirEntry.DirEntry.fromStringId(this._file, dirEntryId);
- };
-
- /**
- * @callback callbackDirEntryList
- * @param {Array.} dirEntryArray Array of DirEntries found
- */
+ /**
+ *
+ * @param {String} dirEntryId
+ * @returns {DirEntry}
+ */
+ ZIMArchive.prototype.parseDirEntryId = function (dirEntryId) {
+ return zimDirEntry.DirEntry.fromStringId(this._file, dirEntryId);
+ };
- /**
- * Look for DirEntries with title starting with the prefix of the current search object.
- * For now, ZIM titles are case sensitive.
- * So, as workaround, we try several variants of the prefix to find more results.
- * This should be enhanced when the ZIM format will be modified to store normalized titles
- * See https://phabricator.wikimedia.org/T108536
- *
- * @param {Object} search The current appstate.search object
- * @param {callbackDirEntryList} callback The function to call with the result
- * @param {Boolean} noInterim A flag to prevent callback until all results are ready (used in testing)
- */
- ZIMArchive.prototype.findDirEntriesWithPrefix = function (search, callback, noInterim) {
- var that = this;
- // Establish array of initial values that must be searched first. All of these patterns are generated by the full
- // search type, and some by basic, but we need the most common patterns to be searched first, as it returns search
- // results much more quickly if we do this (and the user can click on a result before the rarer patterns complete)
- // NB duplicates are removed before processing search array
- var startArray = [];
- var dirEntries = [];
- search.scanCount = 0;
- // Launch a full-text search if possible
- if (LZ) that.findDirEntriesFromFullTextSearch(search, dirEntries).then(function (fullTextDirEntries) {
- // If user initiated a new search, cancel this one
- // In particular, do not set the search status back to 'complete'
- // as that would cause outdated results to unexpectedly pop up
- if (search.status === 'cancelled') return callback([], search);
- dirEntries = fullTextDirEntries;
- search.status = 'complete';
- callback(dirEntries, search);
- });
- // Ensure a search is done on the string exactly as typed
- startArray.push(search.prefix);
- // Normalize any spacing and make string all lowercase
- var prefix = search.prefix.replace(/\s+/g, ' ').toLocaleLowerCase();
- // Add lowercase string with initial uppercase (this is a very common pattern)
- startArray.push(prefix.replace(/^./, function (m) {
- return m.toLocaleUpperCase();
- }));
- // Get the full array of combinations to check number of combinations
- var fullCombos = util.removeDuplicateStringsInSmallArray(util.allCaseFirstLetters(prefix, 'full'));
- // Put cap on exponential number of combinations (five words = 3^5 = 243 combinations)
- search.type = fullCombos.length < 300 ? 'full' : 'basic';
- // We have to remove duplicate string combinations because util.allCaseFirstLetters() can return some combinations
- // where uppercase and lowercase combinations are exactly the same, e.g. where prefix begins with punctuation
- // or currency signs, for languages without case, or where user-entered case duplicates calculated case
- var prefixVariants = util.removeDuplicateStringsInSmallArray(
- startArray.concat(
- // Get basic combinations first for speed of returning results
- util.allCaseFirstLetters(prefix).concat(
- search.type === 'full' ? fullCombos : []
- )
- )
- );
- function searchNextVariant() {
- // If user has initiated a new search, cancel this one
- if (search.status === 'cancelled') return callback([], search);
- if (prefixVariants.length === 0 || dirEntries.length >= search.size) {
- // We have found all the title-search entries we are going to get, so indicate search type if we're still searching
- if (LZ && search.status !== 'complete') search.type = 'fulltext';
- else search.status = 'complete';
- return callback(dirEntries, search);
- }
- // Dynamically populate list of articles
- search.status = 'interim';
- if (!noInterim) callback(dirEntries, search);
- search.found = dirEntries.length;
- var prefix = prefixVariants[0];
- // console.debug('Searching for: ' + prefixVariants[0]);
- prefixVariants = prefixVariants.slice(1);
- that.findDirEntriesWithPrefixCaseSensitive(prefix, search,
- function (newDirEntries, countReport, interim) {
- search.countReport = countReport;
+ /**
+ * @callback callbackDirEntryList
+ * @param {Array.} dirEntryArray Array of DirEntries found
+ */
+
+ /**
+ * Look for DirEntries with title starting with the prefix of the current search object.
+ * For now, ZIM titles are case sensitive.
+ * So, as workaround, we try several variants of the prefix to find more results.
+ * This should be enhanced when the ZIM format will be modified to store normalized titles
+ * See https://phabricator.wikimedia.org/T108536
+ *
+ * @param {Object} search The current appstate.search object
+ * @param {callbackDirEntryList} callback The function to call with the result
+ * @param {Boolean} noInterim A flag to prevent callback until all results are ready (used in testing)
+ */
+ ZIMArchive.prototype.findDirEntriesWithPrefix = function (search, callback, noInterim) {
+ var that = this;
+ // Establish array of initial values that must be searched first. All of these patterns are generated by the full
+ // search type, and some by basic, but we need the most common patterns to be searched first, as it returns search
+ // results much more quickly if we do this (and the user can click on a result before the rarer patterns complete)
+ // NB duplicates are removed before processing search array
+ var startArray = [];
+ var dirEntries = [];
+ search.scanCount = 0;
+ // Launch a full-text search if possible
+ if (LZ) {
+ that.findDirEntriesFromFullTextSearch(search, dirEntries).then(function (fullTextDirEntries) {
+ // If user initiated a new search, cancel this one
+ // In particular, do not set the search status back to 'complete'
+ // as that would cause outdated results to unexpectedly pop up
if (search.status === 'cancelled') return callback([], search);
- if (!noInterim && countReport === true) return callback(dirEntries, search);
- if (interim) {// Only push interim results (else results will be pushed again at end of variant loop)
- [].push.apply(dirEntries, newDirEntries);
- search.found = dirEntries.length;
- if (!noInterim && newDirEntries.length) return callback(dirEntries, search);
- } else return searchNextVariant();
- }
- );
- }
- searchNextVariant();
- };
-
- /**
- * A method to return the namespace in the ZIM file that contains the primary user content. In old-format ZIM files (minor
- * version 0) there are a number of content namespaces, but the primary one in which to search for titles is 'A'. In new-format
- * ZIMs (minor version 1) there is a single content namespace 'C'. See https://openzim.org/wiki/ZIM_file_format. This method
- * throws an error if it cannot determine the namespace or if the ZIM is not ready.
- * @returns {String} The content namespace for the ZIM archive
- */
- ZIMArchive.prototype.getContentNamespace = function () {
- var errorText;
- if (this.isReady()) {
- var ver = this._file.minorVersion;
- // DEV: There are currently only two defined values for minorVersion in the OpenZIM specification
- // If this changes, adapt the error checking and return values
- if (ver > 1) {
- errorText = 'Unknown ZIM minor version!';
- } else {
- return ver === 0 ? 'A' : 'C';
+ dirEntries = fullTextDirEntries;
+ search.status = 'complete';
+ callback(dirEntries, search);
+ });
}
- } else {
- errorText = 'We could not determine the content namespace because the ZIM file is not ready!';
- }
- throw new Error(errorText);
- };
-
- /**
- * Look for dirEntries with title starting with the given prefix (case-sensitive)
- *
- * @param {String} prefix The case-sensitive value against which dirEntry titles (or url) will be compared
- * @param {Object} search The appstate.search object (for comparison, so that we can cancel long binary searches)
- * @param {callbackDirEntryList} callback The function to call with the array of dirEntries with titles that begin with prefix
- */
- ZIMArchive.prototype.findDirEntriesWithPrefixCaseSensitive = function(prefix, search, callback) {
- var that = this;
- var cns = this.getContentNamespace();
- // Search v1 article listing if available, otherwise fallback to v0
- var articleCount = this._file.articleCount || this._file.entryCount;
- util.binarySearch(0, articleCount, function(i) {
- return that._file.dirEntryByTitleIndex(i).then(function(dirEntry) {
- if (search.status === 'cancelled') return 0;
- var ns = dirEntry.namespace;
- // DEV: This search is redundant if we managed to populate articlePtrLst and articleCount, but it only takes two instructions and
- // provides maximum compatibility with rare ZIMs where attempts to find first and last article (in zimArchive.js) may have failed
- if (ns < cns) return 1;
- if (ns > cns) return -1;
- // We should now be in namespace A (old format ZIM) or C (new format ZIM)
- return prefix <= dirEntry.getTitleOrUrl() ? -1 : 1;
- });
- }, true).then(function(firstIndex) {
- var vDirEntries = [];
- var addDirEntries = function(index, lastTitle) {
- if (search.status === 'cancelled' || search.found >= search.size || index >= articleCount
- || lastTitle && !~lastTitle.indexOf(prefix)) {
- // DEV: Diagnostics to be removed before merge
- if (vDirEntries.length) {
- console.debug('Scanned ' + (index - firstIndex) + ' titles for "' + prefix +
- '" (found ' + vDirEntries.length + ' match' + (vDirEntries.length === 1 ? ')' : 'es)'));
- }
- return {
- 'dirEntries': vDirEntries,
- 'nextStart': index
- };
+ // Ensure a search is done on the string exactly as typed
+ startArray.push(search.prefix);
+ // Normalize any spacing and make string all lowercase
+ var prefix = search.prefix.replace(/\s+/g, ' ').toLocaleLowerCase();
+ // Add lowercase string with initial uppercase (this is a very common pattern)
+ startArray.push(prefix.replace(/^./, function (m) {
+ return m.toLocaleUpperCase();
+ }));
+ // Get the full array of combinations to check number of combinations
+ var fullCombos = util.removeDuplicateStringsInSmallArray(util.allCaseFirstLetters(prefix, 'full'));
+ // Put cap on exponential number of combinations (five words = 3^5 = 243 combinations)
+ search.type = fullCombos.length < 300 ? 'full' : 'basic';
+ // We have to remove duplicate string combinations because util.allCaseFirstLetters() can return some combinations
+ // where uppercase and lowercase combinations are exactly the same, e.g. where prefix begins with punctuation
+ // or currency signs, for languages without case, or where user-entered case duplicates calculated case
+ var prefixVariants = util.removeDuplicateStringsInSmallArray(
+ startArray.concat(
+ // Get basic combinations first for speed of returning results
+ util.allCaseFirstLetters(prefix).concat(
+ search.type === 'full' ? fullCombos : []
+ )
+ )
+ );
+ function searchNextVariant () {
+ // If user has initiated a new search, cancel this one
+ if (search.status === 'cancelled') return callback([], search);
+ if (prefixVariants.length === 0 || dirEntries.length >= search.size) {
+ // We have found all the title-search entries we are going to get, so indicate search type if we're still searching
+ if (LZ && search.status !== 'complete') search.type = 'fulltext';
+ else search.status = 'complete';
+ return callback(dirEntries, search);
}
- return that._file.dirEntryByTitleIndex(index).then(function(dirEntry) {
- search.scanCount++;
- var title = dirEntry.getTitleOrUrl();
- // Only return dirEntries with titles that actually begin with prefix
- if (dirEntry.namespace === cns && title.indexOf(prefix) === 0) {
- vDirEntries.push(dirEntry);
- // Report interim result
- callback([dirEntry], false, true);
+ // Dynamically populate list of articles
+ search.status = 'interim';
+ if (!noInterim) callback(dirEntries, search);
+ search.found = dirEntries.length;
+ var prefix = prefixVariants[0];
+ // console.debug('Searching for: ' + prefixVariants[0]);
+ prefixVariants = prefixVariants.slice(1);
+ that.findDirEntriesWithPrefixCaseSensitive(prefix, search,
+ function (newDirEntries, countReport, interim) {
+ search.countReport = countReport;
+ if (search.status === 'cancelled') return callback([], search);
+ if (!noInterim && countReport === true) return callback(dirEntries, search);
+ if (interim) { // Only push interim results (else results will be pushed again at end of variant loop)
+ [].push.apply(dirEntries, newDirEntries);
+ search.found = dirEntries.length;
+ if (!noInterim && newDirEntries.length) return callback(dirEntries, search);
+ } else return searchNextVariant();
}
- return addDirEntries(index + 1, title);
- });
- };
- return addDirEntries(firstIndex);
- }).then(callback);
- };
+ );
+ }
+ searchNextVariant();
+ };
- /**
- * Find Directory Entries corresponding to the requested search using Full Text search provided by libzim
- *
- * @param {Object} search The appstate.search object
- * @param {Array} dirEntries The array of already found Directory Entries
- * @returns {Promise} The augmented array of Directory Entries with titles that correspond to search
- */
- ZIMArchive.prototype.findDirEntriesFromFullTextSearch = function (search, dirEntries) {
- var cns = this.getContentNamespace();
- var that = this;
- // We give ourselves an overhead in caclulating the results needed, because full-text search will return some results already found
- // var resultsNeeded = Math.floor(params.maxSearchResultsSize - dirEntries.length / 2);
- var resultsNeeded = params.maxSearchResultsSize;
- return this.callLibzimWorker({action: "search", text: search.prefix, numResults: resultsNeeded}).then(function (results) {
- if (results) {
- var dirEntryPaths = [];
- var fullTextPaths = [];
- // Collect all the found paths for the dirEntries
- for (var i = 0; i < dirEntries.length; i++) {
- dirEntryPaths.push(dirEntries[i].namespace + '/' + dirEntries[i].url);
- }
- // Collect all the paths for full text search, pruning as we go
- var path;
- for (var j = 0; j < results.entries.length; j++) {
- search.scanCount++;
- path = results.entries[j].path;
- // Full-text search result paths are missing the namespace in Type 1 ZIMs, so we add it back
- path = cns === 'C' ? cns + '/' + path : path;
- if (~dirEntryPaths.indexOf(path)) continue;
- fullTextPaths.push(path);
- }
- var promisesForDirEntries = [];
- for (var k = 0; k < fullTextPaths.length; k++) {
- promisesForDirEntries.push(that.getDirEntryByPath(fullTextPaths[k]));
+ /**
+ * A method to return the namespace in the ZIM file that contains the primary user content. In old-format ZIM files (minor
+ * version 0) there are a number of content namespaces, but the primary one in which to search for titles is 'A'. In new-format
+ * ZIMs (minor version 1) there is a single content namespace 'C'. See https://openzim.org/wiki/ZIM_file_format. This method
+ * throws an error if it cannot determine the namespace or if the ZIM is not ready.
+ * @returns {String} The content namespace for the ZIM archive
+ */
+ ZIMArchive.prototype.getContentNamespace = function () {
+ var errorText;
+ if (this.isReady()) {
+ var ver = this._file.minorVersion;
+ // DEV: There are currently only two defined values for minorVersion in the OpenZIM specification
+ // If this changes, adapt the error checking and return values
+ if (ver > 1) {
+ errorText = 'Unknown ZIM minor version!';
+ } else {
+ return ver === 0 ? 'A' : 'C';
}
- return Promise.all(promisesForDirEntries).then(function (fullTextDirEntries) {
- for (var l = 0; l < fullTextDirEntries.length; l++) {
- dirEntries.push(fullTextDirEntries[l]);
- }
- return(dirEntries);
- });
} else {
- return(dirEntries);
+ errorText = 'We could not determine the content namespace because the ZIM file is not ready!';
}
- });
- };
+ throw new Error(errorText);
+ };
- /**
- * Calls the libzim Web Worker with the given parameters, and returns a Promise with its response
- *
- * @param {Object} parameters
- * @returns {Promise}
- */
- ZIMArchive.prototype.callLibzimWorker = function (parameters) {
- return new Promise(function (resolve, reject) {
- console.debug("Calling libzim WebWorker with parameters", parameters);
- var tmpMessageChannel = new MessageChannel();
- // var t0 = performance.now();
- tmpMessageChannel.port1.onmessage = function (event) {
- // var t1 = performance.now();
- // var readTime = Math.round(t1 - t0);
- // console.debug("Response given by the WebWorker in " + readTime + " ms", event.data);
- resolve(event.data);
- };
- tmpMessageChannel.port1.onerror = function (event) {
- // var t1 = performance.now();
- // var readTime = Math.round(t1 - t0);
- // console.error("Error sent by the WebWorker in " + readTime + " ms", event.data);
- reject(event.data);
- };
- LZ.postMessage(parameters, [tmpMessageChannel.port2]);
- });
- };
-
- /**
- * @callback callbackDirEntry
- * @param {DirEntry} dirEntry The DirEntry found
- */
+ /**
+ * Look for dirEntries with title starting with the given prefix (case-sensitive)
+ *
+ * @param {String} prefix The case-sensitive value against which dirEntry titles (or url) will be compared
+ * @param {Object} search The appstate.search object (for comparison, so that we can cancel long binary searches)
+ * @param {callbackDirEntryList} callback The function to call with the array of dirEntries with titles that begin with prefix
+ */
+ ZIMArchive.prototype.findDirEntriesWithPrefixCaseSensitive = function (prefix, search, callback) {
+ var that = this;
+ var cns = this.getContentNamespace();
+ // Search v1 article listing if available, otherwise fallback to v0
+ var articleCount = this._file.articleCount || this._file.entryCount;
+ util.binarySearch(0, articleCount, function (i) {
+ return that._file.dirEntryByTitleIndex(i).then(function (dirEntry) {
+ if (search.status === 'cancelled') return 0;
+ var ns = dirEntry.namespace;
+ // DEV: This search is redundant if we managed to populate articlePtrLst and articleCount, but it only takes two instructions and
+ // provides maximum compatibility with rare ZIMs where attempts to find first and last article (in zimArchive.js) may have failed
+ if (ns < cns) return 1;
+ if (ns > cns) return -1;
+ // We should now be in namespace A (old format ZIM) or C (new format ZIM)
+ return prefix <= dirEntry.getTitleOrUrl() ? -1 : 1;
+ });
+ }, true).then(function (firstIndex) {
+ var vDirEntries = [];
+ var addDirEntries = function (index, lastTitle) {
+ if (search.status === 'cancelled' || search.found >= search.size || index >= articleCount ||
+ lastTitle && !~lastTitle.indexOf(prefix)) {
+ // DEV: Diagnostics to be removed before merge
+ if (vDirEntries.length) {
+ console.debug('Scanned ' + (index - firstIndex) + ' titles for "' + prefix +
+ '" (found ' + vDirEntries.length + ' match' + (vDirEntries.length === 1 ? ')' : 'es)'));
+ }
+ return {
+ dirEntries: vDirEntries,
+ nextStart: index
+ };
+ }
+ return that._file.dirEntryByTitleIndex(index).then(function (dirEntry) {
+ search.scanCount++;
+ var title = dirEntry.getTitleOrUrl();
+ // Only return dirEntries with titles that actually begin with prefix
+ if (dirEntry.namespace === cns && title.indexOf(prefix) === 0) {
+ vDirEntries.push(dirEntry);
+ // Report interim result
+ callback([dirEntry], false, true);
+ }
+ return addDirEntries(index + 1, title);
+ });
+ };
+ return addDirEntries(firstIndex);
+ }).then(callback);
+ };
- /**
- *
- * @param {DirEntry} dirEntry
- * @param {callbackDirEntry} callback
- */
- ZIMArchive.prototype.resolveRedirect = function(dirEntry, callback) {
- this._file.dirEntryByUrlIndex(dirEntry.redirectTarget).then(callback);
- };
-
- /**
- * @callback callbackStringContent
- * @param {String} content String content
- */
-
- /**
- *
- * @param {DirEntry} dirEntry
- * @param {callbackStringContent} callback
- */
- ZIMArchive.prototype.readUtf8File = function(dirEntry, callback) {
- dirEntry.readData().then(function(data) {
- callback(dirEntry, utf8.parse(data));
- });
- };
+ /**
+ * Find Directory Entries corresponding to the requested search using Full Text search provided by libzim
+ *
+ * @param {Object} search The appstate.search object
+ * @param {Array} dirEntries The array of already found Directory Entries
+ * @returns {Promise} The augmented array of Directory Entries with titles that correspond to search
+ */
+ ZIMArchive.prototype.findDirEntriesFromFullTextSearch = function (search, dirEntries) {
+ var cns = this.getContentNamespace();
+ var that = this;
+ // We give ourselves an overhead in caclulating the results needed, because full-text search will return some results already found
+ // var resultsNeeded = Math.floor(params.maxSearchResultsSize - dirEntries.length / 2);
+ var resultsNeeded = params.maxSearchResultsSize;
+ return this.callLibzimWorker({ action: 'search', text: search.prefix, numResults: resultsNeeded }).then(function (results) {
+ if (results) {
+ var dirEntryPaths = [];
+ var fullTextPaths = [];
+ // Collect all the found paths for the dirEntries
+ for (var i = 0; i < dirEntries.length; i++) {
+ dirEntryPaths.push(dirEntries[i].namespace + '/' + dirEntries[i].url);
+ }
+ // Collect all the paths for full text search, pruning as we go
+ var path;
+ for (var j = 0; j < results.entries.length; j++) {
+ search.scanCount++;
+ path = results.entries[j].path;
+ // Full-text search result paths are missing the namespace in Type 1 ZIMs, so we add it back
+ path = cns === 'C' ? cns + '/' + path : path;
+ if (~dirEntryPaths.indexOf(path)) continue;
+ fullTextPaths.push(path);
+ }
+ var promisesForDirEntries = [];
+ for (var k = 0; k < fullTextPaths.length; k++) {
+ promisesForDirEntries.push(that.getDirEntryByPath(fullTextPaths[k]));
+ }
+ return Promise.all(promisesForDirEntries).then(function (fullTextDirEntries) {
+ for (var l = 0; l < fullTextDirEntries.length; l++) {
+ dirEntries.push(fullTextDirEntries[l]);
+ }
+ return dirEntries;
+ });
+ } else {
+ return dirEntries;
+ }
+ });
+ };
- /**
- * @callback callbackBinaryContent
- * @param {Uint8Array} content binary content
- */
+ /**
+ * Calls the libzim Web Worker with the given parameters, and returns a Promise with its response
+ *
+ * @param {Object} parameters
+ * @returns {Promise}
+ */
+ ZIMArchive.prototype.callLibzimWorker = function (parameters) {
+ return new Promise(function (resolve, reject) {
+ console.debug('Calling libzim WebWorker with parameters', parameters);
+ var tmpMessageChannel = new MessageChannel();
+ // var t0 = performance.now();
+ tmpMessageChannel.port1.onmessage = function (event) {
+ // var t1 = performance.now();
+ // var readTime = Math.round(t1 - t0);
+ // console.debug("Response given by the WebWorker in " + readTime + " ms", event.data);
+ resolve(event.data);
+ };
+ tmpMessageChannel.port1.onerror = function (event) {
+ // var t1 = performance.now();
+ // var readTime = Math.round(t1 - t0);
+ // console.error("Error sent by the WebWorker in " + readTime + " ms", event.data);
+ reject(event.data);
+ };
+ LZ.postMessage(parameters, [tmpMessageChannel.port2]);
+ });
+ };
- /**
- * Read a binary file.
- * @param {DirEntry} dirEntry
- * @param {callbackBinaryContent} callback
- */
- ZIMArchive.prototype.readBinaryFile = function(dirEntry, callback) {
- return dirEntry.readData().then(function(data) {
- callback(dirEntry, data);
- });
- };
+ /**
+ * @callback callbackDirEntry
+ * @param {DirEntry} dirEntry The DirEntry found
+ */
- /**
- * Searches the URL pointer list of Directory Entries by pathname
- * @param {String} path The pathname of the DirEntry that is required (namespace + filename)
- * @return {Promise} A Promise that resolves to a Directory Entry, or null if not found.
- */
- ZIMArchive.prototype.getDirEntryByPath = function(path) {
- var that = this;
- return util.binarySearch(0, this._file.entryCount, function(i) {
- return that._file.dirEntryByUrlIndex(i).then(function(dirEntry) {
- var url = dirEntry.namespace + "/" + dirEntry.url;
- if (path < url)
- return -1;
- else if (path > url)
- return 1;
- else
- return 0;
+ /**
+ *
+ * @param {DirEntry} dirEntry
+ * @param {callbackDirEntry} callback
+ */
+ ZIMArchive.prototype.resolveRedirect = function (dirEntry, callback) {
+ this._file.dirEntryByUrlIndex(dirEntry.redirectTarget).then(callback);
+ };
+
+ /**
+ * @callback callbackStringContent
+ * @param {String} content String content
+ */
+
+ /**
+ *
+ * @param {DirEntry} dirEntry
+ * @param {callbackStringContent} callback
+ */
+ ZIMArchive.prototype.readUtf8File = function (dirEntry, callback) {
+ dirEntry.readData().then(function (data) {
+ callback(dirEntry, utf8.parse(data));
});
- }).then(function(index) {
- if (index === null) return null;
- return that._file.dirEntryByUrlIndex(index);
- }).then(function(dirEntry) {
- return dirEntry;
- });
- };
+ };
- /**
- *
- * @param {callbackDirEntry} callback
- */
- ZIMArchive.prototype.getRandomDirEntry = function(callback) {
- // Prefer an article-only (v1) title pointer list, if available
- var articleCount = this._file.articleCount || this._file.entryCount;
- var index = Math.floor(Math.random() * articleCount);
- this._file.dirEntryByTitleIndex(index).then(callback);
- };
+ /**
+ * @callback callbackBinaryContent
+ * @param {Uint8Array} content binary content
+ */
- /**
- * Read a Metadata string inside the ZIM file.
- * @param {String} key
- * @param {callbackMetadata} callback
- */
- ZIMArchive.prototype.getMetadata = function (key, callback) {
- var that = this;
- this.getDirEntryByPath("M/" + key).then(function (dirEntry) {
- if (dirEntry === null || dirEntry === undefined) {
- console.warn("Title M/" + key + " not found in the archive");
- callback();
- } else {
- that.readUtf8File(dirEntry, function (dirEntryRead, data) {
- callback(data);
+ /**
+ * Read a binary file.
+ * @param {DirEntry} dirEntry
+ * @param {callbackBinaryContent} callback
+ */
+ ZIMArchive.prototype.readBinaryFile = function (dirEntry, callback) {
+ return dirEntry.readData().then(function (data) {
+ callback(dirEntry, data);
+ });
+ };
+
+ /**
+ * Searches the URL pointer list of Directory Entries by pathname
+ * @param {String} path The pathname of the DirEntry that is required (namespace + filename)
+ * @return {Promise} A Promise that resolves to a Directory Entry, or null if not found.
+ */
+ ZIMArchive.prototype.getDirEntryByPath = function (path) {
+ var that = this;
+ return util.binarySearch(0, this._file.entryCount, function (i) {
+ return that._file.dirEntryByUrlIndex(i).then(function (dirEntry) {
+ var url = dirEntry.namespace + '/' + dirEntry.url;
+ if (path < url) {
+ return -1;
+ } else if (path > url) {
+ return 1;
+ } else {
+ return 0;
+ }
});
- }
- }).catch(function (e) {
- console.warn("Metadata with key " + key + " not found in the archive", e);
- callback();
- });
- };
+ }).then(function (index) {
+ if (index === null) return null;
+ return that._file.dirEntryByUrlIndex(index);
+ }).then(function (dirEntry) {
+ return dirEntry;
+ });
+ };
+
+ /**
+ *
+ * @param {callbackDirEntry} callback
+ */
+ ZIMArchive.prototype.getRandomDirEntry = function (callback) {
+ // Prefer an article-only (v1) title pointer list, if available
+ var articleCount = this._file.articleCount || this._file.entryCount;
+ var index = Math.floor(Math.random() * articleCount);
+ this._file.dirEntryByTitleIndex(index).then(callback);
+ };
- /**
- * Functions and classes exposed by this module
- */
- return {
- ZIMArchive: ZIMArchive
- };
-});
+ /**
+ * Read a Metadata string inside the ZIM file.
+ * @param {String} key
+ * @param {callbackMetadata} callback
+ */
+ ZIMArchive.prototype.getMetadata = function (key, callback) {
+ var that = this;
+ this.getDirEntryByPath('M/' + key).then(function (dirEntry) {
+ if (dirEntry === null || dirEntry === undefined) {
+ console.warn('Title M/' + key + ' not found in the archive');
+ callback();
+ } else {
+ that.readUtf8File(dirEntry, function (dirEntryRead, data) {
+ callback(data);
+ });
+ }
+ }).catch(function (e) {
+ console.warn('Metadata with key ' + key + ' not found in the archive', e);
+ callback();
+ });
+ };
+
+ /**
+ * Functions and classes exposed by this module
+ */
+ return {
+ ZIMArchive: ZIMArchive
+ };
+ }
+);
diff --git a/www/js/lib/zimArchiveLoader.js b/www/js/lib/zimArchiveLoader.js
index 3cb1a01f9..577076764 100644
--- a/www/js/lib/zimArchiveLoader.js
+++ b/www/js/lib/zimArchiveLoader.js
@@ -19,73 +19,76 @@
* You should have received a copy of the GNU General Public License
* along with Kiwix (file LICENSE-GPLv3.txt). If not, see
*/
+
'use strict';
+
+/* global define */
+
define(['zimArchive', 'jquery'],
- function(zimArchive, jQuery) {
+ function (zimArchive, jQuery) {
+ /**
+ * Create a ZIMArchive from DeviceStorage location
+ * @param {DeviceStorage} storage
+ * @param {String} path
+ * @param {callbackZIMArchive} callbackReady
+ * @param {callbackZIMArchive} callbackError
+ * @returns {ZIMArchive}
+ */
+ function loadArchiveFromDeviceStorage (storage, path, callbackReady, callbackError) {
+ return new zimArchive.ZIMArchive(storage, path, callbackReady, callbackError);
+ };
+ /**
+ * Create a ZIMArchive from Files
+ * @param {Array.} files
+ * @param {callbackZIMArchive} callbackReady
+ * @param {callbackZIMArchive} callbackError
+ * @returns {ZIMArchive}
+ */
+ function loadArchiveFromFiles (files, callbackReady, callbackError) {
+ if (files.length >= 1) {
+ return new zimArchive.ZIMArchive(files, null, callbackReady, callbackError);
+ }
+ };
+
+ /**
+ * @callback callbackPathList
+ * @param {Array.} directoryList List of directories
+ */
- /**
- * Create a ZIMArchive from DeviceStorage location
- * @param {DeviceStorage} storage
- * @param {String} path
- * @param {callbackZIMArchive} callbackReady
- * @param {callbackZIMArchive} callbackError
- * @returns {ZIMArchive}
- */
- function loadArchiveFromDeviceStorage(storage, path, callbackReady, callbackError) {
- return new zimArchive.ZIMArchive(storage, path, callbackReady, callbackError);
- };
- /**
- * Create a ZIMArchive from Files
- * @param {Array.} files
- * @param {callbackZIMArchive} callbackReady
- * @param {callbackZIMArchive} callbackError
- * @returns {ZIMArchive}
- */
- function loadArchiveFromFiles(files, callbackReady, callbackError) {
- if (files.length >= 1) {
- return new zimArchive.ZIMArchive(files, null, callbackReady, callbackError);
- }
- };
-
- /**
- * @callback callbackPathList
- * @param {Array.} directoryList List of directories
- */
-
-
- /**
- * Scans the DeviceStorage for archives
- *
- * @param {Array.} storages List of DeviceStorage instances
- * @param {callbackPathList} callbackFunction Function to call with the list of directories where archives are found
- * @param {callbackPathList} callbackError Function to call in case of an error
- */
- function scanForArchives(storages, callbackFunction, callbackError) {
- var directories = [];
- var promises = jQuery.map(storages, function(storage) {
- return storage.scanForArchives()
- .then(function(dirs) {
- jQuery.merge(directories, dirs);
- return true;
- });
- });
- jQuery.when.apply(null, promises).then(function() {
- callbackFunction(directories);
- }).catch(function (error) {
- callbackError("Error scanning your device storage : " + error
- + ". If you're using the Firefox OS Simulator, please put the archives in "
- + "a 'fake-sdcard' directory inside your Firefox profile "
- + "(ex : ~/.mozilla/firefox/xxxx.default/extensions/fxos_2_x_simulator@mozilla.org/"
- + "profile/fake-sdcard/wikipedia_en_ray_charles_2015-06.zim)", "Error reading Device Storage");
- });
- };
+ /**
+ * Scans the DeviceStorage for archives
+ *
+ * @param {Array.} storages List of DeviceStorage instances
+ * @param {callbackPathList} callbackFunction Function to call with the list of directories where archives are found
+ * @param {callbackPathList} callbackError Function to call in case of an error
+ */
+ function scanForArchives (storages, callbackFunction, callbackError) {
+ var directories = [];
+ var promises = jQuery.map(storages, function (storage) {
+ return storage.scanForArchives()
+ .then(function (dirs) {
+ jQuery.merge(directories, dirs);
+ return true;
+ });
+ });
+ jQuery.when.apply(null, promises).then(function () {
+ callbackFunction(directories);
+ }).catch(function (error) {
+ callbackError('Error scanning your device storage : ' + error +
+ ". If you're using the Firefox OS Simulator, please put the archives in " +
+ "a 'fake-sdcard' directory inside your Firefox profile " +
+ '(ex : ~/.mozilla/firefox/xxxx.default/extensions/fxos_2_x_simulator@mozilla.org/' +
+ 'profile/fake-sdcard/wikipedia_en_ray_charles_2015-06.zim)', 'Error reading Device Storage');
+ });
+ };
- /**
- * Functions and classes exposed by this module
- */
- return {
- loadArchiveFromDeviceStorage: loadArchiveFromDeviceStorage,
- loadArchiveFromFiles: loadArchiveFromFiles,
- scanForArchives: scanForArchives
- };
-});
+ /**
+ * Functions and classes exposed by this module
+ */
+ return {
+ loadArchiveFromDeviceStorage: loadArchiveFromDeviceStorage,
+ loadArchiveFromFiles: loadArchiveFromFiles,
+ scanForArchives: scanForArchives
+ };
+ }
+);
diff --git a/www/js/lib/zimDirEntry.js b/www/js/lib/zimDirEntry.js
index aff3233af..ee1aa2705 100644
--- a/www/js/lib/zimDirEntry.js
+++ b/www/js/lib/zimDirEntry.js
@@ -19,33 +19,36 @@
* You should have received a copy of the GNU General Public License
* along with Kiwix (file LICENSE-GPLv3.txt). If not, see
*/
+
'use strict';
-define([], function() {
-
+
+/* global define */
+
+define([], function () {
/**
* A Directory Entry in a ZIM File
- *
+ *
* See https://wiki.openzim.org/wiki/ZIM_file_format#Directory_Entries
- *
+ *
* @typedef DirEntry
* @property {File} _zimfile The ZIM file
* @property {Boolean} redirect
* @property {Integer} offset
* @property {Integer} mimetypeInteger MIME type number as defined in the MIME type list
- * @property {String} namespace defines to which namespace this directory entry belongs
+ * @property {String} namespace defines to which namespace this directory entry belongs
* @property {Integer} redirectTarget
- * @property {Integer} cluster cluster number in which the data of this directory entry is stored
- * @property {Integer} blob blob number inside the compressed cluster where the contents are stored
+ * @property {Integer} cluster cluster number in which the data of this directory entry is stored
+ * @property {Integer} blob blob number inside the compressed cluster where the contents are stored
* @property {String} url string with the URL as refered in the URL pointer list
* @property {String} title string with a title as refered in the Title pointer list (or empty)
- *
+ *
*/
-
+
/**
* @param {File} zimfile
* @param {unresolved} dirEntryData
- */
- function DirEntry(zimfile, dirEntryData) {
+ */
+ function DirEntry (zimfile, dirEntryData) {
this._zimfile = zimfile;
this.redirect = dirEntryData.redirect;
this.offset = dirEntryData.offset;
@@ -61,40 +64,40 @@ define([], function() {
/**
* Serialize some attributes of a DirEntry, to be able to store them in a HTML tag attribute,
* and retrieve them later.
- *
+ *
* @returns {String}
*/
- DirEntry.prototype.toStringId = function() {
- //@todo also store isRedirect and redirectTarget
+ DirEntry.prototype.toStringId = function () {
+ // @todo also store isRedirect and redirectTarget
return this.offset + '|' + this.mimetypeInteger + '|' + this.namespace + '|' + this.cluster + '|' +
this.blob + '|' + this.url + '|' + this.title + '|' + this.redirect + '|' + this.redirectTarget;
};
-
+
/**
- *
+ *
* @returns {Boolean}
*/
- DirEntry.prototype.isRedirect = function() {
+ DirEntry.prototype.isRedirect = function () {
return this.redirect;
};
-
+
/**
- *
+ *
* @returns {Promise}
*/
- DirEntry.prototype.readData = function() {
+ DirEntry.prototype.readData = function () {
return this._zimfile.blob(this.cluster, this.blob);
};
/**
- *
+ *
* @param {File} zimfile
* @param {String} stringId
* @returns {DirEntry}
*/
- DirEntry.fromStringId = function(zimfile, stringId) {
+ DirEntry.fromStringId = function (zimfile, stringId) {
var data = {};
- var idParts = stringId.split("|");
+ var idParts = stringId.split('|');
data.offset = parseInt(idParts[0], 10);
data.mimetypeInteger = parseInt(idParts[1], 10);
data.namespace = idParts[2];
@@ -102,7 +105,7 @@ define([], function() {
data.blob = parseInt(idParts[4], 10);
data.url = idParts[5];
data.title = idParts[6];
- data.redirect = ( idParts[7] === "true" );
+ data.redirect = (idParts[7] === 'true');
data.redirectTarget = idParts[8];
return new DirEntry(zimfile, data);
};
@@ -110,19 +113,19 @@ define([], function() {
/**
* Defines a function that returns the URL if the title is empty, as per the specification
* See https://wiki.openzim.org/wiki/ZIM_file_format#Directory_Entries
- *
- * @returns {String} The dirEntry's title or, if empty, the dirEntry's (unescaped) URL
+ *
+ * @returns {String} The dirEntry's title or, if empty, the dirEntry's (unescaped) URL
*/
- DirEntry.prototype.getTitleOrUrl = function() {
+ DirEntry.prototype.getTitleOrUrl = function () {
return this.title ? this.title : this.url;
};
-
+
/**
* Looks up the dirEntry's mimetype number in the ZIM file's MIME type list, and returns the corresponding MIME type
- *
+ *
* @return {String} The MIME type corresponding to mimetypeInteger in the ZIM file's MIME type list
*/
- DirEntry.prototype.getMimetype = function() {
+ DirEntry.prototype.getMimetype = function () {
return this._zimfile.mimeTypes.get(this.mimetypeInteger);
};
diff --git a/www/js/lib/zimfile.js b/www/js/lib/zimfile.js
index 582088bb8..11f5f535d 100644
--- a/www/js/lib/zimfile.js
+++ b/www/js/lib/zimfile.js
@@ -21,6 +21,8 @@
*/
'use strict';
+/* global define, params */
+
/**
* This code makes an assumption that no Directory Entry will be larger that MAX_SUPPORTED_DIRENTRY_SIZE bytes.
* If a larger dirEntry is encountered, a warning will display in console. Increase this value if necessary.
@@ -37,8 +39,8 @@ const MAX_SUPPORTED_DIRENTRY_SIZE = 5120;
*/
if (!String.prototype.startsWith) {
Object.defineProperty(String.prototype, 'startsWith', {
- value: function(search, rawPos) {
- var pos = rawPos > 0 ? rawPos|0 : 0;
+ value: function (search, rawPos) {
+ var pos = rawPos > 0 ? rawPos | 0 : 0;
return this.substring(pos, pos + search.length) === search;
}
});
@@ -58,12 +60,11 @@ params.decompressorAPI = {
errorStatus: null
};
-define(['xzdec_wrapper', 'zstddec_wrapper', 'util', 'utf8', 'zimDirEntry', 'filecache'], function(xz, zstd, util, utf8, zimDirEntry, FileCache) {
-
+define(['xzdec_wrapper', 'zstddec_wrapper', 'util', 'utf8', 'zimDirEntry', 'filecache'], function (xz, zstd, util, utf8, zimDirEntry, FileCache) {
/**
* A variable to keep track of the currently loaded ZIM archive, e.g., for labelling cache entries
* The ID is temporary and is reset to 0 at each session start; it is incremented by 1 each time a new ZIM is loaded
- * @type {Integer}
+ * @type {Integer}
*/
var tempFileId = 0;
@@ -81,13 +82,13 @@ define(['xzdec_wrapper', 'zstddec_wrapper', 'util', 'utf8', 'zimDirEntry', 'file
}
return r;
};
-
+
/**
* A ZIM File
- *
+ *
* See https://wiki.openzim.org/wiki/ZIM_file_format#Header
* Some properties below are extended and are not part of the official OpenZIM specification
- *
+ *
* @typedef {Object} ZIMFile
* @property {Array} _files Array of ZIM files
* @property {String} name Abstract archive name for file set
@@ -107,12 +108,12 @@ define(['xzdec_wrapper', 'zstddec_wrapper', 'util', 'utf8', 'zimDirEntry', 'file
* @property {String} zimType Extended property: currently either 'open' for OpenZIM file type, or 'zimit' for the warc2zim file type used by Zimit (set in zimArchive.js)
* @property {Map} mimeTypes Extended property: the ZIM file's MIME type table rendered as a Map (calculated entry)
*/
-
+
/**
* Abstract an array of one or more (split) ZIM archives
* @param {Array} abstractFileArray An array of ZIM file parts
*/
- function ZIMFile(abstractFileArray) {
+ function ZIMFile (abstractFileArray) {
this._files = abstractFileArray;
}
@@ -120,7 +121,7 @@ define(['xzdec_wrapper', 'zstddec_wrapper', 'util', 'utf8', 'zimDirEntry', 'file
* Read and decode an integer value from the ZIM archive
* @param {Integer} offset The offset at which the integer is found
* @param {Integer} size The size of data to read
- * @returns {Promise} A Promise for the returned value
+ * @returns {Promise} A Promise for the returned value
*/
ZIMFile.prototype._readInteger = function (offset, size) {
return this._readSlice(offset, size).then(function (data) {
@@ -134,15 +135,15 @@ define(['xzdec_wrapper', 'zstddec_wrapper', 'util', 'utf8', 'zimDirEntry', 'file
* @param {Integer} size The number of bytes to read
* @returns {Promise} A Promise for a Uint8Array containing the requested data
*/
- ZIMFile.prototype._readSlice = function(offset, size) {
+ ZIMFile.prototype._readSlice = function (offset, size) {
return FileCache.read(this, offset, offset + size);
};
/**
* Read a slice from a set of one or more ZIM files constituting a single archive, and concatenate the data parts
- * @param {Integer} begin The absolute byte offset from which to start reading
+ * @param {Integer} begin The absolute byte offset from which to start reading
* @param {Integer} end The absolute byte offset where reading should stop (the end byte is not read)
- * @returns {Promise} A Promise for a Uint8Array containing the concatenated data
+ * @returns {Promise} A Promise for a Uint8Array containing the concatenated data
*/
ZIMFile.prototype._readSplitSlice = function (begin, end) {
var file = this;
@@ -308,14 +309,14 @@ define(['xzdec_wrapper', 'zstddec_wrapper', 'util', 'utf8', 'zimDirEntry', 'file
* @typedef {Object} DirListing A list of pointers to directory entries (via the URL pointerlist)
* @property {String} path The path (url) to the directory entry for the Listing
* @property {String} ptrName The name of the pointer to the Listing's data that will be added to the ZIMFile obect
- * @property {String} countName The name of the key that will contain the number of entries in the Listing, to be added to the ZIMFile object
+ * @property {String} countName The name of the key that will contain the number of entries in the Listing, to be added to the ZIMFile object
*/
/**
* Read the metadata (archive offset pointer, and number of entiries) of one or more ZIM directory Listings.
* This supports reading a subset of user content that might be ordered differently from the main URL pointerlist.
* In particular, it supports the v1 article pointerlist, which contains articles sorted by title, superseding the article
- * namespace ('A') in legazy ZIM archives.
+ * namespace ('A') in legazy ZIM archives.
* @param {Array} listings An array of DirListing objects (see zimArchive.js for examples)
* @returns {Promise} A promise that populates calculated entries in the ZIM file header
*/
@@ -327,8 +328,8 @@ define(['xzdec_wrapper', 'zstddec_wrapper', 'util', 'utf8', 'zimDirEntry', 'file
// console.debug('ZIM DirListing version: 0 (legacy)', this);
// Initiate a binary search for the first or last article
var getArticleIndexByOrdinal = function (ordinal) {
- return util.binarySearch(0, that.entryCount, function(i) {
- return that.dirEntryByTitleIndex(i).then(function(dirEntry) {
+ return util.binarySearch(0, that.entryCount, function (i) {
+ return that.dirEntryByTitleIndex(i).then(function (dirEntry) {
var ns = dirEntry.namespace;
var url = ns + '/' + dirEntry.getTitleOrUrl();
var prefix = ordinal === 'first' ? 'A' : 'B';
@@ -336,12 +337,12 @@ define(['xzdec_wrapper', 'zstddec_wrapper', 'util', 'utf8', 'zimDirEntry', 'file
else if (prefix > ns) return 1;
return prefix < url ? -1 : 1;
});
- }, true).then(function(index) {
+ }, true).then(function (index) {
return index;
});
};
- getArticleIndexByOrdinal('first').then(function(idxFirstArticle) {
- return getArticleIndexByOrdinal('last').then(function(idxLastArticle) {
+ getArticleIndexByOrdinal('first').then(function (idxFirstArticle) {
+ return getArticleIndexByOrdinal('last').then(function (idxLastArticle) {
// Technically idxLastArticle points to the entry after the last article in the 'A' namespace,
// We subtract the first from the last to get the number of entries in the 'A' namespace
that.articlePtrPos = that.titlePtrPos + idxFirstArticle * 4;
@@ -366,20 +367,21 @@ define(['xzdec_wrapper', 'zstddec_wrapper', 'util', 'utf8', 'zimDirEntry', 'file
return listingAccessor(listings.pop());
}
// Initiate a binary search for the listing URL
- return util.binarySearch(0, that.entryCount, function(i) {
- return that.dirEntryByUrlIndex(i).then(function(dirEntry) {
- var url = dirEntry.namespace + "/" + dirEntry.url;
- if (listing.path < url)
+ return util.binarySearch(0, that.entryCount, function (i) {
+ return that.dirEntryByUrlIndex(i).then(function (dirEntry) {
+ var url = dirEntry.namespace + '/' + dirEntry.url;
+ if (listing.path < url) {
return -1;
- else if (listing.path > url)
+ } else if (listing.path > url) {
return 1;
- else
+ } else {
return 0;
+ }
});
- }).then(function(index) {
+ }).then(function (index) {
if (index === null) return null;
return that.dirEntryByUrlIndex(index);
- }).then(function(dirEntry) {
+ }).then(function (dirEntry) {
if (!dirEntry) return null;
// Detect a full text index
if (/fulltext\//.test(dirEntry.url)) {
@@ -387,7 +389,7 @@ define(['xzdec_wrapper', 'zstddec_wrapper', 'util', 'utf8', 'zimDirEntry', 'file
}
// Request the metadata for the blob represented by the dirEntry
return that.blob(dirEntry.cluster, dirEntry.blob, true);
- }).then(function(metadata) {
+ }).then(function (metadata) {
// Note that we do not accept a listing if its size is 0, i.e. if it contains no data
// (although this should not occur, we have been asked to handle it - see kiwix-js #708)
if (metadata && metadata.size) {
@@ -397,25 +399,25 @@ define(['xzdec_wrapper', 'zstddec_wrapper', 'util', 'utf8', 'zimDirEntry', 'file
}
// Get the next Listing
return listingAccessor(listings.pop());
- }).catch(function(err) {
+ }).catch(function (err) {
console.error('There was an error accessing a Directory Listing', err);
});
};
return listingAccessor(listings.pop());
- };
+ };
/**
* Reads the whole MIME type list and returns it as a populated Map
* The mimeTypeMap is extracted once after the user has picked the ZIM file
* and is stored as ZIMFile.mimeTypes
- * @param {File} file The ZIM file (or first file in array of files) from which the MIME type list
+ * @param {File} file The ZIM file (or first file in array of files) from which the MIME type list
* is to be extracted
* @param {Integer} mimeListPos The offset in at which the MIME type list is found
* @param {Integer} urlPtrPos The offset of URL Pointer List in the archive
* @returns {Promise} A promise for the MIME Type list as a Map
*/
- function readMimetypeMap(file, mimeListPos, urlPtrPos) {
- var typeMap = new Map;
+ function readMimetypeMap (file, mimeListPos, urlPtrPos) {
+ var typeMap = new Map();
var size = urlPtrPos - mimeListPos;
// ZIM archives produced since May 2020 relocate the URL Pointer List to the end of the archive
// so we limit the slice size to max 1024 bytes in order to prevent reading the entire archive into an array buffer
@@ -429,7 +431,7 @@ define(['xzdec_wrapper', 'zstddec_wrapper', 'util', 'utf8', 'zimDirEntry', 'file
while (pos < size) {
pos++;
mimeString = utf8.parse(data.subarray(pos), true);
- // If the parsed data is an empty string, we have reached the end of the MIME type list, so break
+ // If the parsed data is an empty string, we have reached the end of the MIME type list, so break
if (!mimeString) break;
// Store the parsed string in the Map
typeMap.set(i, mimeString);
@@ -442,7 +444,7 @@ define(['xzdec_wrapper', 'zstddec_wrapper', 'util', 'utf8', 'zimDirEntry', 'file
return typeMap;
}).catch(function (err) {
console.error('Unable to read MIME type list', err);
- return new Map;
+ return new Map();
});
}
@@ -477,13 +479,13 @@ define(['xzdec_wrapper', 'zstddec_wrapper', 'util', 'utf8', 'zimDirEntry', 'file
zf.majorVersion = readInt(header, 4, 2); // Not currently used by this implementation
zf.minorVersion = readInt(header, 6, 2); // Used to determine the User Content namespace
zf.entryCount = readInt(header, 24, 4);
- zf.articleCount = null; // Calculated async by setListings() called from zimArchive.js
+ zf.articleCount = null; // Calculated async by setListings() called from zimArchive.js
zf.clusterCount = readInt(header, 28, 4);
zf.urlPtrPos = urlPtrPos;
zf.titlePtrPos = readInt(header, 40, 8);
zf.articlePtrPos = null; // Calculated async by setListings()
zf.fullTextIndex = null; // Calculated async by setListings()
- zf.fullTextIndexSize = null; // Calbulated async by setListings()
+ zf.fullTextIndexSize = null; // Calbulated async by setListings()
zf.clusterPtrPos = readInt(header, 48, 8);
zf.mimeListPos = mimeListPos;
zf.mainPage = readInt(header, 64, 4);
diff --git a/www/js/lib/zstddec_wrapper.js b/www/js/lib/zstddec_wrapper.js
index 3c057c073..905774020 100644
--- a/www/js/lib/zstddec_wrapper.js
+++ b/www/js/lib/zstddec_wrapper.js
@@ -1,7 +1,7 @@
/**
* zstddec_wrapper.js: Javascript wrapper around compiled zstd decompressor.
*
- * Copyright 2020 Jaifroid, Mossroy and contributors
+ * Copyright 2023 Jaifroid, Mossroy and contributors
* License GPL v3:
*
* This file is part of Kiwix.
@@ -19,8 +19,12 @@
* You should have received a copy of the GNU General Public License
* along with Kiwix (file LICENSE-GPLv3.txt). If not, see
*/
+
'use strict';
+/* global define, params, ZD */
+/* eslint-disable no-multi-spaces */
+
// DEV: Put your RequireJS definition in the rqDefZD array below, and any function exports in the function parenthesis of the define statement
// We need to do it this way in order to load the wasm or asm versions of zstddec conditionally. Older browsers can only use the asm version
// because they cannot interpret WebAssembly.
@@ -40,7 +44,7 @@ if ('WebAssembly' in self) {
rqDefZD.push('zstddec-asm');
}
-define(rqDefZD, function(uiUtil) {
+define(rqDefZD, function (uiUtil) {
// DEV: zstddec.js has been compiled with `-s EXPORT_NAME="ZD" -s MODULARIZE=1` to avoid a clash with xzdec.js
// Note that we include zstddec-wasm or zstddec-asm above in requireJS definition, but we cannot change the name in the function list
// For explanation of loading method below to avoid conflicts, see https://github.com/emscripten-core/emscripten/blob/master/src/settings.js
@@ -68,7 +72,7 @@ define(rqDefZD, function(uiUtil) {
// Get a permanent decoder handle (pointer to control structure)
// NB there is no need to change this handle even between ZIM loads: zstddeclib encourages re-using assigned structures
zd._decHandle = zd._ZSTD_createDStream();
- // In-built function below provides a max recommended chunk size
+ // In-built function below provides a max recommended chunk size
zd._chunkSize = zd._ZSTD_DStreamInSize();
// Change _chunkSize if you need a more conservative memory environment, but you may need to experiment with INITIAL_MEMORY
// in zstddec.js (see below) for this to make any difference
@@ -142,7 +146,7 @@ define(rqDefZD, function(uiUtil) {
/**
* @typedef Decompressor
* @property {FileReader} _reader The filereader to use (uses plain blob reader defined in zimfile.js)
- * @property {Integer} _inStreamPos The current known position in the steam of compressed bytes
+ * @property {Integer} _inStreamPos The current known position in the steam of compressed bytes
* @property {Integer} _inStreamChunkedPos The position once the currently loaded chunk will have been consumed
* @property {Integer} _outStreamPos The position in the decoded byte stream (offset from start of cluster)
* @property {Array} _outDataBuf The buffer that stores decoded bytes (it is set to the requested blob's length, and when full, the data are returned)
@@ -153,7 +157,7 @@ define(rqDefZD, function(uiUtil) {
* @constructor
* @param {FileReader} reader The reader used to extract file slices (defined in zimfile.js)
*/
- function Decompressor(reader) {
+ function Decompressor (reader) {
params.decompressorAPI.decompressorLastUsed = 'ZSTD';
this._reader = reader;
}
@@ -174,7 +178,7 @@ define(rqDefZD, function(uiUtil) {
this._outDataBufPos = 0;
var ret = zd._ZSTD_initDStream(zd._decHandle);
if (zd._ZSTD_isError(ret)) {
- return Promise.reject('Failed to initialize ZSTD decompression');
+ return Promise.reject(new Error('Failed to initialize ZSTD decompression'));
}
return this._readLoop(offset, length).then(function (data) {
@@ -225,7 +229,7 @@ define(rqDefZD, function(uiUtil) {
var finished = false;
var ret = zd._ZSTD_decompressStream(zd._decHandle, zd._outBuffer.ptr, zd._inBuffer.ptr);
if (zd._ZSTD_isError(ret)) {
- var errorMessage = "Failed to decompress data stream!\n" + zd.getErrorString(ret);
+ var errorMessage = 'Failed to decompress data stream!\n' + zd.getErrorString(ret);
return Promise.reject(errorMessage);
}
// Get updated outbuffer values
@@ -236,8 +240,9 @@ define(rqDefZD, function(uiUtil) {
if (outPos > 0 && that._outStreamPos + outPos >= offset) {
var copyStart = offset - that._outStreamPos;
if (copyStart < 0) copyStart = 0;
- for (var i = copyStart; i < outPos && that._outDataBufPos < that._outDataBuf.length; i++)
+ for (var i = copyStart; i < outPos && that._outDataBufPos < that._outDataBuf.length; i++) {
that._outDataBuf[that._outDataBufPos++] = zd.HEAP8[zd._outBuffer.dst + i];
+ }
}
if (that._outDataBufPos === that._outDataBuf.length) finished = true;
// Return without further processing if decompressor has finished
@@ -293,7 +298,7 @@ define(rqDefZD, function(uiUtil) {
* @param {Integer} sizeOfData The number of bytes to be allocated
* @returns {Integer} Pointer to the assigned data block
*/
- function mallocOrDie(sizeOfData) {
+ function mallocOrDie (sizeOfData) {
const dataPointer = zd._malloc(sizeOfData);
if (dataPointer === 0) { // error allocating memory
var errorMessage = 'Failed allocation of ' + sizeOfData + ' bytes.';
@@ -306,4 +311,4 @@ define(rqDefZD, function(uiUtil) {
return {
Decompressor: Decompressor
};
-});
\ No newline at end of file
+});