Created
July 10, 2011 02:08
-
-
Save crcn/1074163 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var http = require('http'), | |
lazyCallback = require('sk/core/lazy').lazy.callback, | |
querystring = require('querystring'), | |
EventEmitter = require('sk/core/events').EventEmitter, | |
SKClass = require('sk/core/struct').struct; | |
function methodPartial(type) | |
{ | |
return function(uri, params, data, callback) | |
{ | |
if(typeof params == 'function') | |
{ | |
callback = params; | |
data = undefined; | |
params = undefined; | |
} | |
if(!callback && (typeof data == 'function')) | |
{ | |
callback = data; | |
data = params; | |
params = undefined; | |
} | |
this._request(uri, type, params, data, callback); | |
} | |
} | |
var IndexTankService = EventEmitter.extend({ | |
'override __construct': function(config) | |
{ | |
this._super(); | |
this._config = config; | |
}, | |
'_put': methodPartial('PUT'), | |
'_get': methodPartial('GET'), | |
'_post': methodPartial('POST'), | |
'_delete': methodPartial('DELETE'), | |
'_request': function(uri, method, params, data, callback) | |
{ | |
var ops = { | |
host: this._config.userId + '.api.indextank.com', | |
path: '/v1/' + uri + (params ? '?' + querystring.stringify(params) : ''), | |
port: 80, | |
method: method, | |
headers: { | |
'authorization': 'Basic '+ (new Buffer(String(this._config.privateKey))).toString('base64'), | |
'Connection': 'keep-alive' | |
} | |
}; | |
if(method != 'GET' && data) | |
{ | |
data = (typeof data == 'object' ? JSON.stringify(data) : data) + '\n'; | |
ops.headers['Content-Length'] = data.length; | |
} | |
var req = http.request(ops, function(res) | |
{ | |
res.setEncoding('utf8'); | |
var buffer = ''; | |
res.on('data', function(data) | |
{ | |
buffer += data; | |
}); | |
res.on('end', function() | |
{ | |
var result = buffer; | |
try | |
{ | |
result = JSON.parse(result); | |
} | |
catch(e) | |
{ | |
//indextank doesn't have proper error responses | |
if(result.length > 3) console.error(result) | |
} | |
if(callback) | |
{ | |
console.log(result) | |
callback(result); | |
} | |
}); | |
}); | |
req.on('error', function(err) | |
{ | |
console.error(err) | |
}) | |
if(method != 'GET' && data) | |
{ | |
console.log(data) | |
req.write(data); | |
} | |
req.end(); | |
// if(method == 'GET') | |
} | |
}) | |
var IndexTankCollection = IndexTankService.extend({ | |
/** | |
*/ | |
'override __construct': function(config, name) | |
{ | |
this._super(config); | |
this.name = name; | |
this._insertBatch = []; | |
this._lazyInsertCallback = lazyCallback(this.getMethod('_insertDocBatch'), 5000); | |
this._maxBatchSize = 10; | |
}, | |
/** | |
* prepares the collection so it can be used | |
*/ | |
'prepare': function(callback) | |
{ | |
if(this._prepared) return callback(this); | |
var self = this; | |
this.create(function() | |
{ | |
console.success('prepared indextank collection '+self.name); | |
self._prepared = true; | |
callback(self); | |
}) | |
}, | |
/** | |
* adds to the collection | |
*/ | |
'create': function(callback) | |
{ | |
this._put(this._getIndexUrl(), callback); | |
}, | |
/** | |
* removes the collection | |
*/ | |
'remove': function(callback) | |
{ | |
this._delete(this._getIndexUrl(), callback); | |
}, | |
/** | |
* returns the metadata of given collection | |
*/ | |
'getMetadata': function(callback) | |
{ | |
this._get(this._getIndexUrl(), callback); | |
}, | |
/** | |
* adds a document to the index | |
* @ops | |
* - @docid the document identifier | |
* - @fields a map from field name to field value | |
* - @variables a map of the var number to float | |
* - @categories a map from the category name to its value | |
*/ | |
'addDoc': function(ops, callback) | |
{ | |
if(ops.fields._id) | |
{ | |
ops.docid = ops.fields._id.toString(); | |
delete ops.fields._id; | |
} | |
// ops.fields = SKClass.copy(ops.fields); | |
ops.fields = JSON.parse(JSON.stringify(ops.fields)); | |
//indextank only accepts strings, and ints, so we need to convert leh object to a string. | |
for(var prop in ops.fields) | |
{ | |
if(typeof ops.fields[prop] == 'object') ops.fields[prop] = 'JSON::' + JSON.stringify(ops.fields[prop]); | |
} | |
//send batch collections | |
this._insertBatch.push(ops); | |
//start the insertion process | |
this._lazyInsertCallback(); | |
if(this._insertBatch.length > this._maxBatchSize) | |
{ | |
this._insertDocBatch(); | |
} | |
//since it's a batch process, add as an observable | |
if(callback) this.addListener('batchInserted', callback); | |
}, | |
/** | |
* removes a doc | |
*/ | |
'removeDoc': function(docid, callback) | |
{ | |
this._delete(this._getIndexUrl() + '/docs', { docid: docid }, callback); | |
}, | |
/** | |
* searches for a document | |
* @ops | |
* - @q the query string | |
* - @len the number of items to return | |
* - @function the the number of scoring function to use | |
* - @fetch comma-separated list of fields to fetch. * returns all | |
* - @snippet comma-separated list of fields to snippet | |
* - @var<n> value of query variable | |
* - @category_filters json map of categoriy name | |
* - @filter_docvar comma-separated list of ranges to filter | |
* - @filter_function comma-separated list of ranges to filter the values of functions. | |
*/ | |
'search': function(ops, callback) | |
{ | |
console.ok('searching %s for "%s"', this._getIndexUrl() + '/search', ops.q); | |
this._get(this._getIndexUrl() + '/search', ops, {}, function(result) | |
{ | |
try | |
{ | |
for(var i = result.results.length; i--;) | |
{ | |
var item = result.results[i]; | |
for(var property in item) | |
{ | |
var v = item[property]; | |
if(typeof v == 'string' && v.substr(0,6) == 'JSON::') | |
{ | |
item[property] = JSON.parse(v.substr(6)); | |
} | |
} | |
item._id = item.docid; | |
delete item.docid; | |
} | |
callback(result.results); | |
} | |
catch(e) | |
{ | |
console.error(JSON.stringify(result)); | |
console.error(e.stack); | |
callback([]); | |
} | |
}); | |
}, | |
/** | |
* promotes item to the top of the query's result page | |
*/ | |
'promote': function(docid, query, callback) | |
{ | |
this._put(this._getIndexUrl() + '/promote', { docid: docid, query: query }, callback); | |
}, | |
/** | |
*/ | |
'autoComplete': function(query, callbackString, callback) | |
{ | |
this._get(this._getIndexUrl() + '/autocomplete', {query: query, callback: callbackString }, callback); | |
}, | |
/** | |
* inserts a batch of documents | |
*/ | |
'_insertDocBatch': function() | |
{ | |
var batch = this._insertBatch, | |
self = this; | |
this._insertBatch = []; | |
if(!batch.length) return; | |
console.ok('adding '+batch.length+' items to indextank'); | |
this._put(this._getIndexUrl() + '/docs', batch, function(result) | |
{ | |
self.emit('batchInserted', result); | |
self.removeListeners('batchInserted'); | |
}); | |
}, | |
/** | |
* returns the index url | |
*/ | |
'_getIndexUrl': function() | |
{ | |
return '/indexes/' + this.name; | |
} | |
}); | |
var IndexTank = IndexTankService.extend({ | |
'override __construct': function(config) | |
{ | |
this._super(config); | |
this._collections = {}; | |
}, | |
'getIndexes': function(callback) | |
{ | |
this._get('indexes', callback); | |
}, | |
'collection': function(name, callback) | |
{ | |
var collection = this._collections[name] || (this._collections[name] = new IndexTankCollection(this._config, name)); | |
collection.prepare(callback); | |
}, | |
'hasCollection': function(name, callback) | |
{ | |
callback(!!this._collections[name]); | |
} | |
}); | |
exports.DEFAULT_PAGE_SIZE = 10; | |
exports.createClient = function(params) | |
{ | |
return new IndexTank(params); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment