Skip to content

Instantly share code, notes, and snippets.

@crcn
Created July 10, 2011 02:08
Show Gist options
  • Save crcn/1074163 to your computer and use it in GitHub Desktop.
Save crcn/1074163 to your computer and use it in GitHub Desktop.
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