From 08b8568f7b6d0942bca6606de23b6cda9a3adcfe Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Tue, 17 May 2011 16:33:54 +0100 Subject: [PATCH] Pads are now saved to the database, but some weird Bugs with the Attribute Pool --- node/MessageHandler.js | 245 +++++++++++++++++++++++++++-------------- node/Models/Pad.js | 102 +++++++++-------- node/PadManager.js | 47 ++++---- 3 files changed, 236 insertions(+), 158 deletions(-) diff --git a/node/MessageHandler.js b/node/MessageHandler.js index 2b3057333..bcb949571 100644 --- a/node/MessageHandler.js +++ b/node/MessageHandler.js @@ -243,85 +243,159 @@ function handleUserChanges(client, message) var baseRev = message.data.baseRev; var wireApool = (AttributePoolFactory.createAttributePool()).fromJsonable(message.data.apool); var changeset = message.data.changeset; - var pad = padManager.getPad(session2pad[client.sessionId], false); - - //ex. _checkChangesetAndPool - - //Copied from Etherpad, don't know what it does exactly - Changeset.checkRep(changeset); - Changeset.eachAttribNumber(changeset, function(n) { - if (! wireApool.getAttrib(n)) { - throw "Attribute pool is missing attribute "+n+" for changeset "+changeset; - } - }); - - //ex. adoptChangesetAttribs - - //Afaik, it copies the new attributes from the changeset, to the global Attribute Pool - changeset = Changeset.moveOpsToNewPool(changeset, wireApool, pad.pool); - - //ex. applyUserChanges - - var apool = pad.pool; - var r = baseRev; - - while (r < pad.getHeadRevisionNumber()) { - r++; - var c = pad.getRevisionChangeset(r); - changeset = Changeset.follow(c, changeset, false, apool); - } - - var prevText = pad.text(); - if (Changeset.oldLen(changeset) != prevText.length) { - throw "Can't apply USER_CHANGES "+changeset+" with oldLen " - + Changeset.oldLen(changeset) + " to document of length " + prevText.length; - } - - var thisAuthor = sessioninfos[client.sessionId].author; - - pad.appendRevision(changeset, thisAuthor); - - var correctionChangeset = _correctMarkersInPad(pad.atext, pad.pool); - if (correctionChangeset) { - pad.appendRevision(correctionChangeset); - } - - if (pad.text().lastIndexOf("\n\n") != pad.text().length-2) { - var nlChangeset = Changeset.makeSplice( - pad.text(), pad.text().length-1, 0, "\n"); - pad.appendRevision(nlChangeset); - } - - //ex. updatePadClients - - for(i in pad2sessions[pad.id]) - { - var session = pad2sessions[pad.id][i]; - var lastRev = sessioninfos[session].rev; - - while (lastRev < pad.getHeadRevisionNumber()) - { - var r = ++lastRev; - var author = pad.getRevisionAuthor(r); - if(author == sessioninfos[session].author) - { - socketio.clients[session].send({"type":"COLLABROOM","data":{type:"ACCEPT_COMMIT", newRev:r}}); - } - else - { - var forWire = Changeset.prepareForWire(pad.getRevisionChangeset(r), pad.pool); - var wireMsg = {"type":"COLLABROOM","data":{type:"NEW_CHANGES", newRev:r, - changeset: forWire.translated, - apool: forWire.pool, - author: author}}; - - socketio.clients[session].send(wireMsg); - } - } + var r, apool, pad; - sessioninfos[session].rev = pad.getHeadRevisionNumber(); - } + async.series([ + //get the pad + function(callback) + { + padManager.getPad(session2pad[client.sessionId], function(err, value) + { + pad = value; + callback(err); + }); + }, + //create the changeset + function(callback) + { + //ex. _checkChangesetAndPool + + //Copied from Etherpad, don't know what it does exactly + Changeset.checkRep(changeset); + Changeset.eachAttribNumber(changeset, function(n) { + if (! wireApool.getAttrib(n)) { + throw "Attribute pool is missing attribute "+n+" for changeset "+changeset; + } + }); + + //ex. adoptChangesetAttribs + + //Afaik, it copies the new attributes from the changeset, to the global Attribute Pool + changeset = Changeset.moveOpsToNewPool(changeset, wireApool, pad.pool); + + //ex. applyUserChanges + apool = pad.pool; + r = baseRev; + + //https://github.com/caolan/async#whilst + async.whilst( + function() { return r < pad.getHeadRevisionNumber(); }, + function(callback) + { + r++; + + pad.getRevisionChangeset(r, function(err, c) + { + if(err) + { + callback(err); + return; + } + else + { + changeset = Changeset.follow(c, changeset, false, apool); + callback(null); + } + }); + }, + //use the callback of the series function + callback + ); + }, + //do correction changesets, and send it to all users + function (callback) + { + var prevText = pad.text(); + if (Changeset.oldLen(changeset) != prevText.length) { + throw "Can't apply USER_CHANGES "+changeset+" with oldLen " + + Changeset.oldLen(changeset) + " to document of length " + prevText.length; + } + + var thisAuthor = sessioninfos[client.sessionId].author; + + pad.appendRevision(changeset, thisAuthor); + + var correctionChangeset = _correctMarkersInPad(pad.atext, pad.pool); + if (correctionChangeset) { + pad.appendRevision(correctionChangeset); + } + + if (pad.text().lastIndexOf("\n\n") != pad.text().length-2) { + var nlChangeset = Changeset.makeSplice(pad.text(), pad.text().length-1, 0, "\n"); + pad.appendRevision(nlChangeset); + } + + //ex. updatePadClients + + //go trough all sessions on this pad + async.forEach(pad2sessions[pad.id], function(session, callback) + { + var lastRev = sessioninfos[session].rev; + + //https://github.com/caolan/async#whilst + //send them all new changesets + async.whilst( + function (){ return lastRev < pad.getHeadRevisionNumber()}, + function(callback) + { + var author, revChangeset; + + var r = ++lastRev; + + async.parallel([ + function (callback) + { + pad.getRevisionAuthor(r, function(err, value) + { + author = value; + callback(err); + }); + }, + function (callback) + { + pad.getRevisionChangeset(r, function(err, value) + { + revChangeset = value; + callback(err); + }); + } + ], function(err) + { + if(err) + { + callback(err); + return; + } + + if(author == sessioninfos[session].author) + { + socketio.clients[session].send({"type":"COLLABROOM","data":{type:"ACCEPT_COMMIT", newRev:r}}); + } + else + { + var forWire = Changeset.prepareForWire(revChangeset, pad.pool); + var wireMsg = {"type":"COLLABROOM","data":{type:"NEW_CHANGES", newRev:r, + changeset: forWire.translated, + apool: forWire.pool, + author: author}}; + + socketio.clients[session].send(wireMsg); + } + + callback(null); + }); + }, + callback + ); + + sessioninfos[session].rev = pad.getHeadRevisionNumber(); + },callback); + } + ], function(err) + { + if(err) throw err; + }); } /** @@ -395,8 +469,9 @@ function handleClientReady(client, message) var author; var authorName; var authorColorId; + var pad; - async.waterfall([ + async.series([ //get all authordata of this new user function(callback) { @@ -423,6 +498,14 @@ function handleClientReady(client, message) authorName = value; callback(err); }); + }, + function(callback) + { + padManager.getPad(message.padId, function(err, value) + { + pad = value; + callback(err); + }); } ], callback); }); @@ -454,12 +537,6 @@ function handleClientReady(client, message) //Saves in pad2sessions that this session belongs to this pad pad2sessions[message.padId].push(sessionId); - - //Tell the PadManager that it should ensure that this Pad exist - padManager.ensurePadExists(message.padId); - - //Ask the PadManager for a function Wrapper for this Pad - var pad = padManager.getPad(message.padId, false); //prepare all values for the wire atext = pad.atext; diff --git a/node/Models/Pad.js b/node/Models/Pad.js index 3277157e1..812d27bd1 100644 --- a/node/Models/Pad.js +++ b/node/Models/Pad.js @@ -1,5 +1,7 @@ var Changeset = require("../Changeset"); var AttributePoolFactory = require("../AttributePoolFactory"); +var db = require("../db").db; +var async = require("async"); exports.startText = "Welcome to Etherpad Lite. This pad text is synchronized as you type, so that everyone viewing this page sees the same text."; @@ -27,23 +29,13 @@ Class('Pad', { getterName : 'apool' // legacy }, // pool - rev : { - is : 'rw', - init : [] - }, // rev - head : { is : 'rw', init : -1, getterName : 'getHeadRevisionNumber' }, // head - authors : { - is : 'rw', - init : [] - }, - - id : { is : 'rw' } + id : { is : 'r' } }, methods : { @@ -65,11 +57,12 @@ Class('Pad', { Changeset.copyAText(newAText, this.atext); var newRev = ++this.head; - this.rev[newRev] = {}; - this.rev[newRev].changeset = aChangeset; - this.rev[newRev].meta = {}; - this.rev[newRev].meta.author = author; - this.rev[newRev].meta.timestamp = new Date().getTime(); + + var newRevData = {}; + newRevData.changeset = aChangeset; + newRevData.meta = {}; + newRevData.meta.author = author; + newRevData.meta.timestamp = new Date().getTime(); //ex. getNumForAuthor if(author != '') @@ -77,34 +70,21 @@ Class('Pad', { if(newRev % 100 == 0) { - this.rev[newRev].meta.atext = this.atext; + newRevData.meta.atext = this.atext; } + db.set("pad:"+this.id+":revs:"+newRev, newRevData); + db.set("pad:"+this.id, {atext: this.atext, pool: this.pool.toJsonable(), head: this.head}); }, //appendRevision - getRevisionChangeset : function(revNum) + getRevisionChangeset : function(revNum, callback) { - - if(revNum < this.rev.length) - { - return this.rev[revNum].changeset; - } else { - throw 'this revision does not exist! : ' + revNum; - return null; - } - + db.getSub("pad:"+this.id+":revs:"+revNum, ["changeset"], callback); }, // getRevisionChangeset - getRevisionAuthor : function(revNum) - { - if(revNum < this.rev.length) - { - return this.rev[revNum].meta.author; - } else { - throw 'this revision author does not exist! : ' + revNum; - return null; - } - + getRevisionAuthor : function(revNum, callback) + { + db.getSub("pad:"+this.id+":revs:"+revNum, ["meta", "author"], callback); }, // getRevisionAuthor getAllAuthors : function() @@ -125,21 +105,39 @@ Class('Pad', { text : function() { return this.atext.text; - } + }, + + init : function (callback) + { + var _this = this; + + //try to load the pad + db.get("pad:"+this.id, function(err, value) + { + if(err) + { + callback(err, null); + return; + } + + //if this pad exists, load it + if(value != null) + { + _this.head = value.head; + _this.atext = value.atext; + _this.pool = _this.pool.fromJsonable(value.pool); + } + //this pad doesn't exist, so create it + else + { + var firstChangeset = Changeset.makeSplice("\n", 0, 0, exports.cleanText(exports.startText)); + + _this.appendRevision(firstChangeset, ''); + } + + callback(null); + }); + } }, // methods - - - after : { - - initialize : function (props) - { - this.id = props.id; - - var firstChangeset = Changeset.makeSplice("\n", 0, 0, exports.cleanText(exports.startText)); - - this.appendRevision(firstChangeset, ''); - } - - } }); diff --git a/node/PadManager.js b/node/PadManager.js index a44b58d9b..5255c2ffd 100644 --- a/node/PadManager.js +++ b/node/PadManager.js @@ -31,31 +31,34 @@ globalPads = []; * @param id A String with the id of the pad * @param createIfNotExist A Boolean which says the function if it should create the Pad if it not exist */ -exports.getPad = function(id, createIfNotExist) -{ - var pad = globalPads[id]; - - if(!pad && createIfNotExist == true) +exports.getPad = function(id, callback) +{ + var pad = globalPads[id]; + + //return pad if its already loaded + if(pad != null) + { + callback(null, pad); + } + //try to load pad + else { pad = new Pad(id); - globalPads[id] = pad; + + //initalize the pad + pad.init(function(err) + { + if(err) + { + callback(err, null); + } + else + { + globalPads[id] = pad; + callback(null, pad); + } + }); } - if(!pad) return null; - //globalPads[id].timestamp = new Date().getTime(); - - return pad; -} - -/** - * Ensures that the Pad exists - * @param id The Pad id - */ -exports.ensurePadExists = function(id) -{ - if(!globalPads[id]) - { - exports.getPad(id, true); - } }