@gmod/bgzf-filehandle
Version:
read from a compressed bgzip file (with .gzi) as if it were uncompressed
77 lines (66 loc) • 2.01 kB
text/typescript
import GziIndex from './gziIndex.ts'
import { unzip } from './unzip.ts'
import { concatUint8Array } from './util.ts'
import type { GenericFilehandle } from 'generic-filehandle2'
export default class BgzFilehandle {
filehandle: GenericFilehandle
gzi: GziIndex
constructor({
filehandle,
gziFilehandle,
}: {
filehandle: GenericFilehandle
gziFilehandle: GenericFilehandle
}) {
this.filehandle = filehandle
this.gzi = new GziIndex({
filehandle: gziFilehandle,
})
}
async _readAndUncompressBlock(
compressedPosition: number,
nextCompressedPosition: number,
) {
let next = nextCompressedPosition
if (!next) {
next = (await this.filehandle.stat()).size
}
// read the compressed data into the block buffer
const blockCompressedLength = next - compressedPosition
const blockBuffer = await this.filehandle.read(
blockCompressedLength,
compressedPosition,
)
// uncompress it
return unzip(blockBuffer)
}
async read(length: number, position: number) {
const blockPositions = await this.gzi.getRelevantBlocksForRead(
length,
position,
)
const blocks = [] as Uint8Array[]
for (
let blockNum = 0;
blockNum < blockPositions.length - 1;
blockNum += 1
) {
const uncompressedBuffer = await this._readAndUncompressBlock(
blockPositions[blockNum]![0],
blockPositions[blockNum + 1]![0],
)
const [, uncompressedPosition] = blockPositions[blockNum]!
const sourceOffset =
uncompressedPosition >= position ? 0 : position - uncompressedPosition
const sourceEnd =
Math.min(
position + length,
uncompressedPosition + uncompressedBuffer.length,
) - uncompressedPosition
if (sourceOffset >= 0 && sourceOffset < uncompressedBuffer.length) {
blocks.push(uncompressedBuffer.subarray(sourceOffset, sourceEnd))
}
}
return concatUint8Array(blocks)
}
}