Skip to content

Instantly share code, notes, and snippets.

@rla
Created January 27, 2012 19:26
Show Gist options
  • Save rla/1690480 to your computer and use it in GitHub Desktop.
Save rla/1690480 to your computer and use it in GitHub Desktop.
Sync 3
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