@gmod/jbrowse
Version:
JBrowse - client-side genome browser
169 lines (149 loc) • 5 kB
JavaScript
cjsRequire('whatwg-fetch')
const tenaciousFetch = cjsRequire('tenacious-fetch').default
const { HttpRangeFetcher } = cjsRequire('http-range-fetcher')
const { Buffer } = cjsRequire('buffer')
define( [ 'dojo/_base/declare',
'JBrowse/Util',
'JBrowse/Model/FileBlob',
],
function( declare, Util, FileBlob ) {
function fetchBinaryRange(url, start, end) {
const requestDate = new Date()
return tenaciousFetch(url, {
method: 'GET',
headers: { range: `bytes=${start}-${end}` },
retries: 5,
retryDelay: 1000, // 1 sec, 2 sec, 3 sec
retryStatus: [500, 404, 503],
onRetry: ({retriesLeft, retryDelay}) => {
console.warn(`${url} bytes ${start}-${end} request failed, retrying (${retriesLeft} retries left)`)
}
}).then(res => {
const responseDate = new Date()
if (res.status !== 206 && res.status !== 200)
throw new Error(
`HTTP ${res.status} when fetching ${url} bytes ${start}-${end}`,
)
// translate the Headers object into a regular key -> value object.
// will miss duplicate headers of course
const headers = {}
for (let entry of res.headers.entries()) {
headers[entry[0]] = entry[1]
}
if (Util.isElectron()) {
// electron charmingly returns HTTP 200 for byte range requests,
// and does not fill in content-range. so we will fill it in
try {
const fs = electronRequire("fs"); //Load the filesystem module
const stats = fs.statSync(Util.unReplacePath(url))
headers['content-range'] = `${start}-${end}/${stats.size}`
} catch(e) {
console.error('Could not get size of file', url, e)
}
} else if(res.status === 200) {
throw new Error(
`HTTP ${res.status} when fetching ${url} bytes ${start}-${end}`,
)
}
// return the response headers, and the data buffer
return res.arrayBuffer()
.then(arrayBuffer => ({
headers,
requestDate,
responseDate,
buffer: Buffer.from(arrayBuffer),
}))
})
}
const globalCache = new HttpRangeFetcher({
fetch: fetchBinaryRange,
size: 50 * 1024, // 50MB
chunkSize: Math.pow(2,18), // 256KB
aggregationTime: 50,
})
var XHRBlob = declare( FileBlob,
/**
* @lends JBrowse.Model.XHRBlob.prototype
*/
{
/**
* Blob of binary data fetched with an XMLHTTPRequest.
*
* Adapted by Robert Buels from the URLFetchable object in the
* Dalliance Genome Explorer, which was is copyright Thomas Down
* 2006-2011.
* @constructs
*/
constructor(url, start, end, opts) {
if (!opts) {
if (typeof start === 'object') {
opts = start;
start = undefined;
} else {
opts = {};
}
}
this.url = url;
this.start = start || 0;
if (end) {
this.end = end;
}
this.opts = opts;
},
slice(s, l) {
var ns = this.start, ne = this.end;
if (ns && s) {
ns = ns + s;
} else {
ns = s || ns;
}
if (l && ns) {
ne = ns + l - 1;
} else {
ne = ne || l - 1;
}
return new XHRBlob(this.url, ns, ne, this.opts);
},
fetch( callback, failCallback ) {
const length = this.end === undefined ? undefined : this.end - this.start + 1
if (length < 0) debugger
globalCache.getRange(this.url, this.start, length)
.then(
this._getResponseArrayBuffer.bind(this,callback),
failCallback,
)
},
async fetchBufferPromise() {
const length = this.end === undefined ? undefined : this.end - this.start + 1
if (length < 0) debugger
const range = await globalCache.getRange(this.url, this.start, length)
return range.buffer
},
_getResponseArrayBuffer(callback,{buffer}) {
if (buffer.buffer) {
const arrayBuffer = buffer.buffer.slice(
buffer.byteOffset, buffer.byteOffset + buffer.byteLength
)
callback(arrayBuffer)
} else throw new Error('could not convert response to ArrayBuffer')
},
read( offset, length, callback, failCallback ) {
globalCache.getRange(this.url, this.start + offset, length)
.then(
this._getResponseArrayBuffer.bind(this,callback),
failCallback,
)
},
async readBufferPromise(offset, length) {
const range = await globalCache.getRange(this.url, this.start + offset, length)
return range.buffer
},
stat(callback, failCallback) {
this.statPromise().then(callback, failCallback)
},
statPromise() {
return globalCache.stat(this.url)
}
});
return XHRBlob;
});