Merge branch 'develop' into express-v3

Conflicts:
	src/node/hooks/express/errorhandling.js
This commit is contained in:
Marcel Klehr 2012-10-03 10:09:00 +02:00
commit 2684a1d295
23 changed files with 628 additions and 174 deletions

View file

@ -35,6 +35,7 @@ var cleanText = require("./Pad").cleanText;
/**GROUP FUNCTIONS*****/
/**********************/
exports.listAllGroups = groupManager.listAllGroups;
exports.createGroup = groupManager.createGroup;
exports.createGroupIfNotExistsFor = groupManager.createGroupIfNotExistsFor;
exports.deleteGroup = groupManager.deleteGroup;

View file

@ -26,6 +26,24 @@ var db = require("./DB").db;
var async = require("async");
var padManager = require("./PadManager");
var sessionManager = require("./SessionManager");
exports.listAllGroups = function(callback) {
db.get("groups", function (err, groups) {
if(ERR(err, callback)) return;
// there are no groups
if(groups == null) {
callback(null, {groupIDs: []});
return;
}
var groupIDs = [];
for ( var groupID in groups ) {
groupIDs.push(groupID);
}
callback(null, {groupIDs: groupIDs});
});
}
exports.deleteGroup = function(groupID, callback)
{
@ -105,6 +123,39 @@ exports.deleteGroup = function(groupID, callback)
db.remove("group2sessions:" + groupID);
db.remove("group:" + groupID);
callback();
},
//unlist the group
function(callback)
{
exports.listAllGroups(function(err, groups) {
if(ERR(err, callback)) return;
groups = groups? groups.groupIDs : [];
// it's not listed
if(groups.indexOf(groupID) == -1) {
callback();
return;
}
groups.splice(groups.indexOf(groupID), 1);
// store empty groupe list
if(groups.length == 0) {
db.set("groups", {});
callback();
return;
}
// regenerate group list
var newGroups = {};
async.forEach(groups, function(group, cb) {
newGroups[group] = 1;
cb();
},function() {
db.set("groups", newGroups);
callback();
});
});
}
], function(err)
{
@ -130,7 +181,24 @@ exports.createGroup = function(callback)
//create the group
db.set("group:" + groupID, {pads: {}});
callback(null, {groupID: groupID});
//list the group
exports.listAllGroups(function(err, groups) {
if(ERR(err, callback)) return;
groups = groups? groups.groupIDs : [];
groups.push(groupID);
// regenerate group list
var newGroups = {};
async.forEach(groups, function(group, cb) {
newGroups[group] = 1;
cb();
},function() {
db.set("groups", newGroups);
callback(null, {groupID: groupID});
});
});
}
exports.createGroupIfNotExistsFor = function(groupMapper, callback)

View file

@ -123,7 +123,7 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback)
}
var sessionIDs = sessionCookie.split(',');
async.foreach(sessionIDs, function(sessionID) {
async.forEach(sessionIDs, function(sessionID, callback) {
sessionManager.getSessionInfo(sessionID, function(err, sessionInfo) {
//skip session if it doesn't exist
if(err && err.message == "sessionID does not exist") return;
@ -141,8 +141,10 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback)
// There is a valid session
validSession = true;
sessionAuthor = sessionInfo.authorID;
callback();
});
}, callback)
}, callback);
},
//get author for token
function(callback)

View file

