UNPKG

molstar

Version:

A comprehensive macromolecular library.

151 lines (150 loc) 5.94 kB
/** * Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> * @author David Sehnal <david.sehnal@gmail.com> * @author Adam Midlik <midlik@gmail.com> */ import * as fs from 'fs'; import fetch from 'node-fetch'; import { FileHandle } from '../../mol-io/common/file-handle'; import { SimpleBuffer } from '../../mol-io/common/simple-buffer'; import { defaults, noop } from '../../mol-util'; import { openRead } from '../volume/common/file'; import { downloadGs } from './google-cloud-storage'; /** Create a file handle from either a file path or a URL (supports file://, http(s)://, gs:// protocols). */ export async function fileHandleFromPathOrUrl(pathOrUrl, name) { if (pathOrUrl.startsWith('gs://')) { return fileHandleFromGS(pathOrUrl, name); } else if (pathOrUrl.startsWith('http://') || pathOrUrl.startsWith('https://')) { return fileHandleFromHTTP(pathOrUrl, name); } else if (pathOrUrl.startsWith('file://')) { return fileHandleFromDescriptor(await openRead(pathOrUrl.slice('file://'.length)), name); } else { return fileHandleFromDescriptor(await openRead(pathOrUrl), name); } } export function fileHandleFromDescriptor(file, name) { if (fs === undefined) throw new Error('fs module not available'); return { name, readBuffer: (position, sizeOrBuffer, length, byteOffset) => { return new Promise((res, rej) => { let outBuffer; if (typeof sizeOrBuffer === 'number') { byteOffset = defaults(byteOffset, 0); length = defaults(length, sizeOrBuffer); outBuffer = SimpleBuffer.fromArrayBuffer(new ArrayBuffer(sizeOrBuffer)); } else { byteOffset = defaults(byteOffset, 0); length = defaults(length, sizeOrBuffer.length); outBuffer = sizeOrBuffer; } fs.read(file, outBuffer, byteOffset, length, position, (err, bytesRead, buffer) => { if (err) { rej(err); return; } if (length !== bytesRead) { console.warn(`byteCount ${length} and bytesRead ${bytesRead} differ`); } res({ bytesRead, buffer }); }); }); }, writeBuffer: (position, buffer, length) => { length = defaults(length, buffer.length); return new Promise((res, rej) => { fs.write(file, buffer, 0, length, position, (err, written) => { if (err) rej(err); else res(written); }); }); }, writeBufferSync: (position, buffer, length) => { length = defaults(length, buffer.length); return fs.writeSync(file, buffer, 0, length, position); }, close: () => { try { if (file !== void 0) fs.close(file, noop); } catch (e) { } } }; } /** Create a read-only file handle from a Google Cloud Storage URL (gs://bucket-name/file-name). */ export function fileHandleFromGS(url, name) { return { name, readBuffer: async (position, sizeOrBuffer, length, byteOffset) => { let outBuffer; if (typeof sizeOrBuffer === 'number') { length = defaults(length, sizeOrBuffer); outBuffer = SimpleBuffer.fromArrayBuffer(new ArrayBuffer(sizeOrBuffer)); } else { length = defaults(length, sizeOrBuffer.length); outBuffer = sizeOrBuffer; } let data; try { data = await downloadGs(url, { decompress: false, start: position, end: position + length - 1 }); } catch (err) { err.isFileNotFound = true; throw err; } const bytesRead = data.copy(outBuffer, byteOffset); if (length !== bytesRead) { console.warn(`byteCount ${length} and bytesRead ${bytesRead} differ`); } return { bytesRead, buffer: outBuffer }; }, writeBuffer: () => { throw new Error('Writing to Google Cloud Storage file handle not supported'); }, writeBufferSync: () => { throw new Error('Writing to Google Cloud Storage file handle not supported'); }, close: () => { }, }; } /** Create a read-only file handle from a HTTP or HTTPS URL. */ export function fileHandleFromHTTP(url, name) { let innerHandle = undefined; return { name, readBuffer: async (position, sizeOrBuffer, length, byteOffset) => { if (!innerHandle) { const response = await fetch(url); if (response.ok) { const buffer = await response.buffer(); innerHandle = FileHandle.fromBuffer(buffer, name); } else { const error = new Error(`fileHandleFromHTTP: Fetch failed with status code ${response.status}`); error.isFileNotFound = true; throw error; } } return innerHandle.readBuffer(position, sizeOrBuffer, length, byteOffset); }, writeBuffer: () => { throw new Error('Writing to HTTP(S) file handle not supported'); }, writeBufferSync: () => { throw new Error('Writing to HTTP(S) file handle not supported'); }, close: () => { }, }; }