Add base rev

This commit is contained in:
Ritchie Martori 2014-02-11 14:26:47 -08:00
parent 06ffaca7b7
commit 49935b1b8a
8 changed files with 934 additions and 1736 deletions

1
.gitignore vendored
View File

@ -11,3 +11,4 @@
*.swp
*.swo
node_modules
dist

2415
dist/loopback.js vendored

File diff suppressed because it is too large Load Diff

35
example/browser/app.css Normal file
View File

@ -0,0 +1,35 @@
* {padding: 10px;}
body {font-size: 12px;}
table{
margin-bottom:20px;
width:100%;
}
tfoot{
text-align:center;
}
td,th{
padding:5px;
border:1px solid #333;
}
th:empty,
td:empty{
border:none;
}
th{
font-weight:bold;
}
tr:nth-of-type(odd){
background:rgba(255,255,136,0.5);
}
td:nth-of-type(odd),
th:nth-of-type(odd){
background:rgba(255,255,136,0.5);
}
td:nth-of-type(odd),
tr:nth-of-type(odd),
th:nth-of-type(odd){
background:rgba(255,255,136,0.5);
}
input {margin-right: 20px !important;}
button {margin-right: 20px !important;}

View File

@ -17,8 +17,6 @@ var network = {
available: true,
toggle: function() {
this.available = !this.available;
replicate();
replicateFromRemote();
},
status: function() {
return this.available ? 'on' : 'off';
@ -44,13 +42,13 @@ function ReplicationCtlr($scope) {
$scope.status = 'idle';
$scope.replicate = replicate;
$scope.replicateFromRemote = replicate;
$scope.replicateFromRemote = replicateFromRemote;
$scope.conflicts = [];
$scope.resolveUsingRemote = function(conflict) {
conflict.source.name = conflict.target.name;
conflict.source.save(function() {
conflict.source.resolve();
conflict.resolve();
});
}
@ -58,41 +56,6 @@ function ReplicationCtlr($scope) {
LocalColor.on('changed', replicate);
LocalColor.on('deletedAll', replicate);
var messages = [];
function flash(msg) {
messages.push(msg);
}
setInterval(function() {
var msg = messages.shift();
if(msg) {
$scope.status = msg;
}
if(!messages.length) {
messages.push(msg);
}
}, 500);
$scope.reset = function() {
flash('reset');
clearInterval(localIntervalId);
clearInterval(remoteIntervalId);
localIntervalId = setInterval(replicateFromRemote, interval);
remoteIntervalId = setInterval(replicate, interval);
replicate();
replicateFromRemote();
}
$scope.enable = function() {
$scope.enabled = true;
$scope.reset();
}
$scope.disable = function() {
$scope.enabled = false;
$scope.reset();
}
function replicate() {
// reset the conflicts array
@ -101,15 +64,14 @@ function ReplicationCtlr($scope) {
if(network.available) {
LocalColor.currentCheckpoint(function(err, cp) {
setTimeout(function() {
flash('replicating local to remote');
LocalColor.replicate(cp, Color, {}, function(err, conflicts) {
// console.log('replicated local to remote');
conflicts.forEach(function(conflict) {
conflict.fetch(function() {
var local = conflict.source.name;
var remote = conflict.target.name;
console.log(conflict);
var local = conflict.source && conflict.source.name;
var remote = conflict.target && conflict.target.name;
if(local !== remote) {
$scope.conflicts.push(conflict)
$scope.conflicts.push(conflict);
}
$scope.$apply();
});
@ -123,8 +85,8 @@ function ReplicationCtlr($scope) {
function replicateFromRemote() {
if(network.available) {
Color.currentCheckpoint(function(err, cp) {
Color.replicate(0, LocalColor, {}, function() {
// console.log('replicated remote to local');
Color.replicate(cp, LocalColor, {}, function(err, conflicts) {
});
});
}
@ -144,7 +106,6 @@ function ListCtrl($scope) {
LocalColor.on('deleted', update);
function update() {
alert('change event');
LocalColor.find({order: 'name ASC'}, function(err, colors) {
$scope.colors = colors;
$scope.$apply();
@ -156,8 +117,35 @@ function ListCtrl($scope) {
$scope.newColor = null;
}
$scope.del = function(color) {
color.destroy();
update();
}
function ChangeCtrl($scope) {
var Change = LocalColor.getChangeModel();
Change.on('changed', update);
Change.on('deleted', update);
function update() {
Change.find({order: 'checkpoint ASC'}, function(err, changes) {
$scope.changes = changes;
$scope.$apply();
});
}
update();
}
function RemoteChangeCtrl($scope) {
var Change = Color.getChangeModel();
setInterval(update, 5000);
function update() {
Change.find({order: 'checkpoint ASC'}, function(err, changes) {
$scope.changes = changes;
$scope.$apply();
});
}
update();

View File

@ -3,22 +3,16 @@
<head>
<meta charset="utf-8">
<title>My AngularJS App</title>
<link rel="stylesheet" href="css/app.css"/>
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.0/css/bootstrap.min.css"/>
<link rel="stylesheet" href="app.css"/>
</head>
<body>
<style>
body {font-size: 24px;}
</style>
<h1 ng-controller="NetworkCtrl">
<input style="font-size: 48px;" type="checkbox" ng-model="network.available" />Enable Network
<input style="font-size: 48px;" type="checkbox" ng-model="network.available" />Online
</h1>
<ul ng-controller="ReplicationCtlr">
<h2>Status: {{status}}</h2>
<button ng-click="reset()">Reset</button>
<button ng-click="replicate()">Replicate</button>
Auto Replication:
<button ng-click="enable()" ng-disabled="enabled">Enable</button>
<button ng-click="disable()" ng-disabled="!enabled">Disable</button>
<button ng-click="replicate()">Push</button>
<button ng-click="replicateFromRemote()">Pull</button>
<h2 ng-show="conflicts.length">Conflicts:</h2>
<li ng-repeat="conflict in conflicts">
<button ng-click="conflict.resolve()">Use local {{conflict.source.name}}</button>
@ -30,7 +24,7 @@
<form ng-submit="color.save()">
<input placeholder="{{color.name}}" ng-model="color.name" />
{{color.conflict}}
<a href="#" ng-click="del(color)">delete</a>
<a href="#" ng-click="color.destroy()">delete</a>
</form>
</li>
<li>
@ -40,6 +34,47 @@
</li>
</ul>
<h1>Local Changes</h1>
<table ng-controller="ChangeCtrl">
<thead>
<th>type</th>
<th>rev</th>
<th>prev</th>
<th>base</th>
<th>checkpoint</th>
<th>modelId</th>
</thead>
<tr ng-repeat="change in changes">
<td>{{change.type()}}</h3>
<td>{{change.rev}}</td>
<td>{{change.prev}}</td>
<td>{{change.base}}</td>
<td>{{change.checkpoint}}</td>
<td>{{change.modelId}}</td>
</tr>
</table>
<h1>Remote Changes</h1>
<table ng-controller="RemoteChangeCtrl">
<thead>
<th>type</th>
<th>rev</th>
<th>prev</th>
<th>base</th>
<th>checkpoint</th>
<th>modelId</th>
</thead>
<tr ng-repeat="change in changes">
<td>{{change.type()}}</h3>
<td>{{change.rev}}</td>
<td>{{change.prev}}</td>
<td>{{change.base}}</td>
<td>{{change.checkpoint}}</td>
<td>{{change.modelId}}</td>
</tr>
</table>
<script src="loopback.js"></script>
<script src="loopback-remote-models.js"></script>
<script src="client.js"></script>

View File

@ -88,6 +88,10 @@ ServerConnector.prototype.buildModel = function(remoteModel) {
var dataSource = this.dataSource;
var connector = this;
if(remoteModel.settings && remoteModel.settings.trackChanges) {
remoteModel.settings.trackChanges = false;
}
var Model = loopback.createModel(
modelName,
remoteModel.properties || {},

View File

@ -17,6 +17,7 @@ var properties = {
id: {type: String, id: true},
rev: {type: String},
prev: {type: String},
base: {type: String},
checkpoint: {type: Number},
modelName: {type: String},
modelId: {type: String}
@ -130,11 +131,7 @@ Change.idForModel = function(modelName, modelId) {
Change.findOrCreate = function(modelName, modelId, callback) {
var id = this.idForModel(modelName, modelId);
var Change = this;
console.log(modelId);
if(!modelId) debugger;
this.findById(id, function(err, change) {
if(err) return callback(err);
if(change) {
@ -160,13 +157,12 @@ Change.findOrCreate = function(modelName, modelId, callback) {
Change.prototype.rectify = function(cb) {
var change = this;
var lastKnownRevision = this.rev;
var tasks = [
updateRevision,
updateCheckpoint
];
if(this.rev) this.prev = this.rev;
async.parallel(tasks, function(err) {
if(err) return cb(err);
change.save(cb);
@ -177,6 +173,13 @@ Change.prototype.rectify = function(cb) {
change.currentRevision(function(err, rev) {
if(err) return Change.handleError(err, cb);
change.rev = rev;
if(lastKnownRevision && lastKnownRevision !== change.rev) {
change.prev = lastKnownRevision;
}
if(change.type() === Change.CREATE) {
change.base = change.rev;
}
cb();
});
}
@ -285,7 +288,8 @@ Change.prototype.equals = function(change) {
*/
Change.prototype.isBasedOn = function(change) {
return this.prev === change.rev;
return this.prev === change.rev
|| this.base === change.rev;
}
/**
@ -455,6 +459,11 @@ Conflict.prototype.fetch = function(cb) {
}
Conflict.prototype.resolve = function(cb) {
this.sourceChange.prev = this.targetChange.rev;
this.sourceChange.save(cb);
var conflict = this;
this.sourceChange.reload(function() {
conflict.sourceChange.prev = conflict.targetChange.rev;
conflict.sourceChange.base = conflict.targetChange.rev;
conflict.sourceChange.save(cb);
});
}

View File

@ -380,6 +380,7 @@ Model.replicate = function(since, targetModel, options, callback) {
getDiffFromTarget,
createSourceUpdates,
bulkUpdate,
setBaseRevisions,
checkpoint
];
@ -411,10 +412,16 @@ Model.replicate = function(since, targetModel, options, callback) {
sourceModel.createUpdates(diff.deltas, cb);
}
function bulkUpdate(updates, cb) {
function bulkUpdate(_updates, cb) {
updates = _updates;
targetModel.bulkUpdate(updates, cb);
}
function setBaseRevisions() {
var cb = arguments[arguments.length - 1];
targetModel.setBaseRevisions(updates, cb);
}
function checkpoint() {
var cb = arguments[arguments.length - 1];
sourceModel.checkpoint(cb);
@ -486,8 +493,6 @@ Model.bulkUpdate = function(updates, callback) {
switch(update.type) {
case Change.UPDATE:
case Change.CREATE:
// var model = new Model(update.data);
// tasks.push(model.save.bind(model));
tasks.push(function(cb) {
var model = new Model(update.data);
model.save(cb);
@ -505,6 +510,38 @@ Model.bulkUpdate = function(updates, callback) {
async.parallel(tasks, callback);
}
/**
* Set the base revision for the changes in the given updates list.
*
* @param {Array} updates An updates list (usually from Model.createUpdates())
* @param {Function} callback
*/
Model.setBaseRevisions = function(updates, callback) {
var tasks = [];
var Model = this;
var idName = this.dataSource.idName(this.modelName);
var Change = this.getChangeModel();
console.log('setBaseRevisions');
updates.forEach(function(update) {
tasks.push(function(cb) {
Change.findById(update.change.id, function(err, change) {
if(err) return cb(err);
if(change) {
change.base = update.change.rev;
change.save(cb);
} else {
process.nextTick(cb);
}
});
});
});
async.parallel(tasks, callback);
}
/**
* Get the `Change` model.
*