From a2f622091f834825b9e96553581e606051d3e0a3 Mon Sep 17 00:00:00 2001 From: Simon Scherzinger Date: Tue, 23 Feb 2016 17:31:04 +0100 Subject: [PATCH] somewhat the first version of cached data. --- lib/DataManager.js | 27 +++++++--- lib/Model.js | 68 +++++++++++++++++++------ test/sdk.test.js | 120 ++++++++++++++++++++++++++++++++++----------- 3 files changed, 165 insertions(+), 50 deletions(-) diff --git a/lib/DataManager.js b/lib/DataManager.js index 3817269..87fa088 100644 --- a/lib/DataManager.js +++ b/lib/DataManager.js @@ -674,14 +674,14 @@ DataManager.prototype._requestOptions = function(additionalHeaders) { }; DataManager.prototype._makeDB = function() { + if (this._db) { + return Promise.resolve(this._db); + } + this._db = new loki(this.id + '.db.json', { + autosave: true, + autosaveInterval: 30000 + }); return new Promise(function(resolve, reject) { - if (this._db) { - return resolve(this._db); - } - this._db = new loki(this.id + '.db.json', { - autosave: true, - autosaveInterval: 30000 - }); this._db.loadDatabase({}, function(err) { if (err && err.message.indexOf('ENOENT') !== -1) { return this._db.saveDatabase(function(err) { @@ -696,6 +696,19 @@ DataManager.prototype._makeDB = function() { } return resolve(this._db); }.bind(this)); + }.bind(this)) + .then(function(db) { + this._cacheMetaData = db.getCollection('_metadata'); + if (!this._cacheMetaData) { + this._cacheMetaData = db.addCollection('_metadata', { + indices: [ + 'etag', + 'created', + 'title' + ] + }); + } + return Promise.resolve(db); }.bind(this)); }; diff --git a/lib/Model.js b/lib/Model.js index 7bbc40e..de51178 100644 --- a/lib/Model.js +++ b/lib/Model.js @@ -18,43 +18,49 @@ var Model = function(title, metadata, dm) { Model.prototype.enableCache = function(maxCacheAge) { return this._dm._makeDB().then(function(db) { - this._collection = db.getCollection(this.title); - if (!this._collection) { - this._collection = db.addCollection(this.title, { + this._maxAge = maxCacheAge || 600000; + this._items = db.getCollection(this.title) || db.addCollection(this.title, { indices: [ '_id', '_entryTitle' ] }); - } + //this._items.ensureUniqueIndex('_id'); return this._loadData(); - }.bind(this)).catch(function(err) { - throw err; - }); + }.bind(this)); }; Model.prototype._loadData = function() { - return new Promise(function(resolve, reject) { - this._getTraversal().then(function(traversal) { + return this._getTraversal() + .then(function(traversal) { + return new Promise(function(resolve, reject) { traversal.continue().newRequest() .follow(this._dm.id + ':' + this.title) .withRequestOptions(this._dm._requestOptions()) .withTemplateParameters({ size: 0 }) .get(function(err, res, traversal) { - util.checkResponse(err, res).then(function(res) { + return util.checkResponse(err, res) + .then(function(res) { var body = halfred.parse(JSON.parse(res.body)); var entries = body.embeddedResourceArray(this._dm.id + ':' + this.title); - this._collection.removeDataOnly(); + this._items.removeDataOnly(); for (var i = 0; i < entries.length; i++) { - this._collection.insert(entries[i]); + this._items.insert(entries[i].original()); } + this._dm._cacheMetaData.removeWhere({ title: this.title }); + this._dm._cacheMetaData.insert({ + title: this.title, + etag: res.headers.etag, + created: new Date().toISOString() + }); this._dm._db.save(function(err) { if (err) { return reject(err); } - return resolve(this); - }.bind(this)) - }.bind(this)).catch(reject); + return resolve(this._items); + }.bind(this)); + }.bind(this)) + .catch(reject); }.bind(this)); }.bind(this)); }.bind(this)); @@ -88,6 +94,15 @@ Model.prototype._getTraversal = function() { }); }; +Model.prototype._ensureNotStale = function() { + var maxAge = new Date().getTime() - this._maxAge; + var created = new Date(this._dm._cacheMetaData.find({ title: this.title })[0].created).getTime(); + if (created > maxAge) { + return Promise.resolve(this._items); + } + return this._loadData(); +}; + Model.prototype.resolve = function() { var model = this; return new Promise(function(resolve, reject) { @@ -132,6 +147,29 @@ Model.prototype.getSchema = function(method) { Model.prototype.entryList = function(options) { var model = this; + if (this._maxAge) { + return Promise.resolve() + .then(function() { + switch (options.cacheType || 'default') { + case 'stale': + throw new Error('cacheType \'stale\' not implemented.'); + case 'refresh': + return this._loadData(); + case 'default': + default: + return this._ensureNotStale(); + } + }.bind(this)) + .then(function(items) { + var res = items.find('getAll'); + var out = []; + for (var i = 0; i < res.length; i++) { + out.push(new Entry(halfred.parse(res[i]), this._dm, this)); + } + return Promise.resolve({ entries: out, count: out.length, total: items.data.length }) + }.bind(this)); + } + return this._getTraversal().then(function(traversal) { return new Promise(function(resolve, reject) { var t = traversal.continue().newRequest() diff --git a/test/sdk.test.js b/test/sdk.test.js index fdfae01..f7a316a 100644 --- a/test/sdk.test.js +++ b/test/sdk.test.js @@ -468,40 +468,104 @@ describe('model', function() { }); }); -describe('offline model', function() { - var dm; - beforeEach(function() { - dm = new DataManager({ - url: baseUrl + '58b9a1f5' +if (isNode) { + describe('offline model', function() { + var dm; + beforeEach(function() { + dm = new DataManager({ + url: baseUrl + '58b9a1f5' + }); }); - }); - afterEach(function() { - dm = null; - }); - it('make single model offline by dm', function() { - return dm.enableCache('to-do-list').then(function(models) { - expect(models[0]).to.be.a('object'); - expect(models[0].id).to.be.equal('to-do-list'); - expect(models[0]._collection).to.be.a('object'); + afterEach(function() { + dm = null; + }); + it('make single model offline by dm', function() { + return dm.enableCache('to-do-list').then(function(models) { + expect(dm._cacheMetaData).to.be.a('object'); + expect(models[0]).to.be.a('object'); + expect(models[0].name).to.be.equal('to-do-list'); + }); + }); + it('make single model offline by model', function() { + return dm.model('to-do-list').enableCache().then(function(model) { + expect(dm._cacheMetaData).to.be.a('object'); + expect(model).to.be.a('object'); + expect(model.name).to.be.equal('to-do-list'); + }); + }); + it('make multiple models offline by dm', function() { + return dm.enableCache([ + 'to-do-list', + 'to-do-item' + ]).then(function(models) { + expect(models).to.be.a('array'); + expect(models.length).to.be.equal(2); + }).catch(); }); }); - it('make single model offline by model', function() { - return dm.model('to-do-list').enableCache().then(function(model) { - expect(model).to.be.a('object'); - expect(model.id).to.be.equal('to-do-list'); - expect(model._collection).to.be.a('object'); + describe('stale data', function() { + var dm; + beforeEach(function(done) { + dm = new DataManager({ + url: baseUrl + '58b9a1f5' + }); + dm.enableCache([ + 'to-do-item', + 'to-do-list' + ], 1) + .then(function() { + done(); + }) + .catch(done); + }); + afterEach(function() { + dm = null; + }); + it('entries size 0', function() { + return dm.model('to-do-list').entries({ size: 0 }) + .then(function(entries) { + expect(entries.length).to.be.equal(2); + }); }); }); - it('make multiple models offline by dm', function() { - return dm.enableCache([ - 'to-do-list', - 'to-do-item' - ]).then(function(models) { - expect(models).to.be.a('array'); - expect(models.length).to.be.equal(2); - }).catch(); + describe('valid cache data', function() { + var dm; + beforeEach(function(done) { + dm = new DataManager({ + url: baseUrl + '58b9a1f5' + }); + dm.enableCache([ + 'to-do-item', + 'to-do-list' + ]) + .then(function() { + done(); + }) + .catch(done); + }); + afterEach(function() { + dm = null; + }); + it('entries size 0', function() { + return dm.model('to-do-list').entries({ size: 0 }) + .then(function(entries) { + expect(entries.length).to.be.equal(2); + }); + }); + it('entries size 0', function() { + return dm.model('to-do-list').entries({ size: 0, cacheType: 'refresh' }) + .then(function(entries) { + expect(entries.length).to.be.equal(2); + }); + }); + it.skip('entries size 0', function() { + return dm.model('to-do-list').entries({ size: 0, cacheType: 'stale' }) + .then(function(entries) { + expect(entries.length).to.be.equal(2); + }); + }); }); -}); +} describe('entry/entries', function() { // this is basically modelList var dm;