diff --git a/src/extensions/volumes-and-segmentations/index.ts b/src/extensions/volumes-and-segmentations/index.ts index 8f66d7ed10805ae02c0cb8e8ceb32d97785e4a30..d62316598de4208898a15e1ca81aa73375079970 100644 --- a/src/extensions/volumes-and-segmentations/index.ts +++ b/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), diff --git a/src/mol-io/common/file-handle.ts b/src/mol-io/common/file-handle.ts index e0b18b39c524a3ad4309400815b6f032aa68b498..724d18d8a382ad8d4f2019aa22f7a877168a009e 100644 --- a/src/mol-io/common/file-handle.ts +++ b/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 }> diff --git a/src/mol-plugin-state/actions/file.ts b/src/mol-plugin-state/actions/file.ts index ee81b0793d6aae3808e1a467f75ab1fc00393dfe..24e01503ad6b8433d4764d1eb028e3e595d3a4ed 100644 --- a/src/mol-plugin-state/actions/file.ts +++ b/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); diff --git a/src/mol-plugin-state/actions/structure.ts b/src/mol-plugin-state/actions/structure.ts index 61765b5a22b2ebc3d3b3592e98c5f0a48357fd20..b123cec15b73bb89c0b697a31ccbef4547cd4e90 100644 --- a/src/mol-plugin-state/actions/structure.ts +++ b/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!); diff --git a/src/mol-plugin-state/actions/volume.ts b/src/mol-plugin-state/actions/volume.ts index fad2beeca7da71321ecb2c52eadb3d0e711759f8..750509ba7a07ab1d280b7c3e943322ab14ea2ff8 100644 --- a/src/mol-plugin-state/actions/volume.ts +++ b/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; diff --git a/src/mol-plugin-state/builder/data.ts b/src/mol-plugin-state/builder/data.ts index 16dec53c0615b67b0905c22e99970edb865874f2..b57cb0fe960104457cb821e2366ff2ffbc8dfb2f 100644 --- a/src/mol-plugin-state/builder/data.ts +++ b/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 }; } diff --git a/src/mol-plugin-state/formats/provider.ts b/src/mol-plugin-state/formats/provider.ts index 6fe0bed66998cbfb2f99ccefdcd4e9cf2be566f7..dc916cdf6f6f4f57fb7c982a98e07a1da77a010c 100644 --- a/src/mol-plugin-state/formats/provider.ts +++ b/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 diff --git a/src/mol-plugin-state/formats/registry.ts b/src/mol-plugin-state/formats/registry.ts index 090ce3f321635fc345a5542e6db394658fb1c49a..326f9093344a67685f41bd197955bb779739008b 100644 --- a/src/mol-plugin-state/formats/registry.ts +++ b/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; diff --git a/src/mol-util/_spec/file-info.spec.ts b/src/mol-util/_spec/file-info.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..aeb530fe37a4902a02b881c40a6ee56d89b62722 --- /dev/null +++ b/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: '' }) + }); +}); diff --git a/src/mol-util/file-info.ts b/src/mol-util/file-info.ts index 0477631a42e2e56ef40714e9831a2e889b827597..ed2fbfedeab823209b612214ada4204e3f6c954b 100644 --- a/src/mol-util/file-info.ts +++ b/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 }; } \ No newline at end of file