mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-04-20 15:36:16 -04:00
Pads are now saved to the database, but some weird Bugs with the Attribute Pool
This commit is contained in:
parent
73efbf216d
commit
08b8568f7b
3 changed files with 236 additions and 158 deletions
|
@ -243,85 +243,159 @@ function handleUserChanges(client, message)
|
||||||
var baseRev = message.data.baseRev;
|
var baseRev = message.data.baseRev;
|
||||||
var wireApool = (AttributePoolFactory.createAttributePool()).fromJsonable(message.data.apool);
|
var wireApool = (AttributePoolFactory.createAttributePool()).fromJsonable(message.data.apool);
|
||||||
var changeset = message.data.changeset;
|
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)
|
var r, apool, pad;
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 author;
|
||||||
var authorName;
|
var authorName;
|
||||||
var authorColorId;
|
var authorColorId;
|
||||||
|
var pad;
|
||||||
|
|
||||||
async.waterfall([
|
async.series([
|
||||||
//get all authordata of this new user
|
//get all authordata of this new user
|
||||||
function(callback)
|
function(callback)
|
||||||
{
|
{
|
||||||
|
@ -423,6 +498,14 @@ function handleClientReady(client, message)
|
||||||
authorName = value;
|
authorName = value;
|
||||||
callback(err);
|
callback(err);
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
function(callback)
|
||||||
|
{
|
||||||
|
padManager.getPad(message.padId, function(err, value)
|
||||||
|
{
|
||||||
|
pad = value;
|
||||||
|
callback(err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
], callback);
|
], callback);
|
||||||
});
|
});
|
||||||
|
@ -454,12 +537,6 @@ function handleClientReady(client, message)
|
||||||
|
|
||||||
//Saves in pad2sessions that this session belongs to this pad
|
//Saves in pad2sessions that this session belongs to this pad
|
||||||
pad2sessions[message.padId].push(sessionId);
|
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
|
//prepare all values for the wire
|
||||||
atext = pad.atext;
|
atext = pad.atext;
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
var Changeset = require("../Changeset");
|
var Changeset = require("../Changeset");
|
||||||
var AttributePoolFactory = require("../AttributePoolFactory");
|
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.";
|
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
|
getterName : 'apool' // legacy
|
||||||
}, // pool
|
}, // pool
|
||||||
|
|
||||||
rev : {
|
|
||||||
is : 'rw',
|
|
||||||
init : []
|
|
||||||
}, // rev
|
|
||||||
|
|
||||||
head : {
|
head : {
|
||||||
is : 'rw',
|
is : 'rw',
|
||||||
init : -1,
|
init : -1,
|
||||||
getterName : 'getHeadRevisionNumber'
|
getterName : 'getHeadRevisionNumber'
|
||||||
}, // head
|
}, // head
|
||||||
|
|
||||||
authors : {
|
id : { is : 'r' }
|
||||||
is : 'rw',
|
|
||||||
init : []
|
|
||||||
},
|
|
||||||
|
|
||||||
id : { is : 'rw' }
|
|
||||||
},
|
},
|
||||||
|
|
||||||
methods : {
|
methods : {
|
||||||
|
@ -65,11 +57,12 @@ Class('Pad', {
|
||||||
Changeset.copyAText(newAText, this.atext);
|
Changeset.copyAText(newAText, this.atext);
|
||||||
|
|
||||||
var newRev = ++this.head;
|
var newRev = ++this.head;
|
||||||
this.rev[newRev] = {};
|
|
||||||
this.rev[newRev].changeset = aChangeset;
|
var newRevData = {};
|
||||||
this.rev[newRev].meta = {};
|
newRevData.changeset = aChangeset;
|
||||||
this.rev[newRev].meta.author = author;
|
newRevData.meta = {};
|
||||||
this.rev[newRev].meta.timestamp = new Date().getTime();
|
newRevData.meta.author = author;
|
||||||
|
newRevData.meta.timestamp = new Date().getTime();
|
||||||
|
|
||||||
//ex. getNumForAuthor
|
//ex. getNumForAuthor
|
||||||
if(author != '')
|
if(author != '')
|
||||||
|
@ -77,34 +70,21 @@ Class('Pad', {
|
||||||
|
|
||||||
if(newRev % 100 == 0)
|
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
|
}, //appendRevision
|
||||||
|
|
||||||
getRevisionChangeset : function(revNum)
|
getRevisionChangeset : function(revNum, callback)
|
||||||
{
|
{
|
||||||
|
db.getSub("pad:"+this.id+":revs:"+revNum, ["changeset"], callback);
|
||||||
if(revNum < this.rev.length)
|
|
||||||
{
|
|
||||||
return this.rev[revNum].changeset;
|
|
||||||
} else {
|
|
||||||
throw 'this revision does not exist! : ' + revNum;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}, // getRevisionChangeset
|
}, // getRevisionChangeset
|
||||||
|
|
||||||
getRevisionAuthor : function(revNum)
|
getRevisionAuthor : function(revNum, callback)
|
||||||
{
|
{
|
||||||
if(revNum < this.rev.length)
|
db.getSub("pad:"+this.id+":revs:"+revNum, ["meta", "author"], callback);
|
||||||
{
|
|
||||||
return this.rev[revNum].meta.author;
|
|
||||||
} else {
|
|
||||||
throw 'this revision author does not exist! : ' + revNum;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}, // getRevisionAuthor
|
}, // getRevisionAuthor
|
||||||
|
|
||||||
getAllAuthors : function()
|
getAllAuthors : function()
|
||||||
|
@ -125,21 +105,39 @@ Class('Pad', {
|
||||||
text : function()
|
text : function()
|
||||||
{
|
{
|
||||||
return this.atext.text;
|
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
|
}, // methods
|
||||||
|
|
||||||
|
|
||||||
after : {
|
|
||||||
|
|
||||||
initialize : function (props)
|
|
||||||
{
|
|
||||||
this.id = props.id;
|
|
||||||
|
|
||||||
var firstChangeset = Changeset.makeSplice("\n", 0, 0, exports.cleanText(exports.startText));
|
|
||||||
|
|
||||||
this.appendRevision(firstChangeset, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -31,31 +31,34 @@ globalPads = [];
|
||||||
* @param id A String with the id of the pad
|
* @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
|
* @param createIfNotExist A Boolean which says the function if it should create the Pad if it not exist
|
||||||
*/
|
*/
|
||||||
exports.getPad = function(id, createIfNotExist)
|
exports.getPad = function(id, callback)
|
||||||
{
|
{
|
||||||
var pad = globalPads[id];
|
var pad = globalPads[id];
|
||||||
|
|
||||||
if(!pad && createIfNotExist == true)
|
//return pad if its already loaded
|
||||||
|
if(pad != null)
|
||||||
|
{
|
||||||
|
callback(null, pad);
|
||||||
|
}
|
||||||
|
//try to load pad
|
||||||
|
else
|
||||||
{
|
{
|
||||||
pad = new Pad(id);
|
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();
|
//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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue