Implement WASM with fallback (#174)

Former-commit-id: c68596bc74e187de64dc2e39e43eadcf1f9ea32f [formerly fa761722a44f952ce2dd17b8c9944b2b4f83adbf] [formerly f3cc4ecfb825af85e764ab89d1b350cdaca4b5e2] [formerly 2ce10066e6d7b53aa1390d7e75189f18c7b59b8a [formerly e7fe66c76d904f511bfa246c059dade2d12911b6 [formerly 40d3b58c2cf6821c94cb138f86d45c498698beed]]]
Former-commit-id: 375cfb19d2c3ca7fcda82adff21837dcaecc2e82 [formerly a9650d1a4d00e1bc0ad8c82cb360c7c79cae2173 [formerly 7d6ea7d5e4332e09c8aa04e00d50c402a6ae0d16]]
Former-commit-id: cba19c61686b992d81f8736997ed56ad669ea3cd [formerly 34f89f43b796b52dc5ff580d4f481a7d1b5ef7fc]
Former-commit-id: a769646441e4eb51a6ee4485f594d46a98d1ce93
This commit is contained in:
Jaifroid 2021-07-22 22:19:54 +01:00 committed by GitHub
parent 3bee578801
commit 9728d41fc9
19 changed files with 660 additions and 406 deletions

43
.gitattributes vendored Normal file
View File

@ -0,0 +1,43 @@
# Auto detect text files and perform LF normalization
* text eol=lf
# Custom for Visual Studio
*.cs diff=csharp
*.sln merge=union
*.csproj merge=union
*.vbproj merge=union
*.fsproj merge=union
*.dbproj merge=union
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
*.png binary
*.jpeg binary
*.jpg binary
*.gif binary
*.dat binary
*.idx binary
*.zip binary
*.gz binary
*.odt binary
*.ods binary
*.odp binary
*.ttf binary
*.zim binary
*.zim* binary
*.eot binary
*.svg binary
*.woff binary
*.woff2 binary
*.deb binary
*.wasm binary

View File

@ -165,7 +165,12 @@
<Content Include="www\js\lib\arrayFromPolyfill.js" />
<Content Include="www\js\lib\promisePolyfill.js" />
<Content Include="www\js\lib\webpHeroBundle_0.0.0-dev.27.js" />
<Content Include="www\js\lib\zstddec.js" />
<Content Include="www\js\lib\xzdec-asm.js" />
<Content Include="www\js\lib\xzdec-wasm.js" />
<Content Include="www\js\lib\xzdec-wasm.wasm" />
<Content Include="www\js\lib\zstddec-asm.js" />
<Content Include="www\js\lib\zstddec-wasm.js" />
<Content Include="www\js\lib\zstddec-wasm.wasm" />
<Content Include="www\js\lib\zstddec_wrapper.js" />
<None Include="Package.StoreAssociation.xml" />
<None Include="kiwix.pfx" />
@ -316,7 +321,6 @@
<Content Include="www\js\lib\uiUtil.js" />
<Content Include="www\js\lib\utf8.js" />
<Content Include="www\js\lib\util.js" />
<Content Include="www\js\lib\xzdec.js" />
<Content Include="www\js\lib\xzdec_wrapper.js" />
<Content Include="www\js\lib\zimArchive.js" />
<Content Include="www\js\lib\zimArchiveLoader.js" />

View File

@ -15,7 +15,7 @@ const regexpKiwixDownloadLinks = /download\.kiwix\.org/i;
const regexpZIMUrlWithNamespace = /(?:^|\/)([^\/]+\/)([-ABCIJMUVWX])\/(.+)/;
const CACHE = "kiwix-precache-" + appVersion;
const precacheFiles = [
let precacheFiles = [
".",
"www",
"www/",
@ -68,16 +68,14 @@ const precacheFiles = [
"www/js/lib/images.js",
"www/js/lib/jquery-3.2.1.slim.js",
"www/js/lib/kiwixServe.js",
"www/js/lib/promisPolyfill.js",
"www/js/lib/promisePolyfill.js",
"www/js/lib/require.js",
"www/js/lib/settingsStore.js",
"www/js/lib/transformStyles.js",
"www/js/lib/uiUtil.js",
"www/js/lib/utf8.js",
"www/js/lib/util.js",
"www/js/lib/xzdec.js",
"www/js/lib/xzdec_wrapper.js",
"www/js/lib/zstddec.js",
"www/js/lib/zstddec_wrapper.js",
"www/js/lib/zimArchive.js",
"www/js/lib/zimArchiveLoader.js",
@ -95,6 +93,20 @@ const precacheFiles = [
"www/js/katex/fonts/KaTeX_Size4-Regular.woff2"
];
if ('WebAssembly' in self) {
precacheFiles.push(
"www/js/lib/xzdec-wasm.js",
"www/js/lib/xzdec-wasm.wasm",
"www/js/lib/zstddec-wasm.js",
"www/js/lib/zstddec-wasm.wasm"
);
} else {
precacheFiles.push(
"www/js/lib/xzdec-asm.js",
"www/js/lib/zstddec-asm.js"
);
}
// DEV: add any URL schemata that should be excluded from caching with the Cache API to the regex below
// As of 08-2019 the chrome-extension: schema is incompatible with the Cache API
// 'example-extension' is included to show how to add another schema if necessary

View File

@ -433,13 +433,21 @@ require.config({
}
});
requirejs(['bootstrap', 'promisePolyfill', 'arrayFromPolyfill'], function () {
var req = ['bootstrap']; // Baseline Require array
// Add polyfills to the Require array only if needed
if (!('Promise' in self)) req.push('promisePolyfill');
if (!('from' in Array)) req.push('arrayFromPolyfill');
requirejs(req, function () {
requirejs(['../app']);
});
// Load the WebP Polyfills only if needed
// Test if WebP is natively supported, and if not, set webpMachine to true. The value of webpMachine
// will determine whether the WebP Polyfills will be loaded (currently only used in uiUtil.js)
var webpMachine = false;
// Using self-invoking function to avoid defining global functions and variables
// We use a self-invoking function here to avoid defining unnecessary global functions and variables
(function (callback) {
// Tests for native WebP support
var webP = new Image();
@ -451,4 +459,4 @@ var webpMachine = false;
if (!support) {
webpMachine = true;
}
});
});

View File

@ -1,105 +1,107 @@
/**
* abstractFilesystemAccess.js: Abstraction layer for file access.
* This is currently only implemented for FirefoxOS, but could be extended to
* Cordova, Electron or other ways to directly browse and read files from the
* 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 <http://www.gnu.org/licenses/>
*/
'use strict';
define(['jquery'], function(jQuery) {
/**
* 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) {
this._storage = storage;
this.storageName = storage.storageName;
};
/**
* Access the given file.
* @param {String} path absolute path to the file
* @return {Promise} Promise which is resolved with a HTML5 file object and
* rejected with an error message.
*/
StorageFirefoxOS.prototype.get = function(path) {
var deferred = q.defer();
var request = this._storage.get(path);
request.onsuccess = function() { deferred.resolve(this.result); };
request.onerror = function() { deferred.reject(this.error.name); };
return deferred.promise;
};
// 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() {
var deferred = jQuery.Deferred();
var directories = [];
var cursor = this._storage.enumerate();
cursor.onerror = function() {
deferred.reject(cursor.error);
};
cursor.onsuccess = function() {
if (!cursor.result) {
deferred.resolve(directories);
return;
}
var file = cursor.result;
if (regexpZIMFileName.test(file.name)) {
directories.push(file.name);
}
cursor.continue();
};
return deferred.promise();
};
/**
* 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) {
return this._storage.enumerate();
};
return {
StorageFirefoxOS: StorageFirefoxOS
};
});
/**
* abstractFilesystemAccess.js: Abstraction layer for file access.
* This is currently only implemented for FirefoxOS, but could be extended to
* Cordova, Electron or other ways to directly browse and read files from the
* 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 <http://www.gnu.org/licenses/>
*/
'use strict';
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) {
this._storage = storage;
this.storageName = storage.storageName;
};
/**
* Access the given file.
* @param {String} path absolute path to the file
* @return {Promise} Promise which is resolved with a HTML5 file object and
* rejected with an error message.
*/
StorageFirefoxOS.prototype.get = function(path) {
var that = this;
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); };
});
};
// 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() {
var that = this;
return new Promise(function (resolve, reject){
var directories = [];
var cursor = that._storage.enumerate();
cursor.onerror = function() {
reject(cursor.error);
};
cursor.onsuccess = function() {
if (!cursor.result) {
resolve(directories);
return;
}
var file = cursor.result;
if (regexpZIMFileName.test(file.name)) {
directories.push(file.name);
}
cursor.continue();
};
});
};
/**
* 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) {
return this._storage.enumerate();
};
return {
StorageFirefoxOS: StorageFirefoxOS
};
});

View File

@ -1,9 +1,7 @@
/**
* Simple Array.from polyfi8ll (with Set support) from https://stackoverflow.com/a/62682524/9727685
* Simple Array.from polyfill (with Set support) from https://stackoverflow.com/a/62682524/9727685
*/
(function () {
// Detection
if ('from' in Array) return;
function arrayFrom(arr, callbackFn, thisArg) {
//if you need you can uncomment the following line
@ -31,5 +29,5 @@
return arNew;
}
//You could also use it without the following line, but it is not recommended because native function is faster.
Array.from = arrayFrom; //We set it as polyfill
Array.from = Array.from || arrayFrom; //We set it as polyfill
}());

