Permalink
Browse files

Merge #714: add commit cache (feature #622)

  • Loading branch information...
michielbdejong committed Jun 17, 2014
1 parent 10187bb commit 395af3f8f80c8961b6bdf8c7d74fbb768a253dda
Showing with 145 additions and 6 deletions.
  1. +86 −1 src/indexeddb.js
  2. +59 −5 test/unit/indexeddb-suite.js
View
@@ -59,10 +59,95 @@
this.getsRunning = 0;
this.putsRunning = 0;
/**
* Property: changesQueued
*
* Given a node for which uncommitted changes exist, this cache
* stores either the entire uncommitted node, or false for a deletion.
* The node's path is used as the key.
*
* changesQueued stores changes for which no IndexedDB transaction has
* been started yet.
*/
this.changesQueued = {};
/**
* Property: changesRunning
*
* Given a node for which uncommitted changes exist, this cache
* stores either the entire uncommitted node, or false for a deletion.
* The node's path is used as the key.
*
* At any time there is at most one IndexedDB transaction running.
* changesRunning stores the changes that are included in that currently
* running IndexedDB transaction, or if none is running, of the last one
* that ran.
*/
this.changesRunning = {};
};
RS.IndexedDB.prototype = {
getNodes: function(paths) {
var misses = [], fromCache = {};
for (var i=0; i<paths.length; i++) {
if (this.changesQueued[paths[i]] !== undefined) {
fromCache[paths[i]] = this._getInternals().deepClone(this.changesQueued[paths[i]] || undefined);
} else if(this.changesRunning[paths[i]] !== undefined) {
fromCache[paths[i]] = this._getInternals().deepClone(this.changesRunning[paths[i]] || undefined);
} else {
misses.push(paths[i]);
}
}
if (misses.length > 0) {
return this.getNodesFromDb(misses).then(function(nodes) {
for (var i in fromCache) {
nodes[i] = fromCache[i];
}
return nodes;
});
} else {
promise = promising();
promise.fulfill(fromCache);
return promise;
}
},
setNodes: function(nodes) {
var promise = promising();
for (var i in nodes) {
this.changesQueued[i] = nodes[i] || false;
}
this.maybeFlush();
promise.fulfill();
return promise;
},
maybeFlush: function() {
if (this.putsRunning === 0) {
this.flushChangesQueued();
} else {
if (!this.commitSlownessWarning) {
this.commitSlownessWarning = setInterval(function() {
console.log('WARNING: waited more than 10 seconds for previous commit to finish');
}, 10000);
}
}
},
flushChangesQueued: function() {
if (this.commitSlownessWarning) {
clearInterval(this.commitSlownessWarning);
this.commitSlownessWarning = null;
}
if (Object.keys(this.changesQueued).length > 0) {
this.changesRunning = this.changesQueued;
this.changesQueued = {};
this.setNodesInDb(this.changesRunning).then(this.flushChangesQueued.bind(this));
}
},
getNodesFromDb: function(paths) {
var promise = promising();
var transaction = this.db.transaction(['nodes'], 'readonly');
var nodes = transaction.objectStore('nodes');
@@ -93,7 +178,7 @@
return promise;
},
setNodes: function(nodes) {
setNodesInDb: function(nodes) {
var promise = promising();
var transaction = this.db.transaction(['nodes'], 'readwrite');
var nodesStore = transaction.objectStore('nodes');
@@ -89,23 +89,77 @@ define(['requirejs'], function(requirejs) {
desc: "fireInitial fires change event with 'local' origin for initial cache content",
timeout: 250,
run: function(env, test) {
env.idb.putsRunning = 0;
env.idb.put('/foo/bla', 'basdf', 'text/plain').then(function() {
env.idb.on('change', function(event) {
test.assert(event.origin, 'local');
test.assertAnd(event.origin, 'local');
setTimeout(function() {
test.done();
}, 50);
});
//the mock is just an in-memory object; need to explicitly set its .length and its .key() function now:
env.idb.fireInitial();
}, function(e) {
test.result(false, e);
});
setTimeout(function() {
env._puts[0].onsuccess();
env._transactions[1].oncomplete();
}, 100);
env._gets[0].onsuccess({ target: {}});
env._transactions[0].oncomplete();
}
},
{
desc: "setNodes calls setNodesInDb when putsRunning is 0",
run: function(env, test) {
var setNodesInDb = env.idb.setNodesInDb,
getNodesFromDb = env.idb.getNodesFromDb;
env.idb.setNodesInDb = function(nodes) {
var promise = promising();
test.assertAnd(nodes, {foo: {path: 'foo'}});
setTimeout(function() {
env.idb.setNodesInDb = setNodesInDb;
env.idb.getNodesFromDb = getNodesFromDb;
test.done();
}, 10);
promise.fulfill();
return promise;
};
env.idb.putsRunning = 0;
env.idb.setNodes({foo: {path: 'foo'}});
}
},
{
desc: "setNodes doesn't call setNodesInDb when putsRunning is 1, but will flush later",
run: function(env, test) {
var setNodesInDb = env.idb.setNodesInDb,
getNodesFromDb = env.idb.getNodesFromDb;
env.idb.changesQueued = {};
env.idb.changesRunning = {};
env.idb.setNodesInDb = function(nodes) {
test.result(false, 'should not have called this function');
};
env.idb.putsRunning = 1;
env.idb.setNodes({foo: {path: 'foo'}});
test.assertAnd(env.idb.changesQueued, {foo: {path: 'foo'}});
test.assertAnd(env.idb.changesRunning, {});
env.idb.setNodesInDb = function(nodes) {
var promise = promising();
test.assertAnd(nodes, {foo: {path: 'foo'}});
setTimeout(function() {
env.idb.setNodesInDb = setNodesInDb;
env.idb.getNodesFromDb = getNodesFromDb;
test.done();
}, 10);
promise.fulfill();
return promise;
};
env.idb.putsRunning = 0;
env.idb.maybeFlush();
test.assertAnd(env.idb.changesQueued, {});
test.assertAnd(env.idb.changesRunning, {foo: {path: 'foo'}});
}
}
/* TODO: mock indexeddb with some nodejs library
{
desc: "getNodes, setNodes",

0 comments on commit 395af3f

Please sign in to comment.