From 914d79ad20d61920996ec35326c199f7e38a0050 Mon Sep 17 00:00:00 2001 From: Egil Moeller Date: Mon, 23 Apr 2012 12:52:30 +0200 Subject: [PATCH] Unified timeslider and pad editing protocol / component --- src/node/handler/PadMessageHandler.js | 348 +++++++++++- src/node/handler/TimesliderMessageHandler.js | 534 ------------------- src/node/hooks/express/socketio.js | 2 - src/static/js/broadcast.js | 40 +- src/static/js/broadcast_revisions.js | 2 +- src/static/js/broadcast_slider.js | 4 +- src/static/js/pad.js | 2 +- src/static/js/timeslider.js | 5 +- 8 files changed, 359 insertions(+), 578 deletions(-) delete mode 100644 src/node/handler/TimesliderMessageHandler.js diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index 4a570e21..194ee6d5 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -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 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;rYou have no permission to access this pad") + } else { + console.warn("Unknown message type: " + message.type); + console.warn(["XXX", message]); } }); @@ -107,7 +110,7 @@ function sendSocketMsg(type, data) var sessionID = readCookie("sessionID"); var password = readCookie("password"); - var msg = { "component" : "timeslider", + var msg = { "component" : "pad", // FIXME: Remove this stupidity! "type": type, "data": data, "padId": padId,