View File

@ -1,186 +1,186 @@
/**
* filecache.js: Generic cache for small, frequently read file slices.
* It discards cached blocks according to a least-recently-used algorithm.
* It is used primarily for fast Directory Entry lookup, speeding up binary search.
*
* Copyright 2020 Mossroy, peter-x, jaifroid and contributors
* License GPL v3:
*
* This file is part of Kiwix.
*
* Kiwix JS 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 JS 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 JS (file LICENSE). If not, see <http://www.gnu.org/licenses/>
*/
'use strict';
define([], function() {
/**
* Set maximum number of cache blocks of BLOCK_SIZE bytes each
* Maximum size of cache in bytes = MAX_CACHE_SIZE * BLOCK_SIZE
* @constant
* @type {Number}
*/
const MAX_CACHE_SIZE = 4000;
/**
* The maximum blocksize to read or store via the block cache (bytes)
* @constant
* @type {Number}
*/
const BLOCK_SIZE = 4096;
/**
* 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 {Map} cache A map to store the cache keys and data
*/
/**
* 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() {
/** CACHE TUNING **/
// console.log('Creating cache of size ' + MAX_CACHE_SIZE + ' * ' + BLOCK_SIZE + ' bytes');
// Initialize persistent Cache properties
this.capacity = MAX_CACHE_SIZE;
this.cache = new Map();
}
/**
* 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
*/
LRUCache.prototype.get = function (key) {
var entry = this.cache.get(key);
// If the key does not exist, return
if (!entry) return entry;
// Remove the key and re-insert it (this moves the key to the bottom of the Map: bottom = most recent)
this.cache.delete(key);
this.cache.set(key, entry);
// Return the cached data
return entry;
};
/**
* 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
*/
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
// to <value>, but <entry> is strongly referenced by the Map. (It should be rare that two async Promises attempt to
// store the same data in the Cache, once the Cache is sufficiently populated.)
var entry = this.cache.get(key);
// If the key already exists, delete it and re-insert it, so that it will be added
// to the bottom of the Map (bottom = most recent)
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,
// 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) {
var firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
} else {
// IE11 doesn't support the keys iterator, so we have to do a forEach loop through all 4000 entries
// to get the oldest values. To prevent excessive iterations, we delete 25% at a time.
var q = Math.floor(0.25 * this.capacity);
var c = 0;
// console.log('Deleteing ' + q + ' cache entries');
this.cache.forEach(function (v, k, map) {
if (c > q) return;
map.delete(k);
c++;
});
}
}
};
/**
* A new Block Cache
* @type {BlockCache}
*/
var cache = new LRUCache();
/** 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;
/**
* Read a certain byte range in the given file, breaking the range into chunks that go through the cache
* If a read of more than BLOCK_SIZE * 2 (bytes) is requested, do not use the cache
* @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<Uint8Array>} A Promise that resolves to the correctly concatenated data from the cache
* or from the ZIM archive
*/
var read = function(file, begin, end) {
// Read large chunks bypassing the block cache because we would have to
// stitch together too many blocks and would clog the cache
if (end - begin > BLOCK_SIZE * 2) return file._readSplitSlice(begin, end);
var readRequests = [];
var blocks = {};
// Look for the requested data in the blocks: we may need to stitch together data from two or more blocks
for (var id = Math.floor(begin / BLOCK_SIZE) * BLOCK_SIZE; id < end; id += BLOCK_SIZE) {
var block = cache.get(file.id + ':' + id);
if (block === undefined) {
// 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 <id> which then
// becomes the <offset> parameter
readRequests.push(function(offset) {
return file._readSplitSlice(offset, offset + BLOCK_SIZE).then(function(result) {
cache.store(file.id + ':' + offset, result);
blocks[offset] = result;
});
}(id));
} else {
/** CACHE TUNING **/
// hits++;
blocks[id] = block;
}
}
/** CACHE TUNING **/
// if (misses + hits > 2000) {
// 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;
// }
// Wait for all the blocks to be read either from the cache or from the archive
return Promise.all(readRequests).then(function() {
var result = new Uint8Array(end - begin);
var pos = 0;
// Stitch together the data parts in the right order
for (var i = Math.floor(begin / BLOCK_SIZE) * BLOCK_SIZE; i < end; i += BLOCK_SIZE) {
var b = Math.max(i, begin) - i;
var e = Math.min(end, i + BLOCK_SIZE) - i;
if (blocks[i].subarray) result.set(blocks[i].subarray(b, e), pos);
pos += e - b;
}
return result;
});
};
return {
read: read
};
});
/**
* filecache.js: Generic cache for small, frequently read file slices.
* It discards cached blocks according to a least-recently-used algorithm.
* It is used primarily for fast Directory Entry lookup, speeding up binary search.
*
* Copyright 2020 Mossroy, peter-x, jaifroid and contributors
* License GPL v3:
*
* This file is part of Kiwix.
*
* Kiwix JS 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 JS 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 JS (file LICENSE). If not, see <http://www.gnu.org/licenses/>
*/
'use strict';
define([], function() {
/**
* Set maximum number of cache blocks of BLOCK_SIZE bytes each
* Maximum size of cache in bytes = MAX_CACHE_SIZE * BLOCK_SIZE
* @constant
* @type {Number}
*/
const MAX_CACHE_SIZE = 4000;
/**
* The maximum blocksize to read or store via the block cache (bytes)
* @constant
* @type {Number}
*/
const BLOCK_SIZE = 4096;
/**
* 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 {Map} cache A map to store the cache keys and data
*/
/**
* 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() {
/** CACHE TUNING **/
// console.log('Creating cache of size ' + MAX_CACHE_SIZE + ' * ' + BLOCK_SIZE + ' bytes');
// Initialize persistent Cache properties
this.capacity = MAX_CACHE_SIZE;
this.cache = new Map();
}
/**
* 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
*/
LRUCache.prototype.get = function (key) {
var entry = this.cache.get(key);
// If the key does not exist, return
if (!entry) return entry;
// Remove the key and re-insert it (this moves the key to the bottom of the Map: bottom = most recent)
this.cache.delete(key);
this.cache.set(key, entry);
// Return the cached data
return entry;
};
/**
* 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
*/
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
// to <value>, but <entry> is strongly referenced by the Map. (It should be rare that two async Promises attempt to
// store the same data in the Cache, once the Cache is sufficiently populated.)
var entry = this.cache.get(key);
// If the key already exists, delete it and re-insert it, so that it will be added
// to the bottom of the Map (bottom = most recent)
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,
// 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) {
var firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
} else {
// IE11 doesn't support the keys iterator, so we have to do a forEach loop through all 4000 entries
// to get the oldest values. To prevent excessive iterations, we delete 25% at a time.
var q = Math.floor(0.25 * this.capacity);
var c = 0;
// console.log('Deleteing ' + q + ' cache entries');
this.cache.forEach(function (v, k, map) {
if (c > q) return;
map.delete(k);
c++;
});
}
}
};
/**
* A new Block Cache
* @type {BlockCache}
*/
var cache = new LRUCache();
/** 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;
/**
* Read a certain byte range in the given file, breaking the range into chunks that go through the cache
* If a read of more than BLOCK_SIZE * 2 (bytes) is requested, do not use the cache
* @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<Uint8Array>} A Promise that resolves to the correctly concatenated data from the cache
* or from the ZIM archive
*/
var read = function(file, begin, end) {
// Read large chunks bypassing the block cache because we would have to
// stitch together too many blocks and would clog the cache
if (end - begin > BLOCK_SIZE * 2) return file._readSplitSlice(begin, end);
var readRequests = [];
var blocks = {};
// Look for the requested data in the blocks: we may need to stitch together data from two or more blocks
for (var id = Math.floor(begin / BLOCK_SIZE) * BLOCK_SIZE; id < end; id += BLOCK_SIZE) {
var block = cache.get(file.id + ':' + id);
if (block === undefined) {
// 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 <id> which then
// becomes the <offset> parameter
readRequests.push(function(offset) {
return file._readSplitSlice(offset, offset + BLOCK_SIZE).then(function(result) {
cache.store(file.id + ':' + offset, result);
blocks[offset] = result;
});
}(id));
} else {
/** CACHE TUNING **/
// hits++;
blocks[id] = block;
}
}
/** CACHE TUNING **/
// if (misses + hits > 2000) {
// 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;
// }
// Wait for all the blocks to be read either from the cache or from the archive
return Promise.all(readRequests).then(function() {
var result = new Uint8Array(end - begin);
var pos = 0;
// Stitch together the data parts in the right order
for (var i = Math.floor(begin / BLOCK_SIZE) * BLOCK_SIZE; i < end; i += BLOCK_SIZE) {
var b = Math.max(i, begin) - i;
var e = Math.min(end, i + BLOCK_SIZE) - i;
if (blocks[i].subarray) result.set(blocks[i].subarray(b, e), pos);
pos += e - b;
}
return result;
});
};
return {
read: read
};
});

