import {startsWith, getFileExtension} from '../utils/string';
import {getUrlFromUri} from '../utils/uri';
import {fileContentTypes} from './filesLoader';
import {urlContentTypes} from './urlsLoader';
import {MemoryLoader} from './memoryLoader';
/**
* The zip library.
*
* Ref: {@link https://github.com/Stuk/jszip}.
*
* @external JSZip
*/
import JSZip from 'jszip';
/**
* ZIP data loader.
*/
export class ZipLoader {
/**
* Loading flag.
*
* @type {boolean}
*/
#isLoading = false;
/**
* Set the loader options.
*
* @param {object} _opt The input options.
*/
setOptions(_opt) {
// does nothing
}
/**
* Is the load ongoing?
*
* @returns {boolean} True if loading.
*/
isLoading() {
return this.#isLoading;
}
#filename = '';
#files = [];
#zobjs = null;
/**
* JSZip.async callback.
*
* @param {ArrayBuffer} content Unzipped file image.
* @param {object} origin The origin of the file.
* @param {number} index The data index.
*/
#zipAsyncCallback(content, origin, index) {
this.#files.push({filename: this.#filename, data: content});
// sent un-ziped progress with the data index
// (max 50% to take into account the memory loading)
const unzipPercent = this.#files.length * 100 / this.#zobjs.length;
this.onprogress({
lengthComputable: true,
loaded: (unzipPercent / 2),
total: 100,
index: index,
item: {
loaded: unzipPercent,
total: 100,
source: origin
}
});
// recursively call until we have all the files
if (this.#files.length < this.#zobjs.length) {
const num = this.#files.length;
this.#filename = this.#zobjs[num].name;
this.#zobjs[num].async('arrayBuffer').then((content) => {
this.#zipAsyncCallback(content, origin, index);
});
} else {
const memoryIO = new MemoryLoader();
// memoryIO.onloadstart: nothing to do
memoryIO.onprogress = (progress) => {
// add 50% to take into account the un-zipping
progress.loaded = 50 + progress.loaded / 2;
// set data index
progress.index = index;
this.onprogress(progress);
};
memoryIO.onloaditem = this.onloaditem;
memoryIO.onload = this.onload;
memoryIO.onloadend = (event) => {
// reset loading flag
this.#isLoading = false;
// call listeners
this.onloadend(event);
};
memoryIO.onerror = this.onerror;
memoryIO.onabort = this.onabort;
// launch
memoryIO.load(this.#files);
}
}
/**
* Load data.
*
* @param {object} buffer The DICOM buffer.
* @param {string} origin The data origin.
* @param {number} index The data index.
*/
load(buffer, origin, index) {
// send start event
this.onloadstart({
source: origin
});
// set loading flag
this.#isLoading = true;
JSZip.loadAsync(buffer).then((zip) => {
this.#files = [];
this.#zobjs = zip.file(/.*\.dcm/);
// recursively load zip files into the files array
const num = this.#files.length;
this.#filename = this.#zobjs[num].name;
this.#zobjs[num].async('arrayBuffer').then((content) => {
this.#zipAsyncCallback(content, origin, index);
});
});
}
/**
* Abort load: pass to listeners.
*/
abort() {
// reset loading flag
this.#isLoading = false;
// call listeners
this.onabort({});
this.onloadend({});
}
/**
* Check if the loader can load the provided file.
* True if the file has a 'zip' extension.
*
* @param {File} file The file to check.
* @returns {boolean} True if the file can be loaded.
*/
canLoadFile(file) {
const ext = getFileExtension(file.name);
return (ext === 'zip');
}
/**
* Check if the loader can load the provided url.
* True if one of the folowing conditions is true:
* - the `options.forceLoader` is 'zip',
* - the `options.requestHeaders` contains an item
* starting with 'Accept: application/zip'.
* - the url has a 'zip' extension.
*
* @param {string} url The url to check.
* @param {object} [options] Optional url request options.
* @returns {boolean} True if the url can be loaded.
*/
canLoadUrl(url, options) {
// check options
if (typeof options !== 'undefined') {
// check options.forceLoader
if (typeof options.forceLoader !== 'undefined' &&
options.forceLoader === 'zip') {
return true;
}
// check options.requestHeaders for 'Accept'
if (typeof options.requestHeaders !== 'undefined') {
const isNameAccept = function (element) {
return element.name === 'Accept';
};
const acceptHeader = options.requestHeaders.find(isNameAccept);
if (typeof acceptHeader !== 'undefined') {
// starts with 'application/zip'
return startsWith(acceptHeader.value, 'application/zip');
}
}
}
const urlObjext = getUrlFromUri(url);
const ext = getFileExtension(urlObjext.pathname);
return (ext === 'zip');
}
/**
* Check if the loader can load the provided memory object.
*
* @param {object} mem The memory object.
* @returns {boolean} True if the object can be loaded.
*/
canLoadMemory(mem) {
const contentType = mem['Content-Type'];
if (typeof contentType !== 'undefined' &&
contentType.startsWith('application/zip')) {
return true;
}
if (typeof mem.filename !== 'undefined') {
const tmpFile = new File(['from memory'], mem.filename);
return this.canLoadFile(tmpFile);
}
return false;
}
/**
* Get the file content type needed by the loader.
*
* @returns {number} One of the 'fileContentTypes'.
*/
loadFileAs() {
return fileContentTypes.ArrayBuffer;
}
/**
* Get the url content type needed by the loader.
*
* @returns {number} One of the 'urlContentTypes'.
*/
loadUrlAs() {
return urlContentTypes.ArrayBuffer;
}
/**
* Handle a load start event.
* Default does nothing.
*
* @param {object} _event The load start event.
*/
onloadstart(_event) {}
/**
* Handle a load progress event.
* Default does nothing.
*
* @param {object} _event The progress event.
*/
onprogress(_event) {}
/**
* Handle a load item event.
* Default does nothing.
*
* @param {object} _event The load item event fired
* when a file item has been loaded successfully.
*/
onloaditem(_event) {}
/**
* Handle a load event.
* Default does nothing.
*
* @param {object} _event The load event fired
* when a file has been loaded successfully.
*/
onload(_event) {}
/**
* Handle an load end event.
* Default does nothing.
*
* @param {object} _event The load end event fired
* when a file load has completed, successfully or not.
*/
onloadend(_event) {}
/**
* Handle an error event.
* Default does nothing.
*
* @param {object} _event The error event.
*/
onerror(_event) {}
/**
* Handle an abort event.
* Default does nothing.
*
* @param {object} _event The abort event.
*/
onabort(_event) {}
} // class ZipLoader