@gmod/bgzf-filehandle
Version:
read from a compressed bgzip file (with .gzi) as if it were uncompressed
126 lines (112 loc) • 3.44 kB
text/typescript
import pkg from 'pako'
import { concatUint8Array } from './util.ts'
//@ts-ignore
const { Z_SYNC_FLUSH, Inflate } = pkg
interface VirtualOffset {
blockPosition: number
dataPosition: number
}
interface Chunk {
minv: VirtualOffset
maxv: VirtualOffset
}
// browserify-zlib, which is the zlib shim used by default in webpacked code,
// does not properly uncompress bgzf chunks that contain more than one bgzf
// block, so export an unzip function that uses pako directly if we are running
// in a browser.
export async function unzip(inputData: Uint8Array) {
try {
let strm
let pos = 0
let inflator
const blocks = [] as Uint8Array[]
do {
const remainingInput = inputData.subarray(pos)
inflator = new Inflate()
//@ts-ignore
;({ strm } = inflator)
inflator.push(remainingInput, Z_SYNC_FLUSH)
if (inflator.err) {
throw new Error(inflator.msg)
}
pos += strm.next_in
blocks.push(inflator.result as Uint8Array)
} while (strm.avail_in)
return concatUint8Array(blocks)
} catch (e) {
// return a slightly more informative error message
if (/incorrect header check/.exec(`${e}`)) {
throw new Error(
'problem decompressing block: incorrect gzip header check',
)
}
throw e
}
}
// keeps track of the position of compressed blocks in terms of file offsets,
// and a decompressed equivalent
//
// also slices (0,minv.dataPosition) and (maxv.dataPosition,end) off
export async function unzipChunkSlice(inputData: Uint8Array, chunk: Chunk) {
try {
let strm
const { minv, maxv } = chunk
let cpos = minv.blockPosition
let dpos = minv.dataPosition
const chunks = [] as Uint8Array[]
const cpositions = [] as number[]
const dpositions = [] as number[]
let i = 0
do {
const remainingInput = inputData.subarray(cpos - minv.blockPosition)
const inflator = new Inflate()
// @ts-ignore
;({ strm } = inflator)
inflator.push(remainingInput, Z_SYNC_FLUSH)
if (inflator.err) {
throw new Error(inflator.msg)
}
const buffer = inflator.result
chunks.push(buffer as Uint8Array)
let len = buffer.length
cpositions.push(cpos)
dpositions.push(dpos)
if (chunks.length === 1 && minv.dataPosition) {
// this is the first chunk, trim it
chunks[0] = chunks[0]!.subarray(minv.dataPosition)
len = chunks[0].length
}
const origCpos = cpos
cpos += strm.next_in
dpos += len
if (origCpos >= maxv.blockPosition) {
// this is the last chunk, trim it and stop decompressing. note if it is
// the same block is minv it subtracts that already trimmed part of the
// slice length
chunks[i] = chunks[i]!.subarray(
0,
maxv.blockPosition === minv.blockPosition
? maxv.dataPosition - minv.dataPosition + 1
: maxv.dataPosition + 1,
)
cpositions.push(cpos)
dpositions.push(dpos)
break
}
i++
} while (strm.avail_in)
return {
buffer: concatUint8Array(chunks),
cpositions,
dpositions,
}
} catch (e) {
// return a slightly more informative error message
if (/incorrect header check/.exec(`${e}`)) {
throw new Error(
'problem decompressing block: incorrect gzip header check',
)
}
throw e
}
}