@apr144/generic-filehandle
Version:
uniform interface for accessing binary data from local files, remote HTTP resources, and browser Blob data
128 lines (110 loc) • 3.68 kB
text/typescript
import { Buffer } from 'buffer'
import { GenericFilehandle, FilehandleOptions, Stats } from './filehandle'
// Using this you can "await" the file like a normal promise
// https://blog.shovonhasan.com/using-promises-with-filereader/
function readBlobAsArrayBuffer(blob: Blob): Promise<ArrayBuffer> {
const fileReader = new FileReader()
return new Promise((resolve, reject): void => {
fileReader.onerror = (): void => {
fileReader.abort()
reject(new Error('problem reading blob'))
}
fileReader.onabort = (): void => {
reject(new Error('blob reading was aborted'))
}
fileReader.onload = (): void => {
if (fileReader.result && typeof fileReader.result !== 'string') {
resolve(fileReader.result)
} else {
reject(new Error('unknown error reading blob'))
}
}
fileReader.readAsArrayBuffer(blob)
})
}
function readBlobAsText(blob: Blob): Promise<string> {
const fileReader = new FileReader()
return new Promise((resolve, reject): void => {
fileReader.onerror = (): void => {
fileReader.abort()
reject(new Error('problem reading blob'))
}
fileReader.onabort = (): void => {
reject(new Error('blob reading was aborted'))
}
fileReader.onload = (): void => {
if (fileReader.result && typeof fileReader.result === 'string') {
resolve(fileReader.result)
} else {
reject(new Error('unknown error reading blob'))
}
}
fileReader.readAsText(blob)
})
}
/**
* Blob of binary data fetched from a local file (with FileReader).
*
* Adapted by Robert Buels and Garrett Stevens from the BlobFetchable object in
* the Dalliance Genome Explorer, which is copyright Thomas Down 2006-2011.
*/
export default class BlobFile implements GenericFilehandle {
private blob: Blob
private size: number
public constructor(blob: Blob) {
this.blob = blob
this.size = blob.size
}
public async read(
buffer: Buffer,
offset = 0,
length: number,
position = 0,
): Promise<{ bytesRead: number; buffer: Buffer }> {
// short-circuit a read of 0 bytes here, because browsers actually sometimes
// crash if you try to read 0 bytes from a local file!
if (!length) {
return { bytesRead: 0, buffer }
}
const start = position
const end = start + length
const result = await readBlobAsArrayBuffer(this.blob.slice(start, end))
const resultBuffer = Buffer.from(result)
const bytesCopied = resultBuffer.copy(buffer, offset)
return { bytesRead: bytesCopied, buffer: resultBuffer }
}
public async readFile(): Promise<Buffer>
public async readFile(options: BufferEncoding): Promise<string>
public async readFile<T extends undefined>(
options:
| Omit<FilehandleOptions, 'encoding'>
| (Omit<FilehandleOptions, 'encoding'> & { encoding: T }),
): Promise<Buffer>
public async readFile<T extends BufferEncoding>(
options: Omit<FilehandleOptions, 'encoding'> & { encoding: T },
): Promise<string>
public async readFile(
options?: FilehandleOptions | BufferEncoding,
): Promise<Buffer | string> {
let encoding
if (typeof options === 'string') {
encoding = options
} else {
encoding = options && options.encoding
}
if (encoding === 'utf8') {
return readBlobAsText(this.blob)
}
if (encoding) {
throw new Error(`unsupported encoding: ${encoding}`)
}
const result = await readBlobAsArrayBuffer(this.blob)
return Buffer.from(result)
}
public async stat(): Promise<Stats> {
return { size: this.size }
}
public async close(): Promise<void> {
return
}
}