From f1d359b3e7b13a3b5232d2d9905835ded98e7be3 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Tue, 10 Feb 2015 14:42:56 +0100 Subject: [PATCH] Smart objects collection. --- packages/xo-lib/collection.js | 141 ++++++++++++++++++++++++++++++++++ packages/xo-lib/index.js | 34 ++++++-- packages/xo-lib/package.json | 5 +- 3 files changed, 173 insertions(+), 7 deletions(-) create mode 100644 packages/xo-lib/collection.js diff --git a/packages/xo-lib/collection.js b/packages/xo-lib/collection.js new file mode 100644 index 000000000..ee43da093 --- /dev/null +++ b/packages/xo-lib/collection.js @@ -0,0 +1,141 @@ +'use strict'; + +//==================================================================== + +var forEach = require('lodash.foreach'); +var indexOf = require('lodash.indexof'); + +//==================================================================== + +function defaultKey(item) { + return item.id || item._id || item; +} + +//==================================================================== + +function Collection(opts) { + if (!opts) { + opts = {}; + } + + this._key = opts.key || defaultKey; + + this._indexes = Object.create(null); + if (opts.indexes) { + forEach(opts.indexes, function (field) { + this[field] = Object.create(null); + }, this._indexes); + } + + this._data = Object.create(null); +} + +function createIndex(_, field) { + /* jshint validthis: true */ + this[field] = Object.create(null); +} + +Collection.prototype.clear = function () { + this._data = Object.create(null); + forEach(this._indexes, createIndex, this._indexes); +}; + +Collection.prototype.get = function (key) { + return this._data[key]; +}; + +Collection.prototype.where = function (field, value) { + var index = this._indexes[field]; + + if (!index) { + throw new Error('no such index'); + } + + return index[value]; +}; + +function unsetItemFromIndex(index, field) { + /* jshint validthis: true */ + + var prop = this[field]; + if (!prop) { + return; + } + + var items = index[prop]; + + var i = indexOf(items, this); + if (i === -1) { + return; + } + + // The index contains only this one item for this prop. + if (items.length === 1) { + delete index[prop]; + return; + } + + // Remove this item. + items.splice(i, 1); +} + +// Internal unset method. +function unset(item, key) { + /* jshint validthis: true */ + + delete this._data[key]; + + forEach(this._indexes, unsetItemFromIndex, item); +} + +function setItemToIndex(index, field) { + /* jshint validthis: true */ + + var prop = this[field]; + if (!prop) { + return; + } + + var items = index[prop]; + if (items) { + // Update the items list. + items.push(this); + } else { + // Create the items list. + index[prop] = [this]; + } +} + +Collection.prototype.set = function (item) { + var key = this._key(item); + if (!key) { + // Ignore empty keys. + return; + } + + var previous = this._data[key]; + if (previous) { + unset.call(this, previous, key); + } + + this._data[key] = item; + forEach(this._indexes, setItemToIndex, item); +}; + +Collection.prototype.unset = function (item) { + unset.call(this, item, this._key(item)); +}; + +Collection.prototype.setMultiple = function (items) { + forEach(items, this.set, this); +}; +Collection.prototype.unsetMultiple = function (items) { + forEach(items, this.unset, this); +}; + +//==================================================================== + +function createCollection(opts) { + return new Collection(opts); +} +module.exports = createCollection; diff --git a/packages/xo-lib/index.js b/packages/xo-lib/index.js index b7f641699..f1c81adbc 100644 --- a/packages/xo-lib/index.js +++ b/packages/xo-lib/index.js @@ -11,6 +11,8 @@ var makeError = require('make-error'); var MethodNotFound = require('json-rpc/errors').MethodNotFound; var WebSocket = require('ws'); +var createCollection = require('./collection'); + //==================================================================== function makeDeferred() { @@ -178,7 +180,16 @@ exports.Api = Api; //==================================================================== - +var objectsOptions = { + indexes: [ + 'ref', + 'type', + 'UUID', + ], + key: function (item) { + return item.UUID || item.ref; + }, +}; // High level interface to Xo. // @@ -189,7 +200,7 @@ function Xo(opts) { this._api = new Api(opts.url); this._auth = opts.auth; this._backOff = fibonacci(1e3); - this._objects = Object.create(null); + this.objects = createCollection(objectsOptions); this.user = null; // Promise representing the connection status. @@ -200,17 +211,19 @@ function Xo(opts) { email: self._auth.email, password: self._auth.password, }).then(function (user) { - this.user = user; + self.user = user; return self._api.call('xo.getAllObjects'); }).then(function (objects) { - self._objects = objects; + self.objects.setMultiple(objects); }); + + return self._connection; }; self._api.on('disconnected', function () { self._connection = null; - self._objects = Object.create(null); + self.objects.clear(); }); self._api.on('notification', function (notification) { @@ -218,14 +231,23 @@ function Xo(opts) { return; } + var method = ( + notification.params.type === 'exit' ? + 'unset' : + 'set' + ) + 'Multiple'; + self.objects[method](notification.params.items); }); } assign(Xo.prototype, { connect: function () { - var self = this; + if (this._connection) { + return this._connection; + } + var self = this; return this._api.connect().then(this._onConnection).catch(function () { return Bluebird.delay(self._backOff.next().value).then(function () { return self.connect(); diff --git a/packages/xo-lib/package.json b/packages/xo-lib/package.json index f89fd4a8e..2fe2297e5 100644 --- a/packages/xo-lib/package.json +++ b/packages/xo-lib/package.json @@ -22,12 +22,15 @@ "test": "mocha index.spec.js" }, "files": [ - "index.js" + "index.js", + "collection.js" ], "dependencies": { "bluebird": "^2.9.6", "json-rpc": "git://github.com/julien-f/js-json-rpc", "lodash.assign": "^3.0.0", + "lodash.foreach": "^3.0.1", + "lodash.indexof": "^3.0.0", "make-error": "^0.3.0", "ws": "^0.7.1" },