@ -40,6 +40,36 @@ catch(e)
//a list of all functions
var version =
{ "1":
{ "createGroup" : []
, "createGroupIfNotExistsFor" : ["groupMapper"]
, "deleteGroup" : ["groupID"]
, "listPads" : ["groupID"]
, "createPad" : ["padID", "text"]
, "createGroupPad" : ["groupID", "padName", "text"]
, "createAuthor" : ["name"]
, "createAuthorIfNotExistsFor": ["authorMapper" , "name"]
, "listPadsOfAuthor" : ["authorID"]
, "createSession" : ["groupID", "authorID", "validUntil"]
, "deleteSession" : ["sessionID"]
, "getSessionInfo" : ["sessionID"]
, "listSessionsOfGroup" : ["groupID"]
, "listSessionsOfAuthor" : ["authorID"]
, "getText" : ["padID", "rev"]
, "setText" : ["padID", "text"]
, "getHTML" : ["padID", "rev"]
, "setHTML" : ["padID", "html"]
, "getRevisionsCount" : ["padID"]
, "getLastEdited" : ["padID"]
, "deletePad" : ["padID"]
, "getReadOnlyID" : ["padID"]
, "setPublicStatus" : ["padID", "publicStatus"]
, "getPublicStatus" : ["padID"]
, "setPassword" : ["padID", "password"]
, "isPasswordProtected" : ["padID"]
, "listAuthorsOfPad" : ["padID"]
, "padUsersCount" : ["padID"]
}
, "1.1":
{ "createGroup" : []
, "createGroupIfNotExistsFor" : ["groupMapper"]
, "deleteGroup" : ["groupID"]
@ -71,6 +101,7 @@ var version =
, "getAuthorName" : ["authorID"]
, "padUsers" : ["padID"]
, "sendClientsMessage" : ["padID", "msg"]
, "listAllGroups" : []
}
};

View file

