Browse Source

support plain string in ParamDefinition.UrlParam

David Sehnal 5 years ago
parent
commit
75ccded612

+ 1 - 1
src/mol-plugin-state/actions/volume.ts

@@ -114,7 +114,7 @@ const DownloadDensity = StateAction.build({
     switch (src.name) {
         case 'url':
             downloadParams = src.params;
-            provider = src.params.format === 'auto' ? plugin.dataFormats.auto(getFileInfo(downloadParams.url.url), data.cell?.obj!) : plugin.dataFormats.get(src.params.format);
+            provider = src.params.format === 'auto' ? plugin.dataFormats.auto(getFileInfo(Asset.getUrl(downloadParams.url)), data.cell?.obj!) : plugin.dataFormats.get(src.params.format);
             break;
         case 'pdb-xray':
             provider = src.params.provider.server === 'pdbe'

+ 5 - 4
src/mol-plugin-state/transforms/data.ts

@@ -47,11 +47,12 @@ const Download = PluginStateTransform.BuiltIn({
 })({
     apply({ params: p, cache }, plugin: PluginContext) {
         return Task.create('Download', async ctx => {
-            const asset = await plugin.managers.asset.resolve(p.url, p.isBinary ? 'binary' : 'string').runInContext(ctx);
+            let url = Asset.getUrlAsset(p.url, plugin.managers.asset);
+            const asset = await plugin.managers.asset.resolve(url, p.isBinary ? 'binary' : 'string').runInContext(ctx);
             (cache as any).asset = asset;
             return p.isBinary
-                ? new SO.Data.Binary(asset.data as Uint8Array, { label: p.label ? p.label : p.url.url })
-                : new SO.Data.String(asset.data as string, { label: p.label ? p.label : p.url.url });
+                ? new SO.Data.Binary(asset.data as Uint8Array, { label: p.label ? p.label : url.url })
+                : new SO.Data.String(asset.data as string, { label: p.label ? p.label : url.url });
         });
     },
     dispose({ cache }) {
@@ -60,7 +61,7 @@ const Download = PluginStateTransform.BuiltIn({
     update({ oldParams, newParams, b }) {
         if (oldParams.url !== newParams.url || oldParams.isBinary !== newParams.isBinary) return StateTransformer.UpdateResult.Recreate;
         if (oldParams.label !== newParams.label) {
-            b.label = newParams.label || newParams.url.url;
+            b.label = newParams.label || ((typeof newParams.url === 'string') ? newParams.url : newParams.url.url);
             return StateTransformer.UpdateResult.Updated;
         }
         return StateTransformer.UpdateResult.Unchanged;

+ 2 - 2
src/mol-plugin-ui/controls/parameters.tsx

@@ -790,7 +790,7 @@ export class Mat4Control extends React.PureComponent<ParamProps<PD.Mat4>, { isEx
 export class UrlControl extends SimpleParam<PD.UrlParam> {
     onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
         const value = e.target.value;
-        if (value !== this.props.value.url) {
+        if (value !== Asset.getUrl(this.props.value || '')) {
             this.update(Asset.Url(value));
         }
     }
@@ -805,7 +805,7 @@ export class UrlControl extends SimpleParam<PD.UrlParam> {
     renderControl() {
         const placeholder = this.props.param.label || camelCaseToWords(this.props.name);
         return <input type='text'
-            value={this.props.value.url || ''}
+            value={Asset.getUrl(this.props.value || '')}
             placeholder={placeholder}
             onChange={this.onChange}
             onKeyPress={this.props.onEnter ? this.onKeyPress : void 0}

+ 36 - 7
src/mol-util/assets.ts

@@ -44,27 +44,51 @@ namespace Asset {
 
         }
     }
+
+    export function getUrl(url: string | Url) {
+        return typeof url === 'string' ? url : url.url;
+    }
+
+    export function getUrlAsset(url: string | Url, manager: AssetManager) {
+        if (typeof url === 'string') {
+            const asset = manager.tryFindUrl(url);
+            return asset || Url(url);
+        }
+        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 }>();
+    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 });
+        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)) {
-                    return new Asset.Wrapper(await readFromFile(this._assets.get(asset.id)!.file, type).runInContext(ctx), asset, this);
+                    const entry = this._assets.get(asset.id)!;
+                    entry.refCount++;
+                    return new Asset.Wrapper(await readFromFile(entry.file, type).runInContext(ctx), asset, this);
                 }
 
                 if (!store) {
@@ -73,19 +97,21 @@ class AssetManager {
 
                 const data = await ajaxGet({ ...asset, type: 'binary' }).runInContext(ctx);
                 const file = new File([data], 'raw-data');
-                this._assets.set(asset.id, { asset, file });
+                this._assets.set(asset.id, { asset, file, refCount: 1 });
                 return new 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)) {
-                    return new Asset.Wrapper(await readFromFile(this._assets.get(asset.id)!.file, type).runInContext(ctx), asset, this);
+                    const entry = this._assets.get(asset.id)!;
+                    entry.refCount++;
+                    return new 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 });
+                    this._assets.set(asset.id, { asset, file: asset.file, refCount: 1 });
                 }
                 return new Asset.Wrapper(await readFromFile(asset.file, type).runInContext(ctx), asset, this);
             });
@@ -93,6 +119,9 @@ class AssetManager {
     }
 
     release(asset: Asset) {
-        this._assets.delete(asset.id);
+        const entry = this._assets.get(asset.id);
+        if (!entry) return;
+        entry.refCount--;
+        if (entry.refCount <= 0) this._assets.delete(asset.id);
     }
 }

+ 3 - 3
src/mol-util/data-source.ts

@@ -282,7 +282,7 @@ function ajaxGetInternal<T extends DataType>(title: string | undefined, url: str
 }
 
 export type AjaxGetManyEntry = { kind: 'ok', id: string, result: Asset.Wrapper<'string' | 'binary'> } | { kind: 'error', id: string, error: any }
-export async function ajaxGetMany(ctx: RuntimeContext, assetManager: AssetManager, sources: { id: string, url: Asset.Url, isBinary?: boolean, canFail?: boolean }[], maxConcurrency: number) {
+export async function ajaxGetMany(ctx: RuntimeContext, assetManager: AssetManager, sources: { id: string, url: Asset.Url | string, isBinary?: boolean, canFail?: boolean }[], maxConcurrency: number) {
     const len = sources.length;
     const slots: AjaxGetManyEntry[] = new Array(sources.length);
 
@@ -293,7 +293,7 @@ export async function ajaxGetMany(ctx: RuntimeContext, assetManager: AssetManage
         const current = sources[currentSrc];
 
         promises.push(wrapPromise(currentSrc, current.id,
-            assetManager.resolve(current.url, current.isBinary ? 'binary' : 'string').runAsChild(ctx)));
+            assetManager.resolve(Asset.getUrlAsset(current.url, assetManager), current.isBinary ? 'binary' : 'string').runAsChild(ctx)));
         promiseKeys.push(currentSrc);
     }
 
@@ -315,7 +315,7 @@ export async function ajaxGetMany(ctx: RuntimeContext, assetManager: AssetManage
         promiseKeys = promiseKeys.filter(_filterRemoveIndex, idx);
         if (currentSrc < len) {
             const current = sources[currentSrc];
-            const asset = assetManager.resolve(current.url, current.isBinary ? 'binary' : 'string').runAsChild(ctx);
+            const asset = assetManager.resolve(Asset.getUrlAsset(current.url, assetManager), current.isBinary ? 'binary' : 'string').runAsChild(ctx);
             promises.push(wrapPromise(currentSrc, current.id, asset));
             promiseKeys.push(currentSrc);
             currentSrc++;

+ 1 - 1
src/mol-util/param-definition.ts

@@ -148,7 +148,7 @@ export namespace ParamDefinition {
         return setInfo<Mat4>({ type: 'mat4', defaultValue }, info);
     }
 
-    export interface UrlParam extends Base<Asset.Url> {
+    export interface UrlParam extends Base<Asset.Url | string> {
         type: 'url'
     }
     export function Url(url: string | { url: string, body?: string }, info?: Info): UrlParam {