UNPKG

molstar

Version:

A comprehensive macromolecular library.

382 lines (381 loc) 16.7 kB
/** * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> * @author Alexander Rose <alexander.rose@weirdbyte.de> * * Adapted from LiteMol */ import { __awaiter, __generator } from "tslib"; import { Task } from '../mol-task'; import { unzip, ungzip } from './zip/zip'; import { utf8Read } from '../mol-io/common/utf8'; import { Asset } from './assets'; // polyfill XMLHttpRequest in node.js var XHR = typeof document === 'undefined' ? require('xhr2') : XMLHttpRequest; export var DataCompressionMethod; (function (DataCompressionMethod) { DataCompressionMethod[DataCompressionMethod["None"] = 0] = "None"; DataCompressionMethod[DataCompressionMethod["Gzip"] = 1] = "Gzip"; DataCompressionMethod[DataCompressionMethod["Zip"] = 2] = "Zip"; })(DataCompressionMethod || (DataCompressionMethod = {})); export function readStringFromFile(file) { return readFromFileInternal(file, 'string'); } export function readUint8ArrayFromFile(file) { return readFromFileInternal(file, 'binary'); } export function readFromFile(file, type) { return readFromFileInternal(file, type); } export function ajaxGet(params) { if (typeof params === 'string') return ajaxGetInternal(params, params, 'string'); return ajaxGetInternal(params.title, params.url, params.type || 'string', params.body, params.headers); } function isDone(data) { if (data instanceof FileReader) { return data.readyState === FileReader.DONE; } else if (data instanceof XMLHttpRequest) { return data.readyState === XMLHttpRequest.DONE; } throw new Error('unknown data type'); } function genericError(isDownload) { if (isDownload) return 'Failed to download data. Possible reasons: Resource is not available, or CORS is not allowed on the server.'; return 'Failed to open file.'; } function readData(ctx, action, data) { return new Promise(function (resolve, reject) { // first check if data reading is already done if (isDone(data)) { var error = data.error; if (error !== null && error !== undefined) { reject(error !== null && error !== void 0 ? error : genericError(data instanceof XMLHttpRequest)); } else { resolve(data); } return; } var hasError = false; data.onerror = function (e) { if (hasError) return; var error = e.target.error; reject(error !== null && error !== void 0 ? error : genericError(data instanceof XMLHttpRequest)); }; data.onprogress = function (e) { if (!ctx.shouldUpdate || hasError) return; try { if (e.lengthComputable) { ctx.update({ message: action, isIndeterminate: false, current: e.loaded, max: e.total }); } else { ctx.update({ message: "".concat(action, " ").concat((e.loaded / 1024 / 1024).toFixed(2), " MB"), isIndeterminate: true }); } } catch (e) { hasError = true; reject(e); } }; data.onload = function (e) { resolve(data); }; }); } function getCompression(name) { return /\.gz$/i.test(name) ? DataCompressionMethod.Gzip : /\.zip$/i.test(name) ? DataCompressionMethod.Zip : DataCompressionMethod.None; } var reFilterPath = /^(__MACOSX|.DS_Store)/; function decompress(ctx, data, compression) { return __awaiter(this, void 0, void 0, function () { var _a, parsed, names; return __generator(this, function (_b) { switch (_b.label) { case 0: _a = compression; switch (_a) { case DataCompressionMethod.None: return [3 /*break*/, 1]; case DataCompressionMethod.Gzip: return [3 /*break*/, 2]; case DataCompressionMethod.Zip: return [3 /*break*/, 3]; } return [3 /*break*/, 5]; case 1: return [2 /*return*/, data]; case 2: return [2 /*return*/, ungzip(ctx, data)]; case 3: return [4 /*yield*/, unzip(ctx, data.buffer)]; case 4: parsed = _b.sent(); names = Object.keys(parsed).filter(function (n) { return !reFilterPath.test(n); }); if (names.length !== 1) throw new Error('can only decompress zip files with a single entry'); return [2 /*return*/, parsed[names[0]]]; case 5: return [2 /*return*/]; } }); }); } function processFile(ctx, reader, type, compression) { return __awaiter(this, void 0, void 0, function () { var result, data, decompressed, parser; return __generator(this, function (_a) { switch (_a.label) { case 0: result = reader.result; data = result instanceof ArrayBuffer ? new Uint8Array(result) : result; if (data === null) throw new Error('no data given'); if (!(compression !== DataCompressionMethod.None)) return [3 /*break*/, 4]; if (!(data instanceof Uint8Array)) throw new Error('need Uint8Array for decompression'); return [4 /*yield*/, decompress(ctx, data, compression)]; case 1: decompressed = _a.sent(); if (!(type === 'string')) return [3 /*break*/, 3]; return [4 /*yield*/, ctx.update({ message: 'Decoding text...' })]; case 2: _a.sent(); data = utf8Read(decompressed, 0, decompressed.length); return [3 /*break*/, 4]; case 3: data = decompressed; _a.label = 4; case 4: if (!(type === 'binary' && data instanceof Uint8Array)) return [3 /*break*/, 5]; return [2 /*return*/, data]; case 5: if (!(type === 'zip' && data instanceof Uint8Array)) return [3 /*break*/, 7]; return [4 /*yield*/, unzip(ctx, data.buffer)]; case 6: return [2 /*return*/, _a.sent()]; case 7: if (type === 'string' && typeof data === 'string') { return [2 /*return*/, data]; } else if (type === 'xml' && typeof data === 'string') { parser = new DOMParser(); return [2 /*return*/, parser.parseFromString(data, 'application/xml')]; } else if (type === 'json' && typeof data === 'string') { return [2 /*return*/, JSON.parse(data)]; } _a.label = 8; case 8: throw new Error("could not get requested response data '".concat(type, "'")); } }); }); } function readFromFileInternal(file, type) { var _this = this; var reader = void 0; return Task.create('Read File', function (ctx) { return __awaiter(_this, void 0, void 0, function () { var compression, fileReader; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, , 5, 6]); reader = new FileReader(); compression = type === 'zip' ? DataCompressionMethod.None : getCompression(file.name); if (type === 'binary' || type === 'zip' || compression !== DataCompressionMethod.None) { reader.readAsArrayBuffer(file); } else { reader.readAsText(file); } return [4 /*yield*/, ctx.update({ message: 'Opening file...', canAbort: true })]; case 1: _a.sent(); return [4 /*yield*/, readData(ctx, 'Reading...', reader)]; case 2: fileReader = _a.sent(); return [4 /*yield*/, ctx.update({ message: 'Processing file...', canAbort: false })]; case 3: _a.sent(); return [4 /*yield*/, processFile(ctx, fileReader, type, compression)]; case 4: return [2 /*return*/, _a.sent()]; case 5: reader = void 0; return [7 /*endfinally*/]; case 6: return [2 /*return*/]; } }); }); }, function () { if (reader) reader.abort(); }); } var RequestPool = /** @class */ (function () { function RequestPool() { } RequestPool.get = function () { if (this.pool.length) { return this.pool.pop(); } return new XHR(); }; RequestPool.emptyFunc = function () { }; RequestPool.deposit = function (req) { if (this.pool.length < this.poolSize) { req.onabort = RequestPool.emptyFunc; req.onerror = RequestPool.emptyFunc; req.onload = RequestPool.emptyFunc; req.onprogress = RequestPool.emptyFunc; this.pool.push(req); } }; RequestPool.pool = []; RequestPool.poolSize = 15; return RequestPool; }()); function processAjax(req, type) { if (req.status >= 200 && req.status < 400) { var response = req.response; RequestPool.deposit(req); if ((type === 'binary' || type === 'zip') && response instanceof ArrayBuffer) { return new Uint8Array(response); } else if (type === 'string' && typeof response === 'string') { return response; } else if (type === 'xml' && response instanceof XMLDocument) { return response; } else if (type === 'json' && typeof response === 'object') { return response; } throw new Error("could not get requested response data '".concat(type, "'")); } else { RequestPool.deposit(req); throw new Error("Download failed with status code ".concat(req.status)); } } function getRequestResponseType(type) { switch (type) { case 'json': return 'json'; case 'xml': return 'document'; case 'string': return 'text'; case 'binary': return 'arraybuffer'; case 'zip': return 'arraybuffer'; } } function ajaxGetInternal(title, url, type, body, headers) { var _this = this; var xhttp = void 0; return Task.create(title ? title : 'Download', function (ctx) { return __awaiter(_this, void 0, void 0, function () { var _a, headers_1, _b, name_1, value, req, result; return __generator(this, function (_c) { switch (_c.label) { case 0: xhttp = RequestPool.get(); xhttp.open(body ? 'post' : 'get', url, true); if (headers) { for (_a = 0, headers_1 = headers; _a < headers_1.length; _a++) { _b = headers_1[_a], name_1 = _b[0], value = _b[1]; xhttp.setRequestHeader(name_1, value); } } xhttp.responseType = getRequestResponseType(type); xhttp.send(body); return [4 /*yield*/, ctx.update({ message: 'Waiting for server...', canAbort: true })]; case 1: _c.sent(); return [4 /*yield*/, readData(ctx, 'Downloading...', xhttp)]; case 2: req = _c.sent(); xhttp = void 0; // guard against reuse, help garbage collector return [4 /*yield*/, ctx.update({ message: 'Parsing response...', canAbort: false })]; case 3: _c.sent(); result = processAjax(req, type); return [2 /*return*/, result]; } }); }); }, function () { if (xhttp) { xhttp.abort(); xhttp = void 0; // guard against reuse, help garbage collector } }); } export function ajaxGetMany(ctx, assetManager, sources, maxConcurrency) { return __awaiter(this, void 0, void 0, function () { var len, slots, promises, promiseKeys, currentSrc, _i, current, done, r, src, idx, current, asset; return __generator(this, function (_a) { switch (_a.label) { case 0: len = sources.length; slots = new Array(sources.length); return [4 /*yield*/, ctx.update({ message: 'Downloading...', current: 0, max: len })]; case 1: _a.sent(); promises = [], promiseKeys = []; currentSrc = 0; for (_i = Math.min(len, maxConcurrency); currentSrc < _i; currentSrc++) { current = sources[currentSrc]; promises.push(wrapPromise(currentSrc, current.id, assetManager.resolve(Asset.getUrlAsset(assetManager, current.url), current.isBinary ? 'binary' : 'string').runAsChild(ctx))); promiseKeys.push(currentSrc); } done = 0; _a.label = 2; case 2: if (!(promises.length > 0)) return [3 /*break*/, 6]; return [4 /*yield*/, Promise.race(promises)]; case 3: r = _a.sent(); src = sources[r.index]; idx = promiseKeys.indexOf(r.index); done++; if (r.kind === 'error' && !src.canFail) { // TODO: cancel other downloads throw new Error("".concat(src.url, ": ").concat(r.error)); } if (!ctx.shouldUpdate) return [3 /*break*/, 5]; return [4 /*yield*/, ctx.update({ message: 'Downloading...', current: done, max: len })]; case 4: _a.sent(); _a.label = 5; case 5: slots[r.index] = r; promises = promises.filter(_filterRemoveIndex, idx); promiseKeys = promiseKeys.filter(_filterRemoveIndex, idx); if (currentSrc < len) { current = sources[currentSrc]; asset = assetManager.resolve(Asset.getUrlAsset(assetManager, current.url), current.isBinary ? 'binary' : 'string').runAsChild(ctx); promises.push(wrapPromise(currentSrc, current.id, asset)); promiseKeys.push(currentSrc); currentSrc++; } return [3 /*break*/, 2]; case 6: return [2 /*return*/, slots]; } }); }); } function _filterRemoveIndex(_, i) { return this !== i; } function wrapPromise(index, id, p) { return __awaiter(this, void 0, void 0, function () { var result, error_1; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 2, , 3]); return [4 /*yield*/, p]; case 1: result = _a.sent(); return [2 /*return*/, { kind: 'ok', result: result, index: index, id: id }]; case 2: error_1 = _a.sent(); return [2 /*return*/, { kind: 'error', error: error_1, index: index, id: id }]; case 3: return [2 /*return*/]; } }); }); }