123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131 |
- /**
- * Copyright (c) 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>
- */
- import { UUID } from './uuid';
- import { iterableToArray } from '../mol-data/util';
- import { ajaxGet, DataType, DataResponse, readFromFile } from './data-source';
- import { Task } from '../mol-task';
- export { AssetManager, Asset };
- type _File = File;
- type Asset = Asset.Url | Asset.File
- namespace Asset {
- export type Url = { kind: 'url', id: UUID, url: string, title?: string, body?: string, headers?: [string, string][] }
- export type File = { kind: 'file', id: UUID, name: string, file?: _File }
- export function Url(url: string, options?: { body?: string, title?: string, headers?: [string, string][] }): Url {
- return { kind: 'url', id: UUID.create22(), url, ...options };
- }
- export function File(file: _File): File {
- return { kind: 'file', id: UUID.create22(), name: file.name, file };
- }
- export function isUrl(x?: Asset): x is Url {
- return x?.kind === 'url';
- }
- export function isFile(x?: Asset): x is File {
- return x?.kind === 'file';
- }
- export interface Wrapper<T extends DataType = DataType> {
- readonly data: DataResponse<T>
- dispose: () => void
- }
- export function Wrapper<T extends DataType = DataType>(data: DataResponse<T>, asset: Asset, manager: AssetManager) {
- return {
- data,
- dispose: () => {
- manager.release(asset);
- }
- };
- }
- export function getUrl(url: string | Url) {
- return typeof url === 'string' ? url : url.url;
- }
- export function getUrlAsset(manager: AssetManager, url: string | Url, body?: string) {
- if (typeof url === 'string') {
- const asset = manager.tryFindUrl(url, body);
- return asset || Url(url, { body });
- }
- return url;
- }
- }
- class AssetManager {
- // TODO: add URL based ref-counted cache?
- // TODO: when serializing, check for duplicates?
- private _assets = new Map<string, { asset: Asset, file: File, refCount: number }>();
- get assets() {
- return iterableToArray(this._assets.values());
- }
- tryFindUrl(url: string, body?: string): Asset.Url | undefined {
- const assets = this.assets.values();
- while (true) {
- const v = assets.next();
- if (v.done) return;
- const asset = v.value.asset;
- if (Asset.isUrl(asset) && asset.url === url && (asset.body || '') === (body || '')) return asset;
- }
- }
- set(asset: Asset, file: File) {
- this._assets.set(asset.id, { asset, file, refCount: 0 });
- }
- resolve<T extends DataType>(asset: Asset, type: T, store = true): Task<Asset.Wrapper<T>> {
- if (Asset.isUrl(asset)) {
- return Task.create(`Download ${asset.title || asset.url}`, async ctx => {
- if (this._assets.has(asset.id)) {
- const entry = this._assets.get(asset.id)!;
- entry.refCount++;
- return Asset.Wrapper(await readFromFile(entry.file, type).runInContext(ctx), asset, this);
- }
- if (!store) {
- return Asset.Wrapper(await ajaxGet({ ...asset, type }).runInContext(ctx), asset, this);
- }
- const data = await ajaxGet({ ...asset, type: 'binary' }).runInContext(ctx);
- const file = new File([data], 'raw-data');
- this._assets.set(asset.id, { asset, file, refCount: 1 });
- return Asset.Wrapper(await readFromFile(file, type).runInContext(ctx), asset, this);
- });
- } else {
- return Task.create(`Read ${asset.name}`, async ctx => {
- if (this._assets.has(asset.id)) {
- const entry = this._assets.get(asset.id)!;
- entry.refCount++;
- return Asset.Wrapper(await readFromFile(entry.file, type).runInContext(ctx), asset, this);
- }
- if (!(asset.file instanceof File)) {
- throw new Error(`Cannot resolve file asset '${asset.name}' (${asset.id})`);
- }
- if (store) {
- this._assets.set(asset.id, { asset, file: asset.file, refCount: 1 });
- }
- return Asset.Wrapper(await readFromFile(asset.file, type).runInContext(ctx), asset, this);
- });
- }
- }
- release(asset: Asset) {
- const entry = this._assets.get(asset.id);
- if (!entry) return;
- entry.refCount--;
- if (entry.refCount <= 0) this._assets.delete(asset.id);
- }
- }
|