Created
January 27, 2012 19:26
-
-
Save rla/1690480 to your computer and use it in GitHub Desktop.
Sync 3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function Server() { | |
this.rev = 0; | |
this.data = {}; | |
this.changes = []; // { id, rev } | |
} | |
/** | |
* Finds changes after given revision. Returns array of {id, op, rev}. | |
*/ | |
Server.prototype.changesAfter = function(rev) { | |
var changes = []; | |
var server = this; | |
this.changes.forEach(function(change) { | |
if (change.rev > rev) { | |
changes.push({ | |
id: change.id, | |
op: server.data[change.id] === undefined ? 'D' : 'U', | |
rev: change.rev | |
}); | |
} | |
}); | |
return changes; | |
}; | |
function Client() { | |
this.rev = 0; | |
this.data = {}; | |
this.changes = []; // { id, rev } | |
} | |
Client.prototype.add = function(id, data) { | |
this.data[id] = data; | |
this.changes.push({ id: id, rev: this.rev }); | |
}; | |
Client.prototype.remove = function(id) { | |
delete this.data[id]; | |
this.changes.push({ id: id, rev: this.rev }); | |
}; | |
Client.prototype.update = function(id, data) { | |
this.data[id] = data; | |
this.changes.push({ id: id, rev: this.rev }); | |
}; | |
/** | |
* Finds current changes. Returns array of {id, op, rev}. | |
*/ | |
Client.prototype.currentChanges = function() { | |
var changes = []; | |
var client = this; | |
this.changes.forEach(function(change) { | |
changes.push({ | |
id: change.id, | |
op: client.data[change.id] === undefined ? 'D' : 'U', | |
rev: change.rev | |
}); | |
}); | |
return changes; | |
}; | |
Client.prototype.sync = function(server) { | |
var scs = server.changesAfter(this.rev); | |
var ccs = this.currentChanges(); | |
var conflicting = {}; // id -> true | |
var client = this; | |
// Find and resolve conflicting changes. | |
scs.forEach(function(sc) { | |
ccs.forEach(function(cc) { | |
if (sc.id === cc.id) { | |
console.log('Conflicting for id ' + sc.id); | |
conflicting[sc.id] = true; | |
// Current resolution: prefer master | |
// Which changes to send to master (below) also depend on it. | |
if (sc.op === 'U') { | |
client.data[sc.id] = server.data[sc.id]; | |
} else if (sc.op === 'D') { | |
delete client.data[sc.id]; | |
} | |
} | |
}); | |
}); | |
// Find and apply non-conflicting server changes. | |
scs.forEach(function(sc) { | |
if (conflicting[sc.id] === undefined) { | |
if (sc.op === 'U') { | |
client.data[sc.id] = server.data[sc.id]; | |
} else if (sc.op === 'D') { | |
delete client.data[sc.id]; | |
} | |
} | |
}); | |
// Find and apply non-conflicting client changes. | |
ccs.forEach(function(cc) { | |
if (conflicting[cc.id] === undefined) { | |
if (cc.op === 'U') { | |
server.data[cc.id] = client.data[cc.id]; | |
} else if (cc.op === 'D') { | |
delete server.data[cc.id]; | |
} | |
} | |
}); | |
// Push rev. | |
server.rev += 1; | |
this.rev = server.rev; | |
// Transfer client changes with increased revision number. | |
// Conflicting changes are not sent since we chose to prefer master. | |
ccs.forEach(function(cc) { | |
if (conflicting[cc.id] === undefined) { | |
server.changes.push({ id: cc.id, rev: client.rev }); | |
} | |
}); | |
// Cleanup. | |
this.changes = []; | |
}; | |
function report(party, title) { | |
console.log(title); | |
console.log('\tRevision: ' + party.rev); | |
console.log('\tData:'); | |
for (id in party.data) { | |
console.log('\t\t' + id + ': ' + party.data[id]); | |
} | |
console.log('\tChanges:'); | |
party.changes.forEach(function(change) { | |
console.log('\t\tr' + change.rev + ' ' + change.id); | |
}); | |
}; | |
var s = new Server(); | |
var c1 = new Client(); | |
c1.add(1, 'd1-1.0'); | |
c1.add(2, 'd2-2.0'); | |
c1.sync(s); | |
report(s, 'Server data after 1st sync'); | |
var c2 = new Client(); | |
c2.add(1, 'd1-1.a'); | |
// Now c2-server has conflict for id = 1. | |
c2.sync(s); | |
report(s, 'Server data after 2nd sync'); // must be unchanged | |
report(c2, 'Client 2 data after 2nd sync'); // override by server | |
/* | |
report(c1, 'Client 1 data before 1st sync'); | |
c1.sync(s); | |
report(c1, 'Client 1 data after 1st sync'); | |
report(s, 'Server data after 1st sync'); | |
var c2 = new Client(); | |
c2.sync(s); | |
report(c2, 'Client 2 data after 2nd sync'); | |
c2.remove(1); | |
c2.add(3, 'd3-1.0'); | |
report(c2, 'Client 2 data before 3rd sync'); | |
c2.sync(s); | |
report(c2, 'Client 2 data after 3rd sync'); | |
report(s, 'Server data after 3rd sync'); | |
c1.sync(s); | |
report(c1, 'Client 1 data after 4th sync'); | |
var c3 = new Client(); | |
c3.sync(s); | |
report(c3, 'Client 3 data after 5th sync'); | |
report(s, 'Server data after 5th sync'); | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment