assets.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. /**
  2. * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  6. */
  7. import { UUID } from './uuid';
  8. import { iterableToArray } from '../mol-data/util';
  9. import { ajaxGet, DataType, DataResponse, readFromFile } from './data-source';
  10. import { Task } from '../mol-task';
  11. export { AssetManager, Asset };
  12. type _File = File;
  13. type Asset = Asset.Url | Asset.File
  14. namespace Asset {
  15. export type Url = { kind: 'url', id: UUID, url: string, title?: string, body?: string, headers?: [string, string][] }
  16. export type File = { kind: 'file', id: UUID, name: string, file?: _File }
  17. export function Url(url: string, options?: { body?: string, title?: string, headers?: [string, string][] }): Url {
  18. return { kind: 'url', id: UUID.create22(), url, ...options };
  19. }
  20. export function File(file: _File): File {
  21. return { kind: 'file', id: UUID.create22(), name: file.name, file };
  22. }
  23. export function isUrl(x?: Asset): x is Url {
  24. return x?.kind === 'url';
  25. }
  26. export function isFile(x?: Asset): x is File {
  27. return x?.kind === 'file';
  28. }
  29. export interface Wrapper<T extends DataType = DataType> {
  30. readonly data: DataResponse<T>
  31. dispose: () => void
  32. }
  33. export function Wrapper<T extends DataType = DataType>(data: DataResponse<T>, asset: Asset, manager: AssetManager) {
  34. return {
  35. data,
  36. dispose: () => {
  37. manager.release(asset);
  38. }
  39. };
  40. }
  41. export function getUrl(url: string | Url) {
  42. return typeof url === 'string' ? url : url.url;
  43. }
  44. export function getUrlAsset(manager: AssetManager, url: string | Url, body?: string) {
  45. if (typeof url === 'string') {
  46. const asset = manager.tryFindUrl(url, body);
  47. return asset || Url(url, { body });
  48. }
  49. return url;
  50. }
  51. }
  52. class AssetManager {
  53. // TODO: add URL based ref-counted cache?
  54. // TODO: when serializing, check for duplicates?
  55. private _assets = new Map<string, { asset: Asset, file: File, refCount: number }>();
  56. get assets() {
  57. return iterableToArray(this._assets.values());
  58. }
  59. tryFindUrl(url: string, body?: string): Asset.Url | undefined {
  60. const assets = this.assets.values();
  61. while (true) {
  62. const v = assets.next();
  63. if (v.done) return;
  64. const asset = v.value.asset;
  65. if (Asset.isUrl(asset) && asset.url === url && (asset.body || '') === (body || '')) return asset;
  66. }
  67. }
  68. set(asset: Asset, file: File) {
  69. this._assets.set(asset.id, { asset, file, refCount: 0 });
  70. }
  71. resolve<T extends DataType>(asset: Asset, type: T, store = true): Task<Asset.Wrapper<T>> {
  72. if (Asset.isUrl(asset)) {
  73. return Task.create(`Download ${asset.title || asset.url}`, async ctx => {
  74. if (this._assets.has(asset.id)) {
  75. const entry = this._assets.get(asset.id)!;
  76. entry.refCount++;
  77. return Asset.Wrapper(await readFromFile(entry.file, type).runInContext(ctx), asset, this);
  78. }
  79. if (!store) {
  80. return Asset.Wrapper(await ajaxGet({ ...asset, type }).runInContext(ctx), asset, this);
  81. }
  82. const data = await ajaxGet({ ...asset, type: 'binary' }).runInContext(ctx);
  83. const file = new File([data], 'raw-data');
  84. this._assets.set(asset.id, { asset, file, refCount: 1 });
  85. return Asset.Wrapper(await readFromFile(file, type).runInContext(ctx), asset, this);
  86. });
  87. } else {
  88. return Task.create(`Read ${asset.name}`, async ctx => {
  89. if (this._assets.has(asset.id)) {
  90. const entry = this._assets.get(asset.id)!;
  91. entry.refCount++;
  92. return Asset.Wrapper(await readFromFile(entry.file, type).runInContext(ctx), asset, this);
  93. }
  94. if (!(asset.file instanceof File)) {
  95. throw new Error(`Cannot resolve file asset '${asset.name}' (${asset.id})`);
  96. }
  97. if (store) {
  98. this._assets.set(asset.id, { asset, file: asset.file, refCount: 1 });
  99. }
  100. return Asset.Wrapper(await readFromFile(asset.file, type).runInContext(ctx), asset, this);
  101. });
  102. }
  103. }
  104. release(asset: Asset) {
  105. const entry = this._assets.get(asset.id);
  106. if (!entry) return;
  107. entry.refCount--;
  108. if (entry.refCount <= 0) this._assets.delete(asset.id);
  109. }
  110. }