import JSZip from 'jszip'; import Monologue from 'monologue.js';
import request from 'paraviewweb/src/IO/Core/DataManager/request'; import htmlRequest from 'paraviewweb/src/IO/Core/DataManager/htmlRequest'; import PatternMap from 'paraviewweb/src/IO/Core/DataManager/pattern';
const typeFnMap = { json: request.fetchJSON, text: request.fetchTxt, blob: request.fetchBlob, arraybuffer: request.fetchArray, array: request.fetchArray, };
function ts() { return new Date().getTime(); }
function updateDataSize(data) { if (data.type === 'json') { data.size = JSON.stringify(data.data).length; } else if (data.type === 'blob') { data.size = data.data.size; } else { data.size = data.data.length; } return data.size; }
export default class DataManager { constructor(cacheSize = 1000000000) { this.pattern = new PatternMap(); this.keyToTypeMap = {}; this.cacheSize = cacheSize; this.cacheData = { cache: {}, modified: 0, ts: 0, size: 0, }; }
destroy() { this.off(); this.clear(); }
fetch(key, options, notificationTopic = null) { const url = options ? this.pattern.getValue(key, options) : key; let dataCached = this.cacheData.cache[url];
if (dataCached) { if (!dataCached.pending) { dataCached.ts = ts(); this.cacheData.ts = dataCached.ts;
setTimeout(() => { const array = dataCached.keysToNotify || [key]; let count = array.length;
delete dataCached.keysToNotify;
while (count) { count -= 1; this.emit(array[count], dataCached); }
if (notificationTopic) { this.emit(notificationTopic, dataCached); } }, 0); } else { dataCached.keysToNotify.push(key); if (notificationTopic) { dataCached.keysToNotify.push(notificationTopic); } } } else { this.gc();
this.cacheData.cache[url] = { pending: true, keysToNotify: [key], };
if (notificationTopic) { this.cacheData.cache[url].keysToNotify.push(notificationTopic); }
const self = this; const typeFnMime = this.keyToTypeMap[key]; const type = typeFnMime[0]; const fn = typeFnMime[1]; const mimeType = typeFnMime[2]; const callback = (error, data) => { if (error) { delete self.cacheData.cache[url]; self.emit(key, { error, data: { key, options, url, typeFnMime, }, }); return; }
dataCached = { data, type, requestedURL: url, pending: false, };
if (mimeType && mimeType.indexOf('image') !== -1) { dataCached.url = window.URL.createObjectURL(data); }
self.cacheData.size += updateDataSize(dataCached);
dataCached.ts = ts(); self.cacheData.ts = dataCached.ts; self.cacheData.modified = self.cacheData.ts;
const array = self.cacheData.cache[url].keysToNotify; let count = array.length;
self.cacheData.cache[url] = dataCached;
while (count) { count -= 1; self.emit(array[count], dataCached); } };
if (mimeType) { fn(url, mimeType, callback); } else { fn(url, callback); } }
return url; }
fetchURL(url, type, mimeType, notificationTopic = null) { this.keyToTypeMap[url] = [type, typeFnMap[type], mimeType]; return this.fetch(url, null, notificationTopic); }
get(url, freeCache) { const dataObj = this.cacheData.cache[url]; if (freeCache) { this.free(url); } return dataObj; }
free(url) { const dataCached = this.cacheData.cache[url]; if (dataCached && dataCached.url) { window.URL.revokeObjectURL(dataCached.url); delete dataCached.url; }
delete this.cacheData.cache[url]; this.off(url); }
registerURL(key, filePattern, type, mimeType) { this.pattern.registerPattern(key, filePattern); this.keyToTypeMap[key] = [type, typeFnMap[type], mimeType]; }
unregisterURL(key) { this.pattern.unregisterPattern(key); delete this.keyToTypeMap[key]; this.off(key); }
clear() { const urlToDelete = []; Object.keys(this.cacheData.cache).forEach((url) => { urlToDelete.push(url); });
let count = urlToDelete.length; while (count) { count -= 1; this.free(urlToDelete[count]); } this.cacheData.size = 0; }
gc() { if (this.cacheData.size > this.cacheSize) { console.log('Free cache memory', this.cacheData.size); this.clear(); } }
setCacheSize(sizeBeforeGC) { this.cacheSize = sizeBeforeGC; }
getCacheSize() { return this.cacheSize; }
getMemoryUsage() { return this.cacheData.size; }
useHttpRequest() { typeFnMap.json = request.fetchJSON; typeFnMap.text = request.fetchTxt; typeFnMap.blob = request.fetchBlob; typeFnMap.arraybuffer = request.fetchArray; typeFnMap.array = request.fetchArray; return this; }
useZipContent(zipContent, options) { return new Promise((accept, reject) => { const zip = new JSZip(); let zipRoot = zip; zip.loadAsync(zipContent, options).then(() => { const metaFiles = []; zip.forEach((relativePath, zipEntry) => { if (relativePath.indexOf('index.json') !== -1) { metaFiles.push(relativePath); } }); metaFiles.sort((a, b) => a.length > b.length); const fullRootPath = metaFiles[0].split('/'); while (fullRootPath.length > 1) { const dirName = fullRootPath.shift(); zipRoot = zipRoot.folder(dirName); }
typeFnMap.json = (url, cb) => { zipRoot .file(url) .async('string') .then( (str) => { cb(null, JSON.parse(str)); }, (err) => { cb(err); } ); };
typeFnMap.text = (url, cb) => { zipRoot .file(url) .async('string') .then( (str) => { cb(null, str); }, (err) => { cb(err); } ); };
typeFnMap.blob = (url, mimeType, cb) => { zipRoot .file(url) .async('uint8array') .then( (uint8array) => { const buffer = new ArrayBuffer(uint8array.length); const view = new Uint8Array(buffer); view.set(uint8array); cb(null, new Blob([buffer], { type: mimeType })); }, (err) => { cb(err); } ); };
typeFnMap.arraybuffer = (url, cb) => { zipRoot .file(url) .async('uint8array') .then( (uint8array) => { const buffer = new ArrayBuffer(uint8array.length); const view = new Uint8Array(buffer); view.set(uint8array); cb(null, buffer); }, (err) => { cb(err); } ); };
typeFnMap.array = typeFnMap.arraybuffer;
Object.keys(this.keyToTypeMap).forEach((key) => { const array = this.keyToTypeMap[key]; array[1] = typeFnMap[array[0]]; });
accept(this); }); }); }
useHtmlContent() { typeFnMap.json = htmlRequest.json; typeFnMap.text = htmlRequest.text; typeFnMap.blob = htmlRequest.blob; typeFnMap.arraybuffer = htmlRequest.array; typeFnMap.array = htmlRequest.array;
Object.keys(this.keyToTypeMap).forEach((key) => { const array = this.keyToTypeMap[key]; array[1] = typeFnMap[array[0]]; });
return this; } }
Monologue.mixInto(DataManager);
|