@gmod/jbrowse
Version:
JBrowse - client-side genome browser
154 lines (137 loc) • 5.18 kB
JavaScript
define( [ 'dojo/_base/declare',
'dojo/_base/lang',
'dojo/_base/array',
'dojo/request',
'dojo/promise/all',
'dojo/Deferred',
'JBrowse/Store/SeqFeature',
'JBrowse/Store/LRUCache',
'JBrowse/Util',
'JBrowse/Model/SimpleFeature',
'JBrowse/Digest/Crc32'
],
function(
declare,
lang,
array,
request,
all,
Deferred,
SeqFeatureStore,
LRUCache,
Util,
SimpleFeature,
Crc32
) {
return declare( SeqFeatureStore,
{
/**
* Storage backend for sequences broken up into chunks, stored and
* served as static text files.
* @constructs
*/
constructor: function(args) {
this.compress = args.compress;
this.urlTemplate = this.getConf('urlTemplate',[]);
if( ! this.urlTemplate ) {
throw "no urlTemplate provided, cannot open sequence store";
}
this.baseUrl = args.baseUrl;
this.seqChunkSize = args.seqChunkSize;
},
getFeatures: function( query, featureCallback, endCallback, errorCallback ) {
errorCallback = errorCallback || function(e) { console.error(e); };
var refname = query.ref;
// if they both regularize to the same thing, use this.refSeq.name since that is guaranteed to be from refseq store
if( ! this.browser.compareReferenceNames( this.refSeq.name, refname ) )
refname = this.refSeq.name;
var chunkSize = ( refname == this.refSeq.name && this.refSeq.seqChunkSize )
|| this.seqChunkSize
|| ( this.compress ? 80000 : 20000 );
var chunksProcessed = 0;
var cache = this.chunkCache = this.chunkCache || new LRUCache({
name: 'SequenceCache',
fillCallback: dojo.hitch( this, '_readChunkItems' ),
sizeFunction: function( chunkItems ) {
return chunkItems.length;
},
maxSize: 100 // cache up to 100 seqchunks
});
var firstChunk = Math.floor( Math.max(0,query.start) / chunkSize );
var lastChunk = Math.floor( (query.end - 1) / chunkSize );
var chunks = [];
for (var i = firstChunk; i <= lastChunk; i++) {
chunks.push({refname: refname, chunkNum: i, chunkSize: chunkSize});
}
var haveError;
array.forEach(chunks, function( c ) {
cache.get( c, function(item, e ) {
if( e && !haveError )
errorCallback( e );
if(( haveError = haveError || e )) {
return;
}
featureCallback( item );
if( ++chunksProcessed == chunks.length ) {
endCallback();
}
});
});
},
_readChunkItems: function( chunk, callback ) {
var thisB = this;
var d = new Deferred(); // need to have our own deferred that is resolved to '' on 404
var sequrl = this.resolveUrl(
this.urlTemplate,
{
'refseq': chunk.refname,
'refseq_dirpath': function() {
var hex = Crc32.crc32( chunk.refname )
.toString(16)
.toLowerCase()
.replace('-','n');
// zero-pad the hex string to be 8 chars if necessary
while( hex.length < 8 )
hex = '0'+hex;
var dirpath = [];
for( var i = 0; i < hex.length; i += 3 ) {
dirpath.push( hex.substring( i, i+3 ) );
}
return dirpath.join('/');
}
}
);
this._fetchChunk( sequrl, chunk.chunkNum )
.then( lang.hitch( d, 'resolve' ),
function( e ) {
if( e.response.status == 404 )
d.resolve( '' );
else
d.reject( e );
}
);
d.then( function( sequenceString ) {
callback( thisB._makeFeature( chunk.refname, chunk.chunkNum, chunk.chunkSize, sequenceString ) );
},
function( e ) {
callback(null, e);
});
},
_fetchChunk: function( sequrl, chunkNum ) {
return request.get( sequrl + chunkNum + ".txt" + ( this.compress ? 'z' : '' ),
{ handleAs: 'text', headers: {'X-Requested-With': null} }
);
},
_makeFeature: function( refname, chunkNum, chunkSize, sequenceString ) {
return new SimpleFeature({
data: {
start: chunkNum * chunkSize,
end: chunkNum*chunkSize + sequenceString.length,
residues: sequenceString,
seq_id: refname,
name: refname
}
});
}
});
});