Unified timeslider and pad editing protocol / component

This commit is contained in:
Egil Moeller 2012-04-23 12:52:30 +02:00
parent d08f3ff5ee
commit 914d79ad20
8 changed files with 359 additions and 578 deletions

View file

@ -203,6 +203,10 @@ exports.handleMessage = function(client, message)
{
handleSuggestUserName(client, message);
}
else if(message.type == "CHANGESET_REQ")
{
handleChangesetRequest(client, message);
}
//if the message type is unknown, throw an exception
else
{
@ -210,6 +214,7 @@ exports.handleMessage = function(client, message)
}
}
/**
* Handles a save revision message
* @param client the client that send this message
@ -661,6 +666,7 @@ function handleClientReady(client, message)
var authorColorId;
var pad;
var historicalAuthorData = {};
var currentTime;
var readOnlyId;
var chatMessages;
@ -735,6 +741,16 @@ function handleClientReady(client, message)
var authors = pad.getAllAuthors();
async.parallel([
//get timestamp of latest revission needed for timeslider
function(callback)
{
pad.getRevisionDate(pad.getHeadRevisionNumber(), function(err, date)
{
if(ERR(err, callback)) return;
currentTime = date;
callback();
});
},
//get all author data out of the database
function(callback)
{
@ -761,7 +777,6 @@ function handleClientReady(client, message)
}
], callback);
},
function(callback)
{
@ -813,7 +828,8 @@ function handleClientReady(client, message)
"historicalAuthorData": historicalAuthorData,
"apool": apool,
"rev": pad.getHeadRevisionNumber(),
"globalPadId": message.padId
"globalPadId": message.padId,
"time": currentTime,
},
"colorPalette": ["#ffc7c7", "#fff1c7", "#e3ffc7", "#c7ffd5", "#c7ffff", "#c7d5ff", "#e3c7ff", "#ffc7f1", "#ff8f8f", "#ffe38f", "#c7ff8f", "#8fffab", "#8fffff", "#8fabff", "#c78fff", "#ff8fe3", "#d97979", "#d9c179", "#a9d979", "#79d991", "#79d9d9", "#7991d9", "#a979d9", "#d979c1", "#d9a9a9", "#d9cda9", "#c1d9a9", "#a9d9b5", "#a9d9d9", "#a9b5d9", "#c1a9d9", "#d9a9cd", "#4c9c82", "#12d1ad", "#2d8e80", "#7485c3", "#a091c7", "#3185ab", "#6818b4", "#e6e76d", "#a42c64", "#f386e5", "#4ecc0c", "#c0c236", "#693224", "#b5de6a", "#9b88fd", "#358f9b", "#496d2f", "#e267fe", "#d23056", "#1a1a64", "#5aa335", "#d722bb", "#86dc6c", "#b5a714", "#955b6a", "#9f2985", "#4b81c8", "#3d6a5b", "#434e16", "#d16084", "#af6a0e", "#8c8bd8"],
"clientIp": "127.0.0.1",
@ -837,7 +853,9 @@ function handleClientReady(client, message)
"plugins": {
"plugins": plugins.plugins,
"parts": plugins.parts,
}
},
"initialChangesets": [] // FIXME: REMOVE THIS SHIT
}
//Add a username to the clientVars if one avaiable
@ -858,7 +876,7 @@ function handleClientReady(client, message)
else
{
//Send the clientVars to the Client
client.json.send(clientVars);
client.json.send({type: "CLIENT_VARS", data: clientVars});
//Save the revision in sessioninfos
sessioninfos[client.id].rev = pad.getHeadRevisionNumber();
}
@ -961,3 +979,325 @@ function handleClientReady(client, message)
ERR(err);
});
}
/**
* Handles a request for a rough changeset, the timeslider client needs it
*/
function handleChangesetRequest(client, message)
{
//check if all ok
if(message.data == null)
{
messageLogger.warn("Dropped message, changeset request has no data!");
return;
}
if(message.padId == null)
{
messageLogger.warn("Dropped message, changeset request has no padId!");
return;
}
if(message.data.granularity == null)
{
messageLogger.warn("Dropped message, changeset request has no granularity!");
return;
}
if(message.data.start == null)
{
messageLogger.warn("Dropped message, changeset request has no start!");
return;
}
if(message.data.requestID == null)
{
messageLogger.warn("Dropped message, changeset request has no requestID!");
return;
}
var granularity = message.data.granularity;
var start = message.data.start;
var end = start + (100 * granularity);
var padId = message.padId;
//build the requested rough changesets and send them back
getChangesetInfo(padId, start, end, granularity, function(err, changesetInfo)
{
ERR(err);
var data = changesetInfo;
data.requestID = message.data.requestID;
client.json.send({type: "CHANGESET_REQ", data: data});
});
}
/**
* Tries to rebuild the getChangestInfo function of the original Etherpad
* https://github.com/ether/pad/blob/master/etherpad/src/etherpad/control/pad/pad_changeset_control.js#L144
*/
function getChangesetInfo(padId, startNum, endNum, granularity, callback)
{
var forwardsChangesets = [];
var backwardsChangesets = [];
var timeDeltas = [];
var apool = new AttributePool();
var pad;
var composedChangesets = {};
var revisionDate = [];
var lines;
async.series([
//get the pad from the database
function(callback)
{
padManager.getPad(padId, function(err, _pad)
{
if(ERR(err, callback)) return;
pad = _pad;
callback();
});
},
function(callback)
{
//calculate the last full endnum
var lastRev = pad.getHeadRevisionNumber();
if (endNum > lastRev+1) {
endNum = lastRev+1;
}
endNum = Math.floor(endNum / granularity)*granularity;
var compositesChangesetNeeded = [];
var revTimesNeeded = [];
//figure out which composite Changeset and revTimes we need, to load them in bulk
var compositeStart = startNum;
while (compositeStart < endNum)
{
var compositeEnd = compositeStart + granularity;
//add the composite Changeset we needed
compositesChangesetNeeded.push({start: compositeStart, end: compositeEnd});
//add the t1 time we need
revTimesNeeded.push(compositeStart == 0 ? 0 : compositeStart - 1);
//add the t2 time we need
revTimesNeeded.push(compositeEnd - 1);
compositeStart += granularity;
}
//get all needed db values parallel
async.parallel([
function(callback)
{
//get all needed composite Changesets
async.forEach(compositesChangesetNeeded, function(item, callback)
{
composePadChangesets(padId, item.start, item.end, function(err, changeset)
{
if(ERR(err, callback)) return;
composedChangesets[item.start + "/" + item.end] = changeset;
callback();
});
}, callback);
},
function(callback)
{
//get all needed revision Dates
async.forEach(revTimesNeeded, function(revNum, callback)
{
pad.getRevisionDate(revNum, function(err, revDate)
{
if(ERR(err, callback)) return;
revisionDate[revNum] = Math.floor(revDate/1000);
callback();
});
}, callback);
},
//get the lines
function(callback)
{
getPadLines(padId, startNum-1, function(err, _lines)
{
if(ERR(err, callback)) return;
lines = _lines;
callback();
});
}
], callback);
},
//doesn't know what happens here excatly :/
function(callback)
{
var compositeStart = startNum;
while (compositeStart < endNum)
{
if (compositeStart + granularity > endNum)
{
break;
}
var compositeEnd = compositeStart + granularity;
var forwards = composedChangesets[compositeStart + "/" + compositeEnd];
var backwards = Changeset.inverse(forwards, lines.textlines, lines.alines, pad.apool());
Changeset.mutateAttributionLines(forwards, lines.alines, pad.apool());
Changeset.mutateTextLines(forwards, lines.textlines);
var forwards2 = Changeset.moveOpsToNewPool(forwards, pad.apool(), apool);
var backwards2 = Changeset.moveOpsToNewPool(backwards, pad.apool(), apool);
var t1, t2;
if (compositeStart == 0)
{
t1 = revisionDate[0];
}
else
{
t1 = revisionDate[compositeStart - 1];
}
t2 = revisionDate[compositeEnd - 1];
timeDeltas.push(t2 - t1);
forwardsChangesets.push(forwards2);
backwardsChangesets.push(backwards2);
compositeStart += granularity;
}
callback();
}
], function(err)
{
if(ERR(err, callback)) return;
callback(null, {forwardsChangesets: forwardsChangesets,
backwardsChangesets: backwardsChangesets,
apool: apool.toJsonable(),
actualEndNum: endNum,
timeDeltas: timeDeltas,
start: startNum,
granularity: granularity });
});
}
/**
* Tries to rebuild the getPadLines function of the original Etherpad
* https://github.com/ether/pad/blob/master/etherpad/src/etherpad/control/pad/pad_changeset_control.js#L263
*/
function getPadLines(padId, revNum, callback)
{
var atext;
var result = {};
var pad;
async.series([
//get the pad from the database
function(callback)
{
padManager.getPad(padId, function(err, _pad)
{
if(ERR(err, callback)) return;
pad = _pad;
callback();
});
},
//get the atext
function(callback)
{
if(revNum >= 0)
{
pad.getInternalRevisionAText(revNum, function(err, _atext)
{
if(ERR(err, callback)) return;
atext = _atext;
callback();
});
}
else
{
atext = Changeset.makeAText("\n");
callback(null);
}
},
function(callback)
{
result.textlines = Changeset.splitTextLines(atext.text);
result.alines = Changeset.splitAttributionLines(atext.attribs, atext.text);
callback(null);
}
], function(err)
{
if(ERR(err, callback)) return;
callback(null, result);
});
}
/**
* Tries to rebuild the composePadChangeset function of the original Etherpad
* https://github.com/ether/pad/blob/master/etherpad/src/etherpad/control/pad/pad_changeset_control.js#L241
*/
function composePadChangesets(padId, startNum, endNum, callback)
{
var pad;
var changesets = [];
var changeset;
async.series([
//get the pad from the database
function(callback)
{
padManager.getPad(padId, function(err, _pad)
{
if(ERR(err, callback)) return;
pad = _pad;
callback();
});
},
//fetch all changesets we need
function(callback)
{
var changesetsNeeded=[];
//create a array for all changesets, we will
//replace the values with the changeset later
for(var r=startNum;r<endNum;r++)
{
changesetsNeeded.push(r);
}
//get all changesets
async.forEach(changesetsNeeded, function(revNum,callback)
{
pad.getRevisionChangeset(revNum, function(err, value)
{
if(ERR(err, callback)) return;
changesets[revNum] = value;
callback();
});
},callback);
},
//compose Changesets
function(callback)
{
changeset = changesets[startNum];
var pool = pad.apool();
for(var r=startNum+1;r<endNum;r++)
{
var cs = changesets[r];
changeset = Changeset.compose(changeset, cs, pool);
}
callback(null);
}
],
//return err and changeset
function(err)
{
if(ERR(err, callback)) return;
callback(null, changeset);
});
}