Преглед на файлове

Address Node incompatibility in `mol-util/file-info` (#787)

* Alter getFileInfo to avoid `instanceof WebAPIClass` and simple renames

* Remove note

* Fix potentially bad `window` reference
Russell Parker преди 2 години
родител
ревизия
2bff0faff7

+ 1 - 1
src/extensions/volumes-and-segmentations/index.ts

@@ -19,7 +19,7 @@ import { VolsegEntryFromRoot, VolsegGlobalStateFromRoot, VolsegStateFromEntry }
 import { VolsegUI } from './ui';
 
 
-const DEBUGGING = window.location.hostname === 'localhost';
+const DEBUGGING = typeof window !== 'undefined' ? window?.location?.hostname === 'localhost' : false;
 
 export const VolsegVolumeServerConfig = {
     // DefaultServer: new PluginConfigItem('volseg-volume-server', DEFAULT_VOLUME_SERVER_V2),

+ 1 - 1
src/mol-io/common/file-handle.ts

@@ -16,7 +16,7 @@ export interface FileHandle {
      * @param position The offset from the beginning of the file from which data should be read.
      * @param sizeOrBuffer The buffer the data will be read from.
      * @param length The number of bytes to read.
-     * @param byteOffset The offset in the buffer at which to start writing.
+     * @param byteOffset The offset in the buffer at which to start reading.
      */
     readBuffer(position: number, sizeOrBuffer: SimpleBuffer | number, length?: number, byteOffset?: number): Promise<{ bytesRead: number, buffer: SimpleBuffer }>
 

+ 4 - 4
src/mol-plugin-state/actions/file.ts

@@ -8,13 +8,13 @@ import { PluginContext } from '../../mol-plugin/context';
 import { StateAction } from '../../mol-state';
 import { Task } from '../../mol-task';
 import { Asset } from '../../mol-util/assets';
-import { getFileInfo } from '../../mol-util/file-info';
+import { getFileNameInfo } from '../../mol-util/file-info';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { unzip } from '../../mol-util/zip/zip';
 import { PluginStateObject } from '../objects';
 
 async function processFile(file: Asset.File, plugin: PluginContext, format: string, visuals: boolean) {
-    const info = getFileInfo(file.file!);
+    const info = getFileNameInfo(file.file?.name ?? '');
     const isBinary = plugin.dataFormats.binaryExtensions.has(info.ext);
     const { data } = await plugin.builders.data.readFile({ file, isBinary });
     const provider = format === 'auto'
@@ -111,8 +111,8 @@ export const DownloadFile = StateAction.build({
                     }
                 } else {
                     const url = Asset.getUrl(params.url);
-                    const info = getFileInfo(url);
-                    await processFile(Asset.File(new File([data.obj?.data as Uint8Array], info.name)), plugin, 'auto', params.visuals);
+                    const fileName = getFileNameInfo(url).name;
+                    await processFile(Asset.File(new File([data.obj?.data as Uint8Array], fileName)), plugin, 'auto', params.visuals);
                 }
             } else {
                 const provider = plugin.dataFormats.get(params.format);

+ 3 - 3
src/mol-plugin-state/actions/structure.ts

@@ -18,7 +18,7 @@ import { Download } from '../transforms/data';
 import { CustomModelProperties, CustomStructureProperties, ModelFromTrajectory, TrajectoryFromModelAndCoordinates } from '../transforms/model';
 import { Asset } from '../../mol-util/assets';
 import { PluginConfig } from '../../mol-plugin/config';
-import { getFileInfo } from '../../mol-util/file-info';
+import { getFileNameInfo } from '../../mol-util/file-info';
 import { assertUnreachable } from '../../mol-util/type-helpers';
 import { TopologyFormatCategory } from '../formats/topology';
 import { CoordinatesFormatCategory } from '../formats/coordinates';
@@ -184,7 +184,7 @@ const DownloadStructure = StateAction.build({
             for (const download of downloadParams) {
                 const data = await plugin.builders.data.download(download, { state: { isGhost: true } });
                 const provider = format === 'auto'
-                    ? plugin.dataFormats.auto(getFileInfo(Asset.getUrl(download.url)), data.cell?.obj!)
+                    ? plugin.dataFormats.auto(getFileNameInfo(Asset.getUrl(download.url)), data.cell?.obj!)
                     : plugin.dataFormats.get(format);
                 if (!provider) throw new Error('unknown file format');
                 const trajectory = await plugin.builders.structure.parseTrajectory(data, provider);
@@ -385,7 +385,7 @@ export const LoadTrajectory = StateAction.build({
         const processFile = async (file: Asset.File | null) => {
             if (!file) throw new Error('No file selected');
 
-            const info = getFileInfo(file.file!);
+            const info = getFileNameInfo(file.file?.name ?? '');
             const isBinary = ctx.dataFormats.binaryExtensions.has(info.ext);
             const { data } = await ctx.builders.data.readFile({ file, isBinary });
             const provider = ctx.dataFormats.auto(info, data.cell?.obj!);

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

@@ -8,7 +8,7 @@
 import { PluginContext } from '../../mol-plugin/context';
 import { StateAction, StateTransformer, StateSelection } from '../../mol-state';
 import { Task } from '../../mol-task';
-import { getFileInfo } from '../../mol-util/file-info';
+import { getFileNameInfo } from '../../mol-util/file-info';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { PluginStateObject } from '../objects';
 import { Download } from '../transforms/data';
@@ -119,7 +119,7 @@ const DownloadDensity = StateAction.build({
     switch (src.name) {
         case 'url':
             downloadParams = src.params;
-            provider = src.params.format === 'auto' ? plugin.dataFormats.auto(getFileInfo(Asset.getUrl(downloadParams.url)), data.cell?.obj!) : plugin.dataFormats.get(src.params.format);
+            provider = src.params.format === 'auto' ? plugin.dataFormats.auto(getFileNameInfo(Asset.getUrl(downloadParams.url)), data.cell?.obj!) : plugin.dataFormats.get(src.params.format);
             break;
         case 'pdb-xray':
             entryId = src.params.provider.id;

+ 2 - 2
src/mol-plugin-state/builder/data.ts

@@ -7,7 +7,7 @@
 import { StateTransformer, StateTransform } from '../../mol-state';
 import { PluginContext } from '../../mol-plugin/context';
 import { Download, ReadFile, DownloadBlob, RawData } from '../transforms/data';
-import { getFileInfo } from '../../mol-util/file-info';
+import { getFileNameInfo } from '../../mol-util/file-info';
 
 export class DataBuilder {
     private get dataState() {
@@ -31,7 +31,7 @@ export class DataBuilder {
 
     async readFile(params: StateTransformer.Params<ReadFile>, options?: Partial<StateTransform.Options>) {
         const data = await this.dataState.build().toRoot().apply(ReadFile, params, options).commit({ revertOnError: true });
-        const fileInfo = getFileInfo(params.file?.file || '');
+        const fileInfo = getFileNameInfo(params.file?.file?.name ?? '');
         return { data: data, fileInfo };
     }
 

+ 3 - 3
src/mol-plugin-state/formats/provider.ts

@@ -8,7 +8,7 @@
 import { decodeMsgPack } from '../../mol-io/common/msgpack/decode';
 import { PluginContext } from '../../mol-plugin/context';
 import { StateObjectRef } from '../../mol-state';
-import { FileInfo } from '../../mol-util/file-info';
+import { FileNameInfo } from '../../mol-util/file-info';
 import { PluginStateObject } from '../objects';
 
 export interface DataFormatProvider<P = any, R = any, V = any> {
@@ -17,7 +17,7 @@ export interface DataFormatProvider<P = any, R = any, V = any> {
     category?: string,
     stringExtensions?: string[],
     binaryExtensions?: string[],
-    isApplicable?(info: FileInfo, data: string | Uint8Array): boolean,
+    isApplicable?(info: FileNameInfo, data: string | Uint8Array): boolean,
     parse(plugin: PluginContext, data: StateObjectRef<PluginStateObject.Data.Binary | PluginStateObject.Data.String>, params?: P): Promise<R>,
     visuals?(plugin: PluginContext, data: R): Promise<V> | undefined
 }
@@ -25,7 +25,7 @@ export interface DataFormatProvider<P = any, R = any, V = any> {
 export function DataFormatProvider<P extends DataFormatProvider>(provider: P): P { return provider; }
 
 type cifVariants = 'dscif' | 'segcif' | 'coreCif' | -1
-export function guessCifVariant(info: FileInfo, data: Uint8Array | string): cifVariants {
+export function guessCifVariant(info: FileNameInfo, data: Uint8Array | string): cifVariants {
     if (info.ext === 'bcif') {
         try {
             // TODO: find a way to run msgpackDecode only once

+ 2 - 2
src/mol-plugin-state/formats/registry.ts

@@ -5,7 +5,7 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { FileInfo } from '../../mol-util/file-info';
+import { FileNameInfo } from '../../mol-util/file-info';
 import { PluginStateObject } from '../objects';
 import { DataFormatProvider } from './provider';
 import { BuiltInTrajectoryFormats } from './trajectory';
@@ -78,7 +78,7 @@ export class DataFormatRegistry {
         this._map.delete(name);
     }
 
-    auto(info: FileInfo, dataStateObject: PluginStateObject.Data.Binary | PluginStateObject.Data.String) {
+    auto(info: FileNameInfo, dataStateObject: PluginStateObject.Data.Binary | PluginStateObject.Data.String) {
         for (let i = 0, il = this.list.length; i < il; ++i) {
             const p = this._list[i].provider;
 

+ 29 - 0
src/mol-util/_spec/file-info.spec.ts

@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Russell Parker <russell@benchling.com>
+ */
+
+import { getFileNameInfo } from '../file-info';
+
+describe('getFileNameInfo', () => {
+    it('handles empty string', () => {
+        expect(getFileNameInfo('')).toEqual({ path: '', name: '', ext: '', base: '', dir: '', protocol: '', query: '' });
+    });
+
+    it('handles url', () => {
+        expect(getFileNameInfo('https://models.rcsb.org/4KTC.bcif')).toEqual({ path: 'models.rcsb.org/4KTC.bcif', name: '4KTC.bcif', ext: 'bcif', base: '4KTC', dir: 'models.rcsb.org/', protocol: 'https', query: '' })
+    });
+
+    it('handles compressed url', () => {
+        expect(getFileNameInfo('https://files.rcsb.org/download/7QPD.cif.gz?foo=bar')).toEqual({ path: 'files.rcsb.org/download/7QPD.cif.gz', name: '7QPD.cif.gz', ext: 'cif', base: '7QPD', dir: 'files.rcsb.org/download/', protocol: 'https', query: '?foo=bar' })
+    });
+
+    it('handles local path', () => {
+        expect(getFileNameInfo('/usr/local/data/structure.pdb')).toEqual({ path: '/usr/local/data/structure.pdb', name: 'structure.pdb', ext: 'pdb', base: 'structure', dir: '/usr/local/data/', protocol: '', query: '' })
+    });
+
+    it('handles local path with protocol', () => {
+        expect(getFileNameInfo('file:///usr/local/data/structure.pdb')).toEqual({ path: '/usr/local/data/structure.pdb', name: 'structure.pdb', ext: 'pdb', base: 'structure', dir: '/usr/local/data/', protocol: 'file', query: '' })
+    });
+});

+ 10 - 18
src/mol-util/file-info.ts

@@ -1,17 +1,15 @@
 /**
- * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author Russell Parker <russell@benchling.com>
  */
 
-/** A File or Blob object or a URL string */
-export type FileInput = File | Blob | string
-
 // TODO only support compressed files for which uncompression support is available???
 // TODO store globally with decompression plugins?
-const compressedExtList = ['gz', 'zip'];
+const COMPRESSED_EXT_LIST = ['gz', 'zip'];
 
-export interface FileInfo {
+export interface FileNameInfo {
     path: string
     name: string
     ext: string
@@ -19,20 +17,12 @@ export interface FileInfo {
     dir: string
     protocol: string
     query: string
-    src: FileInput
 }
 
-export function getFileInfo(file: FileInput): FileInfo {
-    let path: string;
+export function getFileNameInfo(fileName: string): FileNameInfo {
+    let path: string = fileName;
     let protocol = '';
 
-    if (file instanceof File) {
-        path = file.name;
-    } else if (file instanceof Blob) {
-        path = '';
-    } else {
-        path = file;
-    }
     const queryIndex = path.lastIndexOf('?');
     const query = queryIndex !== -1 ? path.substring(queryIndex) : '';
     path = path.substring(0, queryIndex === -1 ? path.length : queryIndex);
@@ -51,12 +41,14 @@ export function getFileInfo(file: FileInput): FileInfo {
 
     const dir = path.substring(0, path.lastIndexOf('/') + 1);
 
-    if (compressedExtList.includes(ext)) {
+    if (COMPRESSED_EXT_LIST.includes(ext)) {
         const n = path.length - ext.length - 1;
+        // TODO: change logic to String.prototype.substring since substr is deprecated
         ext = (path.substr(0, n).split('.').pop() || '').toLowerCase();
         const m = base.length - ext.length - 1;
         base = base.substr(0, m);
     }
 
-    return { path, name, ext, base, dir, protocol, query, src: file };
+    // Note: it appears that most of this data never gets used.
+    return { path, name, ext, base, dir, protocol, query };
 }