mirror of
https://github.com/kiwix/kiwix-js.git
synced 2025-09-24 04:54:51 -04:00
parent
5f16fe39c9
commit
2d792b9c13
@ -204,7 +204,7 @@ define(['jquery', 'zimArchive', 'zimDirEntry', 'util', 'uiUtil', 'utf8'],
|
||||
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.getDirEntryByTitle("A/(The_Night_Time_Is)_The_Right_Time.html").then(function(dirEntry) {
|
||||
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.");
|
||||
@ -223,7 +223,7 @@ define(['jquery', 'zimArchive', 'zimDirEntry', 'util', 'uiUtil', 'utf8'],
|
||||
QUnit.test("article 'Raelettes' correctly redirects to 'The Raelettes'", function(assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(6);
|
||||
localZimArchive.getDirEntryByTitle("A/Raelettes.html").then(function(dirEntry) {
|
||||
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.");
|
||||
@ -242,7 +242,7 @@ define(['jquery', 'zimArchive', 'zimDirEntry', 'util', 'uiUtil', 'utf8'],
|
||||
QUnit.test("article 'Bein Green' correctly redirects to 'Bein' Green", function(assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(6);
|
||||
localZimArchive.getDirEntryByTitle("A/Bein_Green.html").then(function(dirEntry) {
|
||||
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.");
|
||||
@ -261,7 +261,7 @@ define(['jquery', 'zimArchive', 'zimDirEntry', 'util', 'uiUtil', 'utf8'],
|
||||
QUnit.test("article 'America, the Beautiful' correctly redirects to 'America the Beautiful'", function(assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(6);
|
||||
localZimArchive.getDirEntryByTitle("A/America,_the_Beautiful.html").then(function(dirEntry) {
|
||||
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.");
|
||||
@ -280,7 +280,7 @@ define(['jquery', 'zimArchive', 'zimDirEntry', 'util', 'uiUtil', 'utf8'],
|
||||
QUnit.test("Image 'm/RayCharles_AManAndHisSoul.jpg' can be loaded", function(assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(5);
|
||||
localZimArchive.getDirEntryByTitle("I/m/RayCharles_AManAndHisSoul.jpg").then(function(dirEntry) {
|
||||
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.");
|
||||
@ -301,7 +301,7 @@ define(['jquery', 'zimArchive', 'zimDirEntry', 'util', 'uiUtil', 'utf8'],
|
||||
var done = assert.async();
|
||||
|
||||
assert.expect(5);
|
||||
localZimArchive.getDirEntryByTitle("-/s/style.css").then(function(dirEntry) {
|
||||
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.");
|
||||
@ -321,7 +321,7 @@ define(['jquery', 'zimArchive', 'zimDirEntry', 'util', 'uiUtil', 'utf8'],
|
||||
QUnit.test("Javascript '-/j/local.js' can be loaded", function(assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(5);
|
||||
localZimArchive.getDirEntryByTitle("-/j/local.js").then(function(dirEntry) {
|
||||
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.");
|
||||
@ -342,7 +342,7 @@ define(['jquery', 'zimArchive', 'zimDirEntry', 'util', 'uiUtil', 'utf8'],
|
||||
QUnit.test("Split article 'A/Ray_Charles.html' can be loaded", function(assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(7);
|
||||
localZimArchive.getDirEntryByTitle("A/Ray_Charles.html").then(function(dirEntry) {
|
||||
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.");
|
||||
|
@ -1260,7 +1260,7 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
});
|
||||
}
|
||||
};
|
||||
selectedArchive.getDirEntryByTitle(title).then(readFile).catch(function () {
|
||||
selectedArchive.getDirEntryByPath(title).then(readFile).catch(function () {
|
||||
messagePort.postMessage({ 'action': 'giveContent', 'title': title, 'content': new Uint8Array() });
|
||||
});
|
||||
} else {
|
||||
@ -1462,8 +1462,8 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
// Extract the image at the top of the images array and remove it from the array
|
||||
var image = images.shift();
|
||||
var imageUrl = image.getAttribute('data-kiwixurl');
|
||||
var title = decodeURIComponent(imageUrl);
|
||||
selectedArchive.getDirEntryByTitle(title).then(function (dirEntry) {
|
||||
var url = decodeURIComponent(imageUrl);
|
||||
selectedArchive.getDirEntryByPath(url).then(function (dirEntry) {
|
||||
selectedArchive.readBinaryFile(dirEntry, function (fileDirEntry, content) {
|
||||
var mimetype = dirEntry.getMimetype();
|
||||
uiUtil.feedNodeWithBlob(image, 'src', content, mimetype, function() {
|
||||
@ -1472,7 +1472,7 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
});
|
||||
});
|
||||
}).catch(function (e) {
|
||||
console.error("could not find DirEntry for image:" + title, e);
|
||||
console.error("could not find DirEntry for image:" + url, e);
|
||||
images.busy = false;
|
||||
extractImage();
|
||||
});
|
||||
@ -1508,14 +1508,14 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
cssCount++;
|
||||
var link = $(this);
|
||||
var linkUrl = link.attr("data-kiwixurl");
|
||||
var title = uiUtil.removeUrlParameters(decodeURIComponent(linkUrl));
|
||||
if (cssCache.has(title)) {
|
||||
var cssContent = cssCache.get(title);
|
||||
var url = uiUtil.removeUrlParameters(decodeURIComponent(linkUrl));
|
||||
if (cssCache.has(url)) {
|
||||
var cssContent = cssCache.get(url);
|
||||
uiUtil.replaceCSSLinkWithInlineCSS(link, cssContent);
|
||||
cssFulfilled++;
|
||||
} else {
|
||||
if (params.useCache) $('#cachingAssets').show();
|
||||
selectedArchive.getDirEntryByTitle(title)
|
||||
selectedArchive.getDirEntryByPath(url)
|
||||
.then(function (dirEntry) {
|
||||
return selectedArchive.readUtf8File(dirEntry,
|
||||
function (fileDirEntry, content) {
|
||||
@ -1527,7 +1527,7 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
}
|
||||
);
|
||||
}).catch(function (e) {
|
||||
console.error("could not find DirEntry for CSS : " + title, e);
|
||||
console.error("could not find DirEntry for CSS : " + url, e);
|
||||
cssCount--;
|
||||
renderIfCSSFulfilled();
|
||||
});
|
||||
@ -1563,7 +1563,7 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
// var scriptUrl = script.attr("data-kiwixurl");
|
||||
// // TODO check that the type of the script is text/javascript or application/javascript
|
||||
// var title = uiUtil.removeUrlParameters(decodeURIComponent(scriptUrl));
|
||||
// selectedArchive.getDirEntryByTitle(title).then(function(dirEntry) {
|
||||
// selectedArchive.getDirEntryByPath(title).then(function(dirEntry) {
|
||||
// if (dirEntry === null) {
|
||||
// console.log("Error: js file not found: " + title);
|
||||
// } else {
|
||||
@ -1591,7 +1591,7 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
return;
|
||||
}
|
||||
var mediaElement = /audio|video/i.test(mediaSource.tagName) ? mediaSource : mediaSource.parentElement;
|
||||
selectedArchive.getDirEntryByTitle(source).then(function(dirEntry) {
|
||||
selectedArchive.getDirEntryByPath(source).then(function(dirEntry) {
|
||||
return selectedArchive.readBinaryFile(dirEntry, function (fileDirEntry, mediaArray) {
|
||||
var mimeType = mediaSource.type ? mediaSource.type : dirEntry.getMimetype();
|
||||
var blob = new Blob([mediaArray], { type: mimeType });
|
||||
@ -1649,30 +1649,30 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
|
||||
|
||||
/**
|
||||
* Extracts the content of the given article title, or a downloadable file, from the ZIM
|
||||
* Extracts the content of the given article pathname, or a downloadable file, from the ZIM
|
||||
*
|
||||
* @param {String} title The path and filename to the article or file to be extracted
|
||||
* @param {String} path The pathname (namespace + filename) to the article or file to be extracted
|
||||
* @param {Boolean|String} download A Bolean value that will trigger download of title, or the filename that should
|
||||
* be used to save the file in local FS (in HTML5 spec, a string value for the download attribute is optional)
|
||||
* @param {String} contentType The mimetype of the downloadable file, if known
|
||||
*/
|
||||
function goToArticle(title, download, contentType) {
|
||||
function goToArticle(path, download, contentType) {
|
||||
$("#searchingArticles").show();
|
||||
selectedArchive.getDirEntryByTitle(title).then(function(dirEntry) {
|
||||
selectedArchive.getDirEntryByPath(path).then(function(dirEntry) {
|
||||
if (dirEntry === null || dirEntry === undefined) {
|
||||
$("#searchingArticles").hide();
|
||||
alert("Article with title " + title + " not found in the archive");
|
||||
alert("Article with url " + path + " not found in the archive");
|
||||
} else if (download) {
|
||||
selectedArchive.readBinaryFile(dirEntry, function (fileDirEntry, content) {
|
||||
var mimetype = contentType || fileDirEntry.getMimetype();
|
||||
uiUtil.displayFileDownloadAlert(title, download, mimetype, content);
|
||||
uiUtil.displayFileDownloadAlert(path, download, mimetype, content);
|
||||
});
|
||||
} else {
|
||||
params.isLandingPage = false;
|
||||
$('#activeContent').hide();
|
||||
readArticle(dirEntry);
|
||||
}
|
||||
}).catch(function(e) { alert("Error reading article with title " + title + " : " + e); });
|
||||
}).catch(function(e) { alert("Error reading article with url " + path + " : " + e); });
|
||||
}
|
||||
|
||||
function goToRandomArticle() {
|
||||
@ -1683,9 +1683,9 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
alert("Error finding random article.");
|
||||
} else {
|
||||
// We fall back to the old A namespace to support old ZIM files without a text/html MIME type for articles
|
||||
// DEV: This will need to be changed if we search titlePtrList version 1
|
||||
// in a future PR, as that list contains only articles
|
||||
if (dirEntry.getMimetype() === 'text/html' || dirEntry.namespace === 'A') {
|
||||
// DEV: If articlePtrPos is defined in zimFile, then we are using a v1 article-only title listing. By definition,
|
||||
// all dirEntries in an article-only listing must be articles.
|
||||
if (selectedArchive._file.articlePtrPos || dirEntry.getMimetype() === 'text/html' || dirEntry.namespace === 'A') {
|
||||
params.isLandingPage = false;
|
||||
$('#activeContent').hide();
|
||||
$('#searchingArticles').show();
|
||||
|
@ -46,17 +46,39 @@ define(['zimfile', 'zimDirEntry', 'util', 'utf8'],
|
||||
* 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.<Blob>} storage Storage (in this case, the path must be given) or Array of Files (path parameter must be omitted)
|
||||
* @param {String} path
|
||||
* @param {callbackZIMArchive} callbackReady
|
||||
* @param {StorageFirefoxOS|Array<Blob>} 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
|
||||
*/
|
||||
function ZIMArchive(storage, path, callbackReady) {
|
||||
var that = this;
|
||||
that._file = null;
|
||||
that._language = ""; //@TODO
|
||||
var createZimfile = function(fileArray) {
|
||||
zimfile.fromFileArray(fileArray).then(function(file) {
|
||||
var createZimfile = function (fileArray) {
|
||||
zimfile.fromFileArray(fileArray).then(function (file) {
|
||||
that._file = file;
|
||||
// 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'
|
||||
}
|
||||
]);
|
||||
// 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);
|
||||
});
|
||||
};
|
||||
@ -245,7 +267,9 @@ define(['zimfile', 'zimDirEntry', 'util', 'utf8'],
|
||||
ZIMArchive.prototype.findDirEntriesWithPrefixCaseSensitive = function(prefix, resultSize, search, callback) {
|
||||
var that = this;
|
||||
var cns = this.getContentNamespace();
|
||||
util.binarySearch(0, this._file.articleCount, function(i) {
|
||||
// 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;
|
||||
@ -257,7 +281,7 @@ define(['zimfile', 'zimDirEntry', 'util', 'utf8'],
|
||||
}, true).then(function(firstIndex) {
|
||||
var dirEntries = [];
|
||||
var addDirEntries = function(index) {
|
||||
if (search.status === 'cancelled' || index >= firstIndex + resultSize || index >= that._file.articleCount) {
|
||||
if (search.status === 'cancelled' || index >= firstIndex + resultSize || index >= articleCount) {
|
||||
return dirEntries;
|
||||
}
|
||||
return that._file.dirEntryByTitleIndex(index).then(function(dirEntry) {
|
||||
@ -322,18 +346,18 @@ define(['zimfile', 'zimDirEntry', 'util', 'utf8'],
|
||||
};
|
||||
|
||||
/**
|
||||
* Searches a DirEntry (article / page) by its title.
|
||||
* @param {String} title
|
||||
* @return {Promise} resolving to the DirEntry object or null if not 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<DirEntry>} A Promise that resolves to a Directory Entry, or null if not found.
|
||||
*/
|
||||
ZIMArchive.prototype.getDirEntryByTitle = function(title) {
|
||||
ZIMArchive.prototype.getDirEntryByPath = function(path) {
|
||||
var that = this;
|
||||
return util.binarySearch(0, this._file.articleCount, function(i) {
|
||||
return util.binarySearch(0, this._file.entryCount, function(i) {
|
||||
return that._file.dirEntryByUrlIndex(i).then(function(dirEntry) {
|
||||
var url = dirEntry.namespace + "/" + dirEntry.url;
|
||||
if (title < url)
|
||||
if (path < url)
|
||||
return -1;
|
||||
else if (title > url)
|
||||
else if (path > url)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
@ -351,8 +375,10 @@ define(['zimfile', 'zimDirEntry', 'util', 'utf8'],
|
||||
* @param {callbackDirEntry} callback
|
||||
*/
|
||||
ZIMArchive.prototype.getRandomDirEntry = function(callback) {
|
||||
var index = Math.floor(Math.random() * this._file.articleCount);
|
||||
this._file.dirEntryByUrlIndex(index).then(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);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -362,7 +388,7 @@ define(['zimfile', 'zimDirEntry', 'util', 'utf8'],
|
||||
*/
|
||||
ZIMArchive.prototype.getMetadata = function (key, callback) {
|
||||
var that = this;
|
||||
this.getDirEntryByTitle("M/" + key).then(function (dirEntry) {
|
||||
this.getDirEntryByPath("M/" + key).then(function (dirEntry) {
|
||||
if (dirEntry === null || dirEntry === undefined) {
|
||||
console.warn("Title M/" + key + " not found in the archive");
|
||||
callback();
|
||||
|
@ -54,14 +54,17 @@ define(['xzdec_wrapper', 'zstddec_wrapper', 'util', 'utf8', 'q', 'zimDirEntry',
|
||||
* @property {Array<File>} _files Array of ZIM files
|
||||
* @property {String} name Abstract archive name for file set
|
||||
* @property {Integer} id Arbitrary numeric ZIM id used to track the currently loaded archive
|
||||
* @property {Integer} articleCount Total number of articles
|
||||
* @property {Integer} entryCount Total number of entries in the URL pointerlist
|
||||
* @property {Integer} articleCount Total number of articles in the v1 article-only pointerlist (async calculated entry)
|
||||
* @property {Integer} clusterCount Total number of clusters
|
||||
* @property {Integer} urlPtrPos Position of the directory pointerlist ordered by URL
|
||||
* @property {Integer} titlePtrPos Position of the directory pointerlist ordered by title
|
||||
* @property {Integer} titlePtrPos Position of the legacy v0 pointerlist ordered by title
|
||||
* @property {Integer} articlePtrPos Position of the v1 article-only pointerlist ordered by title (async calculated entry)
|
||||
* @property {Integer} clusterPtrPos Position of the cluster pointer list
|
||||
* @property {Integer} mimeListPos Position of the MIME type list (also header size)
|
||||
* @property {Integer} mainPage Main page or 0xffffffff if no main page
|
||||
* @property {Integer} layoutPage Layout page or 0xffffffffff if no layout page
|
||||
* @property {Map} mimeTypes The ZIM file's MIME type table rendered as a Map (calculated entry)
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -182,7 +185,9 @@ define(['xzdec_wrapper', 'zstddec_wrapper', 'util', 'utf8', 'q', 'zimDirEntry',
|
||||
*/
|
||||
ZIMFile.prototype.dirEntryByTitleIndex = function (index) {
|
||||
var that = this;
|
||||
return this._readInteger(this.titlePtrPos + index * 4, 4).then(function (urlIndex) {
|
||||
// Use v1 title pointerlist if available, or fall back to legacy v0 list
|
||||
var ptrList = this.articlePtrPos || this.titlePtrPos;
|
||||
return this._readInteger(ptrList + index * 4, 4).then(function (urlIndex) {
|
||||
return that.dirEntryByUrlIndex(urlIndex);
|
||||
});
|
||||
};
|
||||
@ -191,9 +196,11 @@ define(['xzdec_wrapper', 'zstddec_wrapper', 'util', 'utf8', 'q', 'zimDirEntry',
|
||||
* Read and if necessary decompress a BLOB based on its cluster number and blob number
|
||||
* @param {Integer} cluster The cluster number where the blob is to be found
|
||||
* @param {Integer} blob The blob number within the cluster
|
||||
* @param {Boolean} meta If true, and if the cluster is uncompressed, the function will return only the blob's metadata
|
||||
* (its archive offset and its size), otherwise return null
|
||||
* @returns {Promise<Uint8Array>} A Promise for the BLOB's data
|
||||
*/
|
||||
ZIMFile.prototype.blob = function (cluster, blob) {
|
||||
ZIMFile.prototype.blob = function (cluster, blob, meta) {
|
||||
var that = this;
|
||||
return this._readSlice(this.clusterPtrPos + cluster * 8, 16).then(function (clusterOffsets) {
|
||||
var clusterOffset = readInt(clusterOffsets, 0, 8);
|
||||
@ -202,17 +209,31 @@ define(['xzdec_wrapper', 'zstddec_wrapper', 'util', 'utf8', 'q', 'zimDirEntry',
|
||||
// var thisClusterLength = nextCluster - clusterOffset - 1;
|
||||
return that._readSlice(clusterOffset, 1).then(function (compressionType) {
|
||||
var decompressor;
|
||||
var plainBlobReader = function (offset, size) {
|
||||
var plainBlobReader = function (offset, size, dataPass) {
|
||||
// Check that we are not reading beyond the end of the cluster
|
||||
var offsetStart = clusterOffset + 1 + offset;
|
||||
if (offsetStart < nextCluster) {
|
||||
// Gratuitous parentheses added for legibility
|
||||
size = (offsetStart + size) <= nextCluster ? size : (nextCluster - offsetStart);
|
||||
return that._readSlice(offsetStart, size);
|
||||
// DEV: This blob reader is called twice: on the first pass it reads the cluster's blob list,
|
||||
// and on the second pass ("dataPass") it is ready to read the blob's data
|
||||
if (meta && dataPass) {
|
||||
// If only metadata were requested and we are on the data pass, we should now have them
|
||||
return {
|
||||
ptr: offsetStart,
|
||||
size: size
|
||||
};
|
||||
} else {
|
||||
return that._readSlice(offsetStart, size);
|
||||
}
|
||||
} else {
|
||||
return Q(new Uint8Array(0).buffer);
|
||||
}
|
||||
};
|
||||
// If only metadata were requested and the cluster is compressed, return null (this is probably a ZIM format error)
|
||||
// DEV: This is because metadata are only requested for finding absolute offsets into uncompressed clusters,
|
||||
// principally for finding the start and size of a title pointer listing
|
||||
if (meta && compressionType[0] > 1) return null;
|
||||
if (compressionType[0] === 0 || compressionType[0] === 1) {
|
||||
// uncompressed
|
||||
decompressor = { readSliceSingleThread: plainBlobReader };
|
||||
@ -223,15 +244,85 @@ define(['xzdec_wrapper', 'zstddec_wrapper', 'util', 'utf8', 'q', 'zimDirEntry',
|
||||
} else {
|
||||
return new Uint8Array(); // unsupported compression type
|
||||
}
|
||||
return decompressor.readSliceSingleThread(blob * 4, 8).then(function (data) {
|
||||
return decompressor.readSliceSingleThread(blob * 4, 8, false).then(function (data) {
|
||||
var blobOffset = readInt(data, 0, 4);
|
||||
var nextBlobOffset = readInt(data, 4, 4);
|
||||
return decompressor.readSliceSingleThread(blobOffset, nextBlobOffset - blobOffset);
|
||||
return decompressor.readSliceSingleThread(blobOffset, nextBlobOffset - blobOffset, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* A Directory Listing object
|
||||
* @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
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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 v1 title pointerlists, which contain articles sorted by title, superseding the article
|
||||
* namespace ('A') in legazy ZIM archives.
|
||||
* @param {Array<DirListing>} listings An array of DirListing objects (see zimArchive.js for examples)
|
||||
*/
|
||||
ZIMFile.prototype.setListings = function(listings) {
|
||||
// If we are in a legacy ZIM archive, there is nothing further to look up
|
||||
if (this.minorVersion === 0) {
|
||||
console.debug('ZIM DirListing version: 0 (legacy)', this);
|
||||
return;
|
||||
}
|
||||
var that = this;
|
||||
var highestListingVersion = 0;
|
||||
var listingAccessor = function (listing) {
|
||||
if (!listing) {
|
||||
// No more listings, so exit
|
||||
console.debug('ZIM DirListing version: ' + highestListingVersion, that);
|
||||
return null;
|
||||
}
|
||||
// Check if we already have this listing's values, so we don't do redundant binary searches
|
||||
if (that[listing.ptrName] && that[listing.countName]) {
|
||||
highestListingVersion = Math.max(~~listing.path.replace(/.+(\d)$/, '$1'), highestListingVersion);
|
||||
// Get the next listing
|
||||
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 -1;
|
||||
else if (listing.path > url)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
});
|
||||
}).then(function(index) {
|
||||
if (index === null) return null;
|
||||
return that.dirEntryByUrlIndex(index);
|
||||
}).then(function(dirEntry) {
|
||||
if (!dirEntry) return null;
|
||||
// Request the metadata for the blob represented by the dirEntry
|
||||
return that.blob(dirEntry.cluster, dirEntry.blob, true);
|
||||
}).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) {
|
||||
that[listing.ptrName] = metadata.ptr;
|
||||
that[listing.countName] = metadata.size / 4; // Each entry uses 4 bytes
|
||||
highestListingVersion = Math.max(~~listing.path.replace(/.+(\d)$/, '$1'), highestListingVersion);
|
||||
}
|
||||
// Get the next Listing
|
||||
return listingAccessor(listings.pop());
|
||||
}).catch(function(err) {
|
||||
console.error('There was an error accessing a Directory Listing', err);
|
||||
});
|
||||
};
|
||||
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
|
||||
@ -291,7 +382,7 @@ define(['xzdec_wrapper', 'zstddec_wrapper', 'util', 'utf8', 'q', 'zimDirEntry',
|
||||
return util.readFileSlice(fileArray[0], 0, 80).then(function (header) {
|
||||
var mimeListPos = readInt(header, 56, 8);
|
||||
var urlPtrPos = readInt(header, 32, 8);
|
||||
return readMimetypeMap(fileArray[0], mimeListPos, urlPtrPos).then(function (data) {
|
||||
return readMimetypeMap(fileArray[0], mimeListPos, urlPtrPos).then(function (mapData) {
|
||||
var zf = new ZIMFile(fileArray);
|
||||
// Add an abstract archive name (ignoring split file extensions)
|
||||
zf.name = fileArray[0].name.replace(/(\.zim)\w\w$/i, '$1');
|
||||
@ -304,15 +395,17 @@ define(['xzdec_wrapper', 'zstddec_wrapper', 'util', 'utf8', 'q', 'zimDirEntry',
|
||||
// For a description of these values, see https://wiki.openzim.org/wiki/ZIM_file_format
|
||||
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.articleCount = readInt(header, 24, 4);
|
||||
zf.entryCount = readInt(header, 24, 4);
|
||||
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() called from zimArchive.js
|
||||
zf.clusterPtrPos = readInt(header, 48, 8);
|
||||
zf.mimeListPos = mimeListPos;
|
||||
zf.mainPage = readInt(header, 64, 4);
|
||||
zf.layoutPage = readInt(header, 68, 4);
|
||||
zf.mimeTypes = data;
|
||||
zf.mimeTypes = mapData;
|
||||
return zf;
|
||||
});
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user