@ -436,15 +436,22 @@ function handleUserInfoUpdate(client, message)
}
/**
* Handles a USERINFO_UPDATE, that means that a user have changed his color or name. Anyway, we get both informations
* This Method is nearly 90% copied out of the Etherpad Source Code. So I can't tell you what happens here exactly
* Look at https://github.com/ether/pad/blob/master/etherpad/src/etherpad/collab/collab_server.js in the function applyUserChanges()
* Handles a USER_CHANGES message, where the client submits its local
* edits as a changeset.
*
* This handler's job is to update the incoming changeset so that it applies
* to the latest revision, then add it to the pad, broadcast the changes
* to all other clients, and send a confirmation to the submitting client.
*
* This function is based on a similar one in the original Etherpad.
* See https://github.com/ether/pad/blob/master/etherpad/src/etherpad/collab/collab_server.js in the function applyUserChanges()
*
* @param client the client that send this message
* @param message the message from the client
*/
function handleUserChanges(client, message)
{
//check if all ok
// Make sure all required fields are present
if(message.data.baseRev == null)
{
messageLogger.warn("Dropped message, USER_CHANGES Message has no baseRev!");
@ -465,6 +472,9 @@ function handleUserChanges(client, message)
var baseRev = message.data.baseRev;
var wireApool = (new AttributePool()).fromJsonable(message.data.apool);
var changeset = message.data.changeset;
// The client might disconnect between our callbacks. We should still
// finish processing the changeset, so keep a reference to the session.
var thisSession = sessioninfos[client.id];
var r, apool, pad;
@ -472,7 +482,7 @@ function handleUserChanges(client, message)
//get the pad
function(callback)
{
padManager.getPad(sessioninfos[client.id].padId, function(err, value)
padManager.getPad(thisSession.padId, function(err, value)
{
if(ERR(err, callback)) return;
pad = value;
@ -484,22 +494,23 @@ function handleUserChanges(client, message)
{
//ex. _checkChangesetAndPool
//Copied from Etherpad, don't know what it does exactly
try
{
//this looks like a changeset check, it throws errors sometimes
// Verify that the changeset has valid syntax and is in canonical form
Changeset.checkRep(changeset);
// Verify that the attribute indexes used in the changeset are all
// defined in the accompanying attribute pool.
Changeset.eachAttribNumber(changeset, function(n) {
if (! wireApool.getAttrib(n)) {
throw "Attribute pool is missing attribute "+n+" for changeset "+changeset;
}
});
}
//there is an error in this changeset, so just refuse it
catch(e)
{
console.warn("Can't apply USER_CHANGES "+changeset+", cause it faild checkRep");
// There is an error in this changeset, so just refuse it
console.warn("Can't apply USER_CHANGES "+changeset+", because it failed checkRep");
client.json.send({disconnect:"badChangeset"});
return;
}
@ -512,7 +523,10 @@ function handleUserChanges(client, message)
//ex. applyUserChanges
apool = pad.pool;
r = baseRev;
// The client's changeset might not be based on the latest revision,
// since other clients are sending changes at the same time.
// Update the changeset so that it can be applied to the latest revision.
//https://github.com/caolan/async#whilst
async.whilst(
function() { return r < pad.getHeadRevisionNumber(); },
@ -523,9 +537,18 @@ function handleUserChanges(client, message)
pad.getRevisionChangeset(r, function(err, c)
{
if(ERR(err, callback)) return;
// At this point, both "c" (from the pad) and "changeset" (from the
// client) are relative to revision r - 1. The follow function
// rebases "changeset" so that it is relative to revision r
// and can be applied after "c".
changeset = Changeset.follow(c, changeset, false, apool);
callback(null);
if ((r - baseRev) % 200 == 0) { // don't let the stack get too deep
async.nextTick(callback);
} else {
callback(null);
}
});
},
//use the callback of the series function
@ -545,15 +568,14 @@ function handleUserChanges(client, message)
return;
}
var thisAuthor = sessioninfos[client.id].author;
pad.appendRevision(changeset, thisAuthor);
pad.appendRevision(changeset, thisSession.author);
var correctionChangeset = _correctMarkersInPad(pad.atext, pad.pool);
if (correctionChangeset) {
pad.appendRevision(correctionChangeset);
}
// Make sure the pad always ends with an empty line.
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);
@ -860,6 +882,13 @@ function handleClientReady(client, message)
},
function(callback)
{
//Check that the client is still here. It might have disconnected between callbacks.
if(sessioninfos[client.id] === undefined)
{
callback();
return;
}
//Check if this author is already on the pad, if yes, kick the other sessions!
if(pad2sessions[padIds.padId])
{
@ -874,10 +903,9 @@ function handleClientReady(client, message)
}
//Save in sessioninfos that this session belonges to this pad
var sessionId=String(client.id);
sessioninfos[sessionId].padId = padIds.padId;
sessioninfos[sessionId].readOnlyPadId = padIds.readOnlyPadId;
sessioninfos[sessionId].readonly = padIds.readonly;
sessioninfos[client.id].padId = padIds.padId;
sessioninfos[client.id].readOnlyPadId = padIds.readOnlyPadId;
sessioninfos[client.id].readonly = padIds.readonly;
//check if there is already a pad2sessions entry, if not, create one
if(!pad2sessions[padIds.padId])
@ -886,7 +914,7 @@ function handleClientReady(client, message)
}
//Saves in pad2sessions that this session belongs to this pad
pad2sessions[padIds.padId].push(sessionId);
pad2sessions[padIds.padId].push(client.id);
//prepare all values for the wire
var atext = Changeset.cloneAText(pad.atext);
@ -951,26 +979,22 @@ function handleClientReady(client, message)
clientVars.userName = authorName;
}
if(sessioninfos[client.id] !== undefined)
//If this is a reconnect, we don't have to send the client the ClientVars again
if(message.reconnect == true)
{
//This is a reconnect, so we don't have to send the client the ClientVars again
if(message.reconnect == true)
{
//Save the revision in sessioninfos, we take the revision from the info the client send to us
sessioninfos[client.id].rev = message.client_rev;
}
//This is a normal first connect
else
{
//Send the clientVars to the Client
client.json.send({type: "CLIENT_VARS", data: clientVars});
//Save the revision in sessioninfos
sessioninfos[client.id].rev = pad.getHeadRevisionNumber();
}
//Save the revision and the author id in sessioninfos
sessioninfos[client.id].author = author;
//Save the revision in sessioninfos, we take the revision from the info the client send to us
sessioninfos[client.id].rev = message.client_rev;
}
//This is a normal first connect
else
{
//Send the clientVars to the Client
client.json.send({type: "CLIENT_VARS", data: clientVars});
//Save the current revision in sessioninfos, should be the same as in clientVars
sessioninfos[client.id].rev = pad.getHeadRevisionNumber();
}
sessioninfos[client.id].author = author;
//prepare the notification for the other users on the pad, that this user joined
var messageToTheOtherUsers = {

View file

@ -39,11 +39,8 @@ exports.expressCreateServer = function (hook_name, args, cb) {
// allowing you to respond however you like
res.send(500, { error: 'Sorry, something bad happened!' });
console.error(err.stack? err.stack : err.toString());
exports.gracefulShutdown();
next();
})
//connect graceful shutdown with sigint and uncaughtexception
if(os.type().indexOf("Windows") == -1) {
//sigint is so far not working on windows