View File

@ -12,8 +12,6 @@
(function () {
'use strict';
if ('Promise' in self) return;
var $undefined
, $null = null
, isBrowser = typeof self === 'object'

54
www/js/lib/xzdec-asm.js Normal file

File diff suppressed because one or more lines are too long

32
www/js/lib/xzdec-wasm.js Normal file
View File

@ -0,0 +1,32 @@

var XZ = (function () {
var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined;
return (
function(XZ) {
XZ = XZ || {};
var a;a||(a=typeof XZ !== 'undefined' ? XZ : {});var e,f;a.ready=new Promise(function(b,c){e=b;f=c});var h={},k;for(k in a)a.hasOwnProperty(k)&&(h[k]=a[k]);var m="";"undefined"!==typeof document&&document.currentScript&&(m=document.currentScript.src);_scriptDir&&(m=_scriptDir);0!==m.indexOf("blob:")?m=m.substr(0,m.lastIndexOf("/")+1):m="";var n=a.printErr||console.warn.bind(console);for(k in h)h.hasOwnProperty(k)&&(a[k]=h[k]);h=null;var p;a.wasmBinary&&(p=a.wasmBinary);
var noExitRuntime=a.noExitRuntime||!0;"object"!==typeof WebAssembly&&q("no native wasm support detected");var r,t=!1,u,v,w=[],x=[],z=[];function A(){var b=a.preRun.shift();w.unshift(b)}var B=0,C=null,D=null;a.preloadedImages={};a.preloadedAudios={};function q(b){if(a.onAbort)a.onAbort(b);n(b);t=!0;b=new WebAssembly.RuntimeError("abort("+b+"). Build with -s ASSERTIONS=1 for more info.");f(b);throw b;}function E(){return F.startsWith("data:application/octet-stream;base64,")}var F;F="xzdec-wasm.wasm";
if(!E()){var G=F;F=a.locateFile?a.locateFile(G,m):m+G}function H(){var b=F;try{if(b==F&&p)return new Uint8Array(p);throw"both async and sync fetching of the wasm failed";}catch(c){q(c)}}function I(){return p||"function"!==typeof fetch?Promise.resolve().then(function(){return H()}):fetch(F,{credentials:"same-origin"}).then(function(b){if(!b.ok)throw"failed to load wasm binary file at '"+F+"'";return b.arrayBuffer()}).catch(function(){return H()})}
function J(b){for(;0<b.length;){var c=b.shift();if("function"==typeof c)c(a);else{var g=c.u;"number"===typeof g?void 0===c.s?v.get(g)():v.get(g)(c.s):g(void 0===c.s?null:c.s)}}}var K={a:function(b,c,g){u.copyWithin(b,c,c+g)},b:function(){q("OOM")}};
(function(){function b(d){a.asm=d.exports;r=a.asm.c;d=r.buffer;a.HEAP8=new Int8Array(d);a.HEAP16=new Int16Array(d);a.HEAP32=new Int32Array(d);a.HEAPU8=u=new Uint8Array(d);a.HEAPU16=new Uint16Array(d);a.HEAPU32=new Uint32Array(d);a.HEAPF32=new Float32Array(d);a.HEAPF64=new Float64Array(d);v=a.asm.o;x.unshift(a.asm.d);B--;a.monitorRunDependencies&&a.monitorRunDependencies(B);0==B&&(null!==C&&(clearInterval(C),C=null),D&&(d=D,D=null,d()))}function c(d){b(d.instance)}function g(d){return I().then(function(l){return WebAssembly.instantiate(l,
y)}).then(d,function(l){n("failed to asynchronously prepare wasm: "+l);q(l)})}var y={a:K};B++;a.monitorRunDependencies&&a.monitorRunDependencies(B);if(a.instantiateWasm)try{return a.instantiateWasm(y,b)}catch(d){return n("Module.instantiateWasm callback failed with error: "+d),!1}(function(){return p||"function"!==typeof WebAssembly.instantiateStreaming||E()||"function"!==typeof fetch?g(c):fetch(F,{credentials:"same-origin"}).then(function(d){return WebAssembly.instantiateStreaming(d,y).then(c,function(l){n("wasm streaming compile failed: "+
l);n("falling back to ArrayBuffer instantiation");return g(c)})})})().catch(f);return{}})();a.___wasm_call_ctors=function(){return(a.___wasm_call_ctors=a.asm.d).apply(null,arguments)};a._init=function(){return(a._init=a.asm.e).apply(null,arguments)};a._init_decompression=function(){return(a._init_decompression=a.asm.f).apply(null,arguments)};a._input_empty=function(){return(a._input_empty=a.asm.g).apply(null,arguments)};a._get_in_buffer=function(){return(a._get_in_buffer=a.asm.h).apply(null,arguments)};
a._set_new_input=function(){return(a._set_new_input=a.asm.i).apply(null,arguments)};a._decompress=function(){return(a._decompress=a.asm.j).apply(null,arguments)};a._get_out_pos=function(){return(a._get_out_pos=a.asm.k).apply(null,arguments)};a._get_out_buffer=function(){return(a._get_out_buffer=a.asm.l).apply(null,arguments)};a._out_buffer_cleared=function(){return(a._out_buffer_cleared=a.asm.m).apply(null,arguments)};a._release=function(){return(a._release=a.asm.n).apply(null,arguments)};var L;
D=function M(){L||N();L||(D=M)};
function N(){function b(){if(!L&&(L=!0,a.calledRun=!0,!t)){J(x);e(a);if(a.onRuntimeInitialized)a.onRuntimeInitialized();if(a.postRun)for("function"==typeof a.postRun&&(a.postRun=[a.postRun]);a.postRun.length;){var c=a.postRun.shift();z.unshift(c)}J(z)}}if(!(0<B)){if(a.preRun)for("function"==typeof a.preRun&&(a.preRun=[a.preRun]);a.preRun.length;)A();J(w);0<B||(a.setStatus?(a.setStatus("Running..."),setTimeout(function(){setTimeout(function(){a.setStatus("")},1);b()},1)):b())}}a.run=N;
if(a.preInit)for("function"==typeof a.preInit&&(a.preInit=[a.preInit]);0<a.preInit.length;)a.preInit.pop()();N();
return XZ.ready
}
);
})();
if (typeof exports === 'object' && typeof module === 'object')
module.exports = XZ;
else if (typeof define === 'function' && define['amd'])
define([], function() { return XZ; });
else if (typeof exports === 'object')
exports["XZ"] = XZ;

BIN
www/js/lib/xzdec-wasm.wasm Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,7 @@
/**
* xzdec_wrapper.js: Javascript wrapper around compiled xz decompressor.
*
* Copyright 2015 Mossroy and contributors
* Copyright 2021 Mossroy and contributors
* License GPL v3:
*
* This file is part of Kiwix.
@ -20,10 +20,55 @@
* along with Kiwix (file LICENSE-GPLv3.txt). If not, see <http://www.gnu.org/licenses/>
*/
'use strict';
define(['xzdec'], function() {
// DEV: xzdec.js emits a global Module variable, which cannot be set in requireJS function line above, though it can be loaded in definition
var xzdec = Module;
xzdec._init();
// 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.
var rqDefXZ = [];
// Select asm or wasm conditionally
if ('WebAssembly' in self) {
console.debug('Using WASM xz decoder');
rqDefXZ.push('xzdec-wasm');
} else {
console.debug('Using ASM xz decoder');
rqDefXZ.push('xzdec-asm');
}
define(rqDefXZ, function() {
// 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
// For explanation of loading method below to avoid conflicts, see https://github.com/emscripten-core/emscripten/blob/master/src/settings.js
/**
* @typedef EMSInstance An object type representing an Emscripten instance
*/
/**
* The XZ Decoder instance
* @type EMSInstance
*/
var xzdec;
var instantiateDecoder = function (instance) {
xzdec = instance;
};
XZ().then(instantiateDecoder)
.catch(function (err) {
console.debug(err);
if (/CompileError.+?WASM/i.test(err.message)) {
console.log("WASM failed to load, falling back to ASM...", err);
XZ = null;
require(['xzdec-asm'], function() {
XZ().then(instantiateDecoder)
.catch(function (err) {
console.error('Could not instantiate any decoder!', err);
});
});
}
});
/**
* Number of milliseconds to wait for the decompressor to be available for another chunk
@ -86,6 +131,7 @@ define(['xzdec'], function() {
* @returns {Promise} A Promise for the read data
*/
Decompressor.prototype.readSliceSingleThread = function(offset, length) {
// Tests whether the decompressor is ready (initiated) and not busy
if (xzdec && !busy) {
return this.readSlice(offset, length);
} else {
@ -94,7 +140,6 @@ define(['xzdec'], function() {
// before using it for another decompression
var that = this;
return new Promise(function (resolve, reject) {
setTimeout(function(){
that.readSliceSingleThread(offset, length).then(resolve, reject);
}, DELAY_WAITING_IDLE_DECOMPRESSOR);
@ -149,7 +194,6 @@ define(['xzdec'], function() {
*/
Decompressor.prototype._fillInBufferIfNeeded = function() {
if (!xzdec._input_empty(this._decHandle)) {
// DEV: When converting to Promise/A+, use Promise.resolve(0) here
return Promise.resolve(0);
}
var that = this;

View File

@ -20,6 +20,21 @@
* along with Kiwix (file LICENSE-GPLv3.txt). If not, see <http://www.gnu.org/licenses/>
*/
'use strict';
/**
* Add Polyfill currently required by IE11 to run zstddec-asm and xzdec-asm
* See https://github.com/emscripten-core/emscripten/issues/14700
* If this is resolved upstream, remove this polyfill
*/
if (!String.prototype.startsWith) {
Object.defineProperty(String.prototype, 'startsWith', {
value: function(search, rawPos) {
var pos = rawPos > 0 ? rawPos|0 : 0;
return this.substring(pos, pos + search.length) === search;
}
});
}
define(['xzdec_wrapper', 'zstddec_wrapper', 'util', 'utf8', 'zimDirEntry', 'filecache'], function(xz, zstd, util, utf8, zimDirEntry, FileCache) {
/**

58
www/js/lib/zstddec-asm.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,36 @@

var ZD = (function() {
var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined;
return (
function(ZD) {
ZD = ZD || {};
var a;a||(a=typeof ZD !== 'undefined' ? ZD : {});var q,r;a.ready=new Promise(function(b,c){q=b;r=c});var t={},w;for(w in a)a.hasOwnProperty(w)&&(t[w]=a[w]);var x="";"undefined"!==typeof document&&document.currentScript&&(x=document.currentScript.src);_scriptDir&&(x=_scriptDir);0!==x.indexOf("blob:")?x=x.substr(0,x.lastIndexOf("/")+1):x="";var y=a.printErr||console.warn.bind(console);for(w in t)t.hasOwnProperty(w)&&(a[w]=t[w]);t=null;var z;a.wasmBinary&&(z=a.wasmBinary);
var noExitRuntime=a.noExitRuntime||!0;"object"!==typeof WebAssembly&&A("no native wasm support detected");var B,C=!1;function D(b){var c=a["_"+b];c||A("Assertion failed: Cannot call unknown function "+(b+", make sure it is exported"));return c}
function aa(b,c,g,n){var e={string:function(d){var m=0;if(null!==d&&void 0!==d&&0!==d){var l=(d.length<<2)+1;m=E(l);var h=m,f=F;if(0<l){l=h+l-1;for(var u=0;u<d.length;++u){var k=d.charCodeAt(u);if(55296<=k&&57343>=k){var ba=d.charCodeAt(++u);k=65536+((k&1023)<<10)|ba&1023}if(127>=k){if(h>=l)break;f[h++]=k}else{if(2047>=k){if(h+1>=l)break;f[h++]=192|k>>6}else{if(65535>=k){if(h+2>=l)break;f[h++]=224|k>>12}else{if(h+3>=l)break;f[h++]=240|k>>18;f[h++]=128|k>>12&63}f[h++]=128|k>>6&63}f[h++]=128|k&63}}f[h]=
0}}return m},array:function(d){var m=E(d.length);H.set(d,m);return m}},p=D(b),G=[];b=0;if(n)for(var v=0;v<n.length;v++){var Q=e[g[v]];Q?(0===b&&(b=I()),G[v]=Q(n[v])):G[v]=n[v]}g=p.apply(null,G);g=function(d){if("string"===c)if(d){for(var m=F,l=d+NaN,h=d;m[h]&&!(h>=l);)++h;if(16<h-d&&m.subarray&&J)d=J.decode(m.subarray(d,h));else{for(l="";d<h;){var f=m[d++];if(f&128){var u=m[d++]&63;if(192==(f&224))l+=String.fromCharCode((f&31)<<6|u);else{var k=m[d++]&63;f=224==(f&240)?(f&15)<<12|u<<6|k:(f&7)<<18|
u<<12|k<<6|m[d++]&63;65536>f?l+=String.fromCharCode(f):(f-=65536,l+=String.fromCharCode(55296|f>>10,56320|f&1023))}}else l+=String.fromCharCode(f)}d=l}}else d="";else d="boolean"===c?!!d:d;return d}(g);0!==b&&K(b);return g}var J="undefined"!==typeof TextDecoder?new TextDecoder("utf8"):void 0,H,F,L,M=[],N=[],O=[];function ca(){var b=a.preRun.shift();M.unshift(b)}var P=0,R=null,S=null;a.preloadedImages={};a.preloadedAudios={};
function A(b){if(a.onAbort)a.onAbort(b);y(b);C=!0;b=new WebAssembly.RuntimeError("abort("+b+"). Build with -s ASSERTIONS=1 for more info.");r(b);throw b;}function T(){return U.startsWith("data:application/octet-stream;base64,")}var U;U="zstddec-wasm.wasm";if(!T()){var V=U;U=a.locateFile?a.locateFile(V,x):x+V}function W(){var b=U;try{if(b==U&&z)return new Uint8Array(z);throw"both async and sync fetching of the wasm failed";}catch(c){A(c)}}
function da(){return z||"function"!==typeof fetch?Promise.resolve().then(function(){return W()}):fetch(U,{credentials:"same-origin"}).then(function(b){if(!b.ok)throw"failed to load wasm binary file at '"+U+"'";return b.arrayBuffer()}).catch(function(){return W()})}function X(b){for(;0<b.length;){var c=b.shift();if("function"==typeof c)c(a);else{var g=c.u;"number"===typeof g?void 0===c.s?L.get(g)():L.get(g)(c.s):g(void 0===c.s?null:c.s)}}}var ea={a:function(b,c,g){F.copyWithin(b,c,c+g)},b:function(){A("OOM")}};
(function(){function b(e){a.asm=e.exports;B=a.asm.c;e=B.buffer;a.HEAP8=H=new Int8Array(e);a.HEAP16=new Int16Array(e);a.HEAP32=new Int32Array(e);a.HEAPU8=F=new Uint8Array(e);a.HEAPU16=new Uint16Array(e);a.HEAPU32=new Uint32Array(e);a.HEAPF32=new Float32Array(e);a.HEAPF64=new Float64Array(e);L=a.asm.o;N.unshift(a.asm.d);P--;a.monitorRunDependencies&&a.monitorRunDependencies(P);0==P&&(null!==R&&(clearInterval(R),R=null),S&&(e=S,S=null,e()))}function c(e){b(e.instance)}function g(e){return da().then(function(p){return WebAssembly.instantiate(p,
n)}).then(e,function(p){y("failed to asynchronously prepare wasm: "+p);A(p)})}var n={a:ea};P++;a.monitorRunDependencies&&a.monitorRunDependencies(P);if(a.instantiateWasm)try{return a.instantiateWasm(n,b)}catch(e){return y("Module.instantiateWasm callback failed with error: "+e),!1}(function(){return z||"function"!==typeof WebAssembly.instantiateStreaming||T()||"function"!==typeof fetch?g(c):fetch(U,{credentials:"same-origin"}).then(function(e){return WebAssembly.instantiateStreaming(e,n).then(c,function(p){y("wasm streaming compile failed: "+
p);y("falling back to ArrayBuffer instantiation");return g(c)})})})().catch(r);return{}})();a.___wasm_call_ctors=function(){return(a.___wasm_call_ctors=a.asm.d).apply(null,arguments)};a._malloc=function(){return(a._malloc=a.asm.e).apply(null,arguments)};a._free=function(){return(a._free=a.asm.f).apply(null,arguments)};a._ZSTD_isError=function(){return(a._ZSTD_isError=a.asm.g).apply(null,arguments)};a._ZSTD_getErrorName=function(){return(a._ZSTD_getErrorName=a.asm.h).apply(null,arguments)};
a._ZSTD_createDStream=function(){return(a._ZSTD_createDStream=a.asm.i).apply(null,arguments)};a._ZSTD_freeDStream=function(){return(a._ZSTD_freeDStream=a.asm.j).apply(null,arguments)};a._ZSTD_DStreamInSize=function(){return(a._ZSTD_DStreamInSize=a.asm.k).apply(null,arguments)};a._ZSTD_DStreamOutSize=function(){return(a._ZSTD_DStreamOutSize=a.asm.l).apply(null,arguments)};a._ZSTD_initDStream=function(){return(a._ZSTD_initDStream=a.asm.m).apply(null,arguments)};
a._ZSTD_decompressStream=function(){return(a._ZSTD_decompressStream=a.asm.n).apply(null,arguments)};var I=a.stackSave=function(){return(I=a.stackSave=a.asm.p).apply(null,arguments)},K=a.stackRestore=function(){return(K=a.stackRestore=a.asm.q).apply(null,arguments)},E=a.stackAlloc=function(){return(E=a.stackAlloc=a.asm.r).apply(null,arguments)};a.cwrap=function(b,c,g,n){g=g||[];var e=g.every(function(p){return"number"===p});return"string"!==c&&e&&!n?D(b):function(){return aa(b,c,g,arguments)}};var Y;
S=function fa(){Y||Z();Y||(S=fa)};
function Z(){function b(){if(!Y&&(Y=!0,a.calledRun=!0,!C)){X(N);q(a);if(a.onRuntimeInitialized)a.onRuntimeInitialized();if(a.postRun)for("function"==typeof a.postRun&&(a.postRun=[a.postRun]);a.postRun.length;){var c=a.postRun.shift();O.unshift(c)}X(O)}}if(!(0<P)){if(a.preRun)for("function"==typeof a.preRun&&(a.preRun=[a.preRun]);a.preRun.length;)ca();X(M);0<P||(a.setStatus?(a.setStatus("Running..."),setTimeout(function(){setTimeout(function(){a.setStatus("")},1);b()},1)):b())}}a.run=Z;
if(a.preInit)for("function"==typeof a.preInit&&(a.preInit=[a.preInit]);0<a.preInit.length;)a.preInit.pop()();Z();
return ZD.ready
}
);
})();
if (typeof exports === 'object' && typeof module === 'object')
module.exports = ZD;
else if (typeof define === 'function' && define['amd'])
define([], function() { return ZD; });
else if (typeof exports === 'object')
exports["ZD"] = ZD;

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -20,10 +20,24 @@
* along with Kiwix (file LICENSE-GPLv3.txt). If not, see <http://www.gnu.org/licenses/>
*/
'use strict';
define(['zstddec'], function() {
// DEV: zstddec.js has been compiled with `-s EXPORT_NAME="ZD" -s MODULARIZE=1` to avoid a clash with xzdec which uses "Module" as its exported object
// Note that we include zstddec 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
// 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.
var rqDefZD = [];
// Select asm or wasm conditionally
if ('WebAssembly' in self) {
console.debug('Using WASM zstandard decoder');
rqDefZD.push('zstddec-wasm');
} else {
console.debug('Using ASM zstandard decoder');
rqDefZD.push('zstddec-asm');
}
define(rqDefZD, function() {
// 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
/**
@ -39,7 +53,8 @@ define(['zstddec'], function() {
* @type EMSInstanceExt
*/
var zd;
ZD().then(function(instance) {
var instantiateDecoder = function (instance) {
// Instantiate the zd object
zd = instance;
// Create JS API by wrapping C++ functions
@ -82,6 +97,21 @@ define(['zstddec'], function() {
zd._outBuffer.ptr = mallocOrDie(3 << 2); // 3 x 32bit bytes
// Reserve w/asm memory for the outBuffer data steam
zd._outBuffer.dst = mallocOrDie(zd._outBuffer.size);
};
ZD().then(instantiateDecoder)
.catch(function (err) {
console.debug(err);
if (/CompileError.+?WASM/i.test(err.message)) {
console.log("WASM failed to load, falling back to ASM...", err);
ZD = null;
require(['zstddec-asm'], function() {
ZD().then(instantiateDecoder)
.catch(function (err) {
console.error('Could not instantiate any decoder!', err);
});
});
}
});
/**
@ -94,7 +124,7 @@ define(['zstddec'], function() {
* Is the decompressor already working?
* @type Boolean
*/
appstate.zdBusy = false;
var busy = false;
/**
* @typedef Decompressor
@ -113,6 +143,7 @@ define(['zstddec'], function() {
function Decompressor(reader) {
this._reader = reader;
}
/**
* Set up the decompression stream, and initiate a read loop to decompress from the beginning of the cluster
* until we reach <offset> in the decompressed byte stream
@ -121,7 +152,7 @@ define(['zstddec'], function() {
* @returns {Promise<ArrayBuffer>} Promise for an ArrayBuffer with decoded data
*/
Decompressor.prototype.readSlice = function(offset, length) {
appstate.zdBusy = true;
busy = true;
this._inStreamPos = 0;
this._inStreamChunkedPos = 0;
this._outStreamPos = 0;
@ -139,7 +170,7 @@ define(['zstddec'], function() {
// Should you need to free the decoder stream handle, use command below, but be sure to create a new stream control object
// before attempting further decompression
// zd._ZSTD_freeDStream(zd._decHandle);
appstate.zdBusy = false;
busy = false;
return data;
});
};
@ -152,7 +183,8 @@ define(['zstddec'], function() {
* @returns {Promise} A Promise for the readSlice() function
*/
Decompressor.prototype.readSliceSingleThread = function (offset, length) {
if (zd && !appstate.zdBusy) {
// Tests whether the decompressor is ready (initiated) and not busy
if (zd && !busy) {
return this.readSlice(offset, length);
} else {
// The decompressor is already in progress.
@ -260,4 +292,4 @@ define(['zstddec'], function() {
return {
Decompressor: Decompressor
};
});
});