Add base rev
This commit is contained in:
parent
06ffaca7b7
commit
49935b1b8a
|
@ -11,3 +11,4 @@
|
||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
node_modules
|
node_modules
|
||||||
|
dist
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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;}
|
|
@ -17,8 +17,6 @@ var network = {
|
||||||
available: true,
|
available: true,
|
||||||
toggle: function() {
|
toggle: function() {
|
||||||
this.available = !this.available;
|
this.available = !this.available;
|
||||||
replicate();
|
|
||||||
replicateFromRemote();
|
|
||||||
},
|
},
|
||||||
status: function() {
|
status: function() {
|
||||||
return this.available ? 'on' : 'off';
|
return this.available ? 'on' : 'off';
|
||||||
|
@ -44,13 +42,13 @@ function ReplicationCtlr($scope) {
|
||||||
|
|
||||||
$scope.status = 'idle';
|
$scope.status = 'idle';
|
||||||
$scope.replicate = replicate;
|
$scope.replicate = replicate;
|
||||||
$scope.replicateFromRemote = replicate;
|
$scope.replicateFromRemote = replicateFromRemote;
|
||||||
$scope.conflicts = [];
|
$scope.conflicts = [];
|
||||||
|
|
||||||
$scope.resolveUsingRemote = function(conflict) {
|
$scope.resolveUsingRemote = function(conflict) {
|
||||||
conflict.source.name = conflict.target.name;
|
conflict.source.name = conflict.target.name;
|
||||||
conflict.source.save(function() {
|
conflict.source.save(function() {
|
||||||
conflict.source.resolve();
|
conflict.resolve();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,41 +56,6 @@ function ReplicationCtlr($scope) {
|
||||||
LocalColor.on('changed', replicate);
|
LocalColor.on('changed', replicate);
|
||||||
LocalColor.on('deletedAll', 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() {
|
function replicate() {
|
||||||
// reset the conflicts array
|
// reset the conflicts array
|
||||||
|
@ -101,15 +64,14 @@ function ReplicationCtlr($scope) {
|
||||||
if(network.available) {
|
if(network.available) {
|
||||||
LocalColor.currentCheckpoint(function(err, cp) {
|
LocalColor.currentCheckpoint(function(err, cp) {
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
flash('replicating local to remote');
|
|
||||||
LocalColor.replicate(cp, Color, {}, function(err, conflicts) {
|
LocalColor.replicate(cp, Color, {}, function(err, conflicts) {
|
||||||
// console.log('replicated local to remote');
|
|
||||||
conflicts.forEach(function(conflict) {
|
conflicts.forEach(function(conflict) {
|
||||||
conflict.fetch(function() {
|
conflict.fetch(function() {
|
||||||
var local = conflict.source.name;
|
console.log(conflict);
|
||||||
var remote = conflict.target.name;
|
var local = conflict.source && conflict.source.name;
|
||||||
|
var remote = conflict.target && conflict.target.name;
|
||||||
if(local !== remote) {
|
if(local !== remote) {
|
||||||
$scope.conflicts.push(conflict)
|
$scope.conflicts.push(conflict);
|
||||||
}
|
}
|
||||||
$scope.$apply();
|
$scope.$apply();
|
||||||
});
|
});
|
||||||
|
@ -123,8 +85,8 @@ function ReplicationCtlr($scope) {
|
||||||
function replicateFromRemote() {
|
function replicateFromRemote() {
|
||||||
if(network.available) {
|
if(network.available) {
|
||||||
Color.currentCheckpoint(function(err, cp) {
|
Color.currentCheckpoint(function(err, cp) {
|
||||||
Color.replicate(0, LocalColor, {}, function() {
|
Color.replicate(cp, LocalColor, {}, function(err, conflicts) {
|
||||||
// console.log('replicated remote to local');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -144,7 +106,6 @@ function ListCtrl($scope) {
|
||||||
LocalColor.on('deleted', update);
|
LocalColor.on('deleted', update);
|
||||||
|
|
||||||
function update() {
|
function update() {
|
||||||
alert('change event');
|
|
||||||
LocalColor.find({order: 'name ASC'}, function(err, colors) {
|
LocalColor.find({order: 'name ASC'}, function(err, colors) {
|
||||||
$scope.colors = colors;
|
$scope.colors = colors;
|
||||||
$scope.$apply();
|
$scope.$apply();
|
||||||
|
@ -156,8 +117,35 @@ function ListCtrl($scope) {
|
||||||
$scope.newColor = null;
|
$scope.newColor = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.del = function(color) {
|
update();
|
||||||
color.destroy();
|
}
|
||||||
|
|
||||||
|
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();
|
update();
|
||||||
|
|
|
@ -3,22 +3,16 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>My AngularJS App</title>
|
<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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<style>
|
|
||||||
body {font-size: 24px;}
|
|
||||||
</style>
|
|
||||||
<h1 ng-controller="NetworkCtrl">
|
<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>
|
</h1>
|
||||||
<ul ng-controller="ReplicationCtlr">
|
<ul ng-controller="ReplicationCtlr">
|
||||||
<h2>Status: {{status}}</h2>
|
<button ng-click="replicate()">Push</button>
|
||||||
<button ng-click="reset()">Reset</button>
|
<button ng-click="replicateFromRemote()">Pull</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>
|
|
||||||
<h2 ng-show="conflicts.length">Conflicts:</h2>
|
<h2 ng-show="conflicts.length">Conflicts:</h2>
|
||||||
<li ng-repeat="conflict in conflicts">
|
<li ng-repeat="conflict in conflicts">
|
||||||
<button ng-click="conflict.resolve()">Use local {{conflict.source.name}}</button>
|
<button ng-click="conflict.resolve()">Use local {{conflict.source.name}}</button>
|
||||||
|
@ -30,7 +24,7 @@
|
||||||
<form ng-submit="color.save()">
|
<form ng-submit="color.save()">
|
||||||
<input placeholder="{{color.name}}" ng-model="color.name" />
|
<input placeholder="{{color.name}}" ng-model="color.name" />
|
||||||
{{color.conflict}}
|
{{color.conflict}}
|
||||||
<a href="#" ng-click="del(color)">delete</a>
|
<a href="#" ng-click="color.destroy()">delete</a>
|
||||||
</form>
|
</form>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
|
@ -40,6 +34,47 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</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.js"></script>
|
||||||
<script src="loopback-remote-models.js"></script>
|
<script src="loopback-remote-models.js"></script>
|
||||||
<script src="client.js"></script>
|
<script src="client.js"></script>
|
||||||
|
|
|
@ -88,6 +88,10 @@ ServerConnector.prototype.buildModel = function(remoteModel) {
|
||||||
var dataSource = this.dataSource;
|
var dataSource = this.dataSource;
|
||||||
var connector = this;
|
var connector = this;
|
||||||
|
|
||||||
|
if(remoteModel.settings && remoteModel.settings.trackChanges) {
|
||||||
|
remoteModel.settings.trackChanges = false;
|
||||||
|
}
|
||||||
|
|
||||||
var Model = loopback.createModel(
|
var Model = loopback.createModel(
|
||||||
modelName,
|
modelName,
|
||||||
remoteModel.properties || {},
|
remoteModel.properties || {},
|
||||||
|
|
|
@ -17,6 +17,7 @@ var properties = {
|
||||||
id: {type: String, id: true},
|
id: {type: String, id: true},
|
||||||
rev: {type: String},
|
rev: {type: String},
|
||||||
prev: {type: String},
|
prev: {type: String},
|
||||||
|
base: {type: String},
|
||||||
checkpoint: {type: Number},
|
checkpoint: {type: Number},
|
||||||
modelName: {type: String},
|
modelName: {type: String},
|
||||||
modelId: {type: String}
|
modelId: {type: String}
|
||||||
|
@ -130,11 +131,7 @@ Change.idForModel = function(modelName, modelId) {
|
||||||
Change.findOrCreate = function(modelName, modelId, callback) {
|
Change.findOrCreate = function(modelName, modelId, callback) {
|
||||||
var id = this.idForModel(modelName, modelId);
|
var id = this.idForModel(modelName, modelId);
|
||||||
var Change = this;
|
var Change = this;
|
||||||
|
|
||||||
console.log(modelId);
|
|
||||||
|
|
||||||
if(!modelId) debugger;
|
|
||||||
|
|
||||||
this.findById(id, function(err, change) {
|
this.findById(id, function(err, change) {
|
||||||
if(err) return callback(err);
|
if(err) return callback(err);
|
||||||
if(change) {
|
if(change) {
|
||||||
|
@ -160,13 +157,12 @@ Change.findOrCreate = function(modelName, modelId, callback) {
|
||||||
|
|
||||||
Change.prototype.rectify = function(cb) {
|
Change.prototype.rectify = function(cb) {
|
||||||
var change = this;
|
var change = this;
|
||||||
|
var lastKnownRevision = this.rev;
|
||||||
var tasks = [
|
var tasks = [
|
||||||
updateRevision,
|
updateRevision,
|
||||||
updateCheckpoint
|
updateCheckpoint
|
||||||
];
|
];
|
||||||
|
|
||||||
if(this.rev) this.prev = this.rev;
|
|
||||||
|
|
||||||
async.parallel(tasks, function(err) {
|
async.parallel(tasks, function(err) {
|
||||||
if(err) return cb(err);
|
if(err) return cb(err);
|
||||||
change.save(cb);
|
change.save(cb);
|
||||||
|
@ -177,6 +173,13 @@ Change.prototype.rectify = function(cb) {
|
||||||
change.currentRevision(function(err, rev) {
|
change.currentRevision(function(err, rev) {
|
||||||
if(err) return Change.handleError(err, cb);
|
if(err) return Change.handleError(err, cb);
|
||||||
change.rev = rev;
|
change.rev = rev;
|
||||||
|
if(lastKnownRevision && lastKnownRevision !== change.rev) {
|
||||||
|
change.prev = lastKnownRevision;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(change.type() === Change.CREATE) {
|
||||||
|
change.base = change.rev;
|
||||||
|
}
|
||||||
cb();
|
cb();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -285,7 +288,8 @@ Change.prototype.equals = function(change) {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Change.prototype.isBasedOn = 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) {
|
Conflict.prototype.resolve = function(cb) {
|
||||||
this.sourceChange.prev = this.targetChange.rev;
|
var conflict = this;
|
||||||
this.sourceChange.save(cb);
|
|
||||||
|
this.sourceChange.reload(function() {
|
||||||
|
conflict.sourceChange.prev = conflict.targetChange.rev;
|
||||||
|
conflict.sourceChange.base = conflict.targetChange.rev;
|
||||||
|
conflict.sourceChange.save(cb);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -380,6 +380,7 @@ Model.replicate = function(since, targetModel, options, callback) {
|
||||||
getDiffFromTarget,
|
getDiffFromTarget,
|
||||||
createSourceUpdates,
|
createSourceUpdates,
|
||||||
bulkUpdate,
|
bulkUpdate,
|
||||||
|
setBaseRevisions,
|
||||||
checkpoint
|
checkpoint
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -411,10 +412,16 @@ Model.replicate = function(since, targetModel, options, callback) {
|
||||||
sourceModel.createUpdates(diff.deltas, cb);
|
sourceModel.createUpdates(diff.deltas, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
function bulkUpdate(updates, cb) {
|
function bulkUpdate(_updates, cb) {
|
||||||
|
updates = _updates;
|
||||||
targetModel.bulkUpdate(updates, cb);
|
targetModel.bulkUpdate(updates, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setBaseRevisions() {
|
||||||
|
var cb = arguments[arguments.length - 1];
|
||||||
|
targetModel.setBaseRevisions(updates, cb);
|
||||||
|
}
|
||||||
|
|
||||||
function checkpoint() {
|
function checkpoint() {
|
||||||
var cb = arguments[arguments.length - 1];
|
var cb = arguments[arguments.length - 1];
|
||||||
sourceModel.checkpoint(cb);
|
sourceModel.checkpoint(cb);
|
||||||
|
@ -486,8 +493,6 @@ Model.bulkUpdate = function(updates, callback) {
|
||||||
switch(update.type) {
|
switch(update.type) {
|
||||||
case Change.UPDATE:
|
case Change.UPDATE:
|
||||||
case Change.CREATE:
|
case Change.CREATE:
|
||||||
// var model = new Model(update.data);
|
|
||||||
// tasks.push(model.save.bind(model));
|
|
||||||
tasks.push(function(cb) {
|
tasks.push(function(cb) {
|
||||||
var model = new Model(update.data);
|
var model = new Model(update.data);
|
||||||
model.save(cb);
|
model.save(cb);
|
||||||
|
@ -505,6 +510,38 @@ Model.bulkUpdate = function(updates, callback) {
|
||||||
async.parallel(tasks, 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.
|
* Get the `Change` model.
|
||||||
*
|
*
|
||||||
|
|
Loading…
Reference in New Issue