diff --git a/node/db/AuthorManager.js b/node/db/AuthorManager.js
index 7c054a56f..90984cfd2 100644
--- a/node/db/AuthorManager.js
+++ b/node/db/AuthorManager.js
@@ -19,163 +19,167 @@
*/
var ERR = require("async-stacktrace");
-var db = require("./DB").db;
var async = require("async");
var randomString = require("../utils/randomstring");
+var AuthorManager = function AuthorManager(db) {
+ this.db = db;
+};
+
+exports.AuthorManager = AuthorManager;
+
/**
* Checks if the author exists
*/
-exports.doesAuthorExists = function (authorID, callback)
+AuthorManager.prototype.doesAuthorExists = function (authorID, callback)
{
//check if the database entry of this author exists
- db.get("globalAuthor:" + authorID, function (err, author)
+ this.db.get("globalAuthor:" + authorID, function (err, author)
{
if(ERR(err, callback)) return;
callback(null, author != null);
});
-}
+};
/**
- * Returns the AuthorID for a token.
- * @param {String} token The token
- * @param {Function} callback callback (err, author)
+ * Returns the AuthorID for a token.
+ * @param {String} token The token
+ * @param {Function} callback callback (err, author)
*/
-exports.getAuthor4Token = function (token, callback)
+AuthorManager.prototype.getAuthor4Token = function (token, callback)
{
- mapAuthorWithDBKey("token2author", token, function(err, author)
+ this.mapAuthorWithDBKey("token2author", token, function(err, author)
{
if(ERR(err, callback)) return;
//return only the sub value authorID
callback(null, author ? author.authorID : author);
});
-}
+};
/**
- * Returns the AuthorID for a mapper.
+ * Returns the AuthorID for a mapper.
* @param {String} token The mapper
- * @param {Function} callback callback (err, author)
+ * @param {Function} callback callback (err, author)
*/
-exports.createAuthorIfNotExistsFor = function (authorMapper, name, callback)
+AuthorManager.prototype.createAuthorIfNotExistsFor = function (authorMapper, name, callback)
{
- mapAuthorWithDBKey("mapper2author", authorMapper, function(err, author)
+ var that = this;
+ this.mapAuthorWithDBKey("mapper2author", authorMapper, function(err, author)
{
if(ERR(err, callback)) return;
-
+
//set the name of this author
if(name)
- exports.setAuthorName(author.authorID, name);
-
+ that.setAuthorName(author.authorID, name);
+
//return the authorID
callback(null, author);
});
-}
+};
/**
* Returns the AuthorID for a mapper. We can map using a mapperkey,
* so far this is token2author and mapper2author
- * @param {String} mapperkey The database key name for this mapper
+ * @param {String} mapperkey The database key name for this mapper
* @param {String} mapper The mapper
- * @param {Function} callback callback (err, author)
+ * @param {Function} callback callback (err, author)
*/
-function mapAuthorWithDBKey (mapperkey, mapper, callback)
-{
+AuthorManager.prototype.mapAuthorWithDBKey = function mapAuthorWithDBKey (mapperkey, mapper, callback) {
+ var that = this;
//try to map to an author
- db.get(mapperkey + ":" + mapper, function (err, author)
+ this.db.get(mapperkey + ":" + mapper, function (err, author)
{
if(ERR(err, callback)) return;
-
+
//there is no author with this mapper, so create one
if(author == null)
{
- exports.createAuthor(null, function(err, author)
- {
+ that.createAuthor(null, function(err, author) {
if(ERR(err, callback)) return;
-
+
//create the token2author relation
- db.set(mapperkey + ":" + mapper, author.authorID);
-
+ that.db.set(mapperkey + ":" + mapper, author.authorID);
+
//return the author
callback(null, author);
});
}
//there is a author with this mapper
- else
- {
+ else {
//update the timestamp of this author
- db.setSub("globalAuthor:" + author, ["timestamp"], new Date().getTime());
-
+ that.db.setSub("globalAuthor:" + author, ["timestamp"], new Date().getTime());
+
//return the author
callback(null, {authorID: author});
}
});
-}
+};
/**
- * Internal function that creates the database entry for an author
- * @param {String} name The name of the author
+ * Internal function that creates the database entry for an author
+ * @param {String} name The name of the author
*/
-exports.createAuthor = function(name, callback)
+AuthorManager.prototype.createAuthor = function(name, callback)
{
//create the new author name
var author = "a." + randomString(16);
-
+
//create the globalAuthors db entry
var authorObj = {"colorId" : Math.floor(Math.random()*32), "name": name, "timestamp": new Date().getTime()};
-
+
//set the global author db entry
- db.set("globalAuthor:" + author, authorObj);
-
+ this.db.set("globalAuthor:" + author, authorObj);
+
callback(null, {authorID: author});
-}
+};
/**
* Returns the Author Obj of the author
* @param {String} author The id of the author
* @param {Function} callback callback(err, authorObj)
*/
-exports.getAuthor = function (author, callback)
+AuthorManager.prototype.getAuthor = function (author, callback)
{
- db.get("globalAuthor:" + author, callback);
-}
+ this.db.get("globalAuthor:" + author, callback);
+};
/**
* Returns the color Id of the author
* @param {String} author The id of the author
* @param {Function} callback callback(err, colorId)
*/
-exports.getAuthorColorId = function (author, callback)
+AuthorManager.prototype.getAuthorColorId = function (author, callback)
{
- db.getSub("globalAuthor:" + author, ["colorId"], callback);
-}
+ this.db.getSub("globalAuthor:" + author, ["colorId"], callback);
+};
/**
* Sets the color Id of the author
* @param {String} author The id of the author
* @param {Function} callback (optional)
*/
-exports.setAuthorColorId = function (author, colorId, callback)
+AuthorManager.prototype.setAuthorColorId = function (author, colorId, callback)
{
- db.setSub("globalAuthor:" + author, ["colorId"], colorId, callback);
-}
+ this.db.setSub("globalAuthor:" + author, ["colorId"], colorId, callback);
+};
/**
* Returns the name of the author
* @param {String} author The id of the author
* @param {Function} callback callback(err, name)
*/
-exports.getAuthorName = function (author, callback)
+AuthorManager.prototype.getAuthorName = function (author, callback)
{
- db.getSub("globalAuthor:" + author, ["name"], callback);
-}
+ this.db.getSub("globalAuthor:" + author, ["name"], callback);
+};
/**
* Sets the name of the author
* @param {String} author The id of the author
* @param {Function} callback (optional)
*/
-exports.setAuthorName = function (author, name, callback)
+AuthorManager.prototype.setAuthorName = function (author, name, callback)
{
- db.setSub("globalAuthor:" + author, ["name"], name, callback);
-}
+ this.db.setSub("globalAuthor:" + author, ["name"], name, callback);
+};
diff --git a/node/db/DB.js b/node/db/DB.js
index 7273c83e3..589425eb3 100644
--- a/node/db/DB.js
+++ b/node/db/DB.js
@@ -1,5 +1,5 @@
/**
- * The DB Module provides a database initalized with the settings
+ * The DB Module provides a database initalized with the settings
* provided by the settings module
*/
@@ -20,38 +20,25 @@
*/
var ueberDB = require("ueberDB");
-var settings = require("../utils/Settings");
var log4js = require('log4js');
-//set database settings
-var db = new ueberDB.database(settings.dbType, settings.dbSettings, null, log4js.getLogger("ueberDB"));
-
-/**
- * The UeberDB Object that provides the database functions
- */
-exports.db = null;
-
/**
* Initalizes the database with the settings provided by the settings module
- * @param {Function} callback
+ * @param {Function} callback
*/
-exports.init = function(callback)
-{
- //initalize the database async
- db.init(function(err)
- {
- //there was an error while initializing the database, output it and stop
- if(err)
- {
- console.error("ERROR: Problem while initalizing the database");
- console.error(err.stack ? err.stack : err);
- process.exit(1);
- }
- //everything ok
- else
- {
- exports.db = db;
- callback(null);
- }
- });
-}
+exports.init = function init(settings, callback) {
+ //set database settings
+ console.log(settings);
+ var db = new ueberDB.database(settings.dbType, settings.dbSettings, null, log4js.getLogger("ueberDB"));
+
+ //initalize the database async
+ db.init(function(error){
+ //there was an error while initializing the database, output it and stop
+ if(error) {
+ callback(error, null);
+ }
+ else {
+ callback(null, db);
+ }
+ });
+};
diff --git a/node/db/GroupManager.js b/node/db/GroupManager.js
index 7e3b7d6d1..3273cab43 100644
--- a/node/db/GroupManager.js
+++ b/node/db/GroupManager.js
@@ -17,28 +17,35 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
+
var ERR = require("async-stacktrace");
var customError = require("../utils/customError");
var randomString = require("../utils/randomstring");
-var db = require("./DB").db;
var async = require("async");
-var padManager = require("./PadManager");
-var sessionManager = require("./SessionManager");
-
-exports.deleteGroup = function(groupID, callback)
+
+var GroupManager = function GroupManager(db, padManager, SessionManager) {
+ this.db = db;
+ this.padManager = padManager;
+ this.SessionManager = SessionManager;
+
+};
+
+exports.GroupManager = GroupManager;
+
+GroupManager.prototype.deleteGroup = function(groupID, callback)
{
var group;
+ var that = this;
async.series([
- //ensure group exists
+ //ensure group exists
function (callback)
{
//try to get the group entry
- db.get("group:" + groupID, function (err, _group)
+ that.db.get("group:" + groupID, function (err, _group)
{
if(ERR(err, callback)) return;
-
+
//group does not exist
if(_group == null)
{
@@ -61,14 +68,14 @@ exports.deleteGroup = function(groupID, callback)
{
padIDs.push(i);
}
-
- //loop trough all pads and delete them
+
+ //loop trough all pads and delete them
async.forEach(padIDs, function(padID, callback)
{
- padManager.getPad(padID, function(err, pad)
+ that.padManager.getPad(padID, function(err, pad)
{
if(ERR(err, callback)) return;
-
+
pad.remove(callback);
});
}, callback);
@@ -77,21 +84,21 @@ exports.deleteGroup = function(groupID, callback)
function(callback)
{
//try to get the group entry
- db.get("group2sessions:" + groupID, function (err, group2sessions)
+ that.db.get("group2sessions:" + groupID, function (err, group2sessions)
{
if(ERR(err, callback)) return;
-
+
//skip if there is no group2sessions entry
if(group2sessions == null) {callback(); return}
-
+
//collect all sessions in an array, that allows us to use async.forEach
var sessions = [];
for(var i in group2sessions.sessionsIDs)
{
sessions.push(i);
}
-
- //loop trough all sessions and delete them
+
+ //loop trough all sessions and delete them
async.forEach(sessions, function(session, callback)
{
sessionManager.deleteSession(session, callback);
@@ -101,8 +108,8 @@ exports.deleteGroup = function(groupID, callback)
//remove group and group2sessions entry
function(callback)
{
- db.remove("group2sessions:" + groupID);
- db.remove("group:" + groupID);
+ that.db.remove("group2sessions:" + groupID);
+ that.db.remove("group:" + groupID);
callback();
}
], function(err)
@@ -110,52 +117,53 @@ exports.deleteGroup = function(groupID, callback)
if(ERR(err, callback)) return;
callback();
});
-}
-
-exports.doesGroupExist = function(groupID, callback)
+};
+
+GroupManager.prototype.doesGroupExist = function(groupID, callback)
{
//try to get the group entry
- db.get("group:" + groupID, function (err, group)
+ this.db.get("group:" + groupID, function (err, group)
{
if(ERR(err, callback)) return;
callback(null, group != null);
});
-}
+};
-exports.createGroup = function(callback)
+GroupManager.prototype.createGroup = function(callback)
{
//search for non existing groupID
var groupID = "g." + randomString(16);
-
- //create the group
- db.set("group:" + groupID, {pads: {}});
- callback(null, {groupID: groupID});
-}
-exports.createGroupIfNotExistsFor = function(groupMapper, callback)
+ //create the group
+ this.db.set("group:" + groupID, {pads: {}});
+ callback(null, {groupID: groupID});
+};
+
+GroupManager.prototype.createGroupIfNotExistsFor = function(groupMapper, callback)
{
+ var that = this;
//ensure mapper is optional
if(typeof groupMapper != "string")
{
callback(new customError("groupMapper is no string","apierror"));
return;
}
-
+
//try to get a group for this mapper
- db.get("mapper2group:"+groupMapper, function(err, groupID)
+ this.db.get("mapper2group:"+groupMapper, function(err, groupID)
{
if(ERR(err, callback)) return;
-
+
//there is no group for this mapper, let's create a group
if(groupID == null)
{
- exports.createGroup(function(err, responseObj)
+ that.createGroup(function(err, responseObj)
{
if(ERR(err, callback)) return;
-
+
//create the mapper entry for this group
- db.set("mapper2group:"+groupMapper, responseObj.groupID);
-
+ that.db.set("mapper2group:"+groupMapper, responseObj.groupID);
+
callback(null, responseObj);
});
}
@@ -166,21 +174,22 @@ exports.createGroupIfNotExistsFor = function(groupMapper, callback)
callback(null, {groupID: groupID});
}
});
-}
+};
-exports.createGroupPad = function(groupID, padName, text, callback)
+GroupManager.prototype.createGroupPad = function(groupID, padName, text, callback)
{
+ var that = this;
//create the padID
var padID = groupID + "$" + padName;
async.series([
- //ensure group exists
+ //ensure group exists
function (callback)
{
- exports.doesGroupExist(groupID, function(err, exists)
+ that.doesGroupExist(groupID, function(err, exists)
{
if(ERR(err, callback)) return;
-
+
//group does not exist
if(exists == false)
{
@@ -196,12 +205,12 @@ exports.createGroupPad = function(groupID, padName, text, callback)
//ensure pad does not exists
function (callback)
{
- padManager.doesPadExists(padID, function(err, exists)
+ that.padManager.doesPadExists(padID, function(err, exists)
{
if(ERR(err, callback)) return;
-
+
//pad exists already
- if(exists == true)
+ if(exists)
{
callback(new customError("padName does already exist","apierror"));
}
@@ -215,7 +224,7 @@ exports.createGroupPad = function(groupID, padName, text, callback)
//create the pad
function (callback)
{
- padManager.getPad(padID, text, function(err)
+ that.padManager.getPad(padID, text, function(err)
{
if(ERR(err, callback)) return;
callback();
@@ -224,7 +233,7 @@ exports.createGroupPad = function(groupID, padName, text, callback)
//create an entry in the group for this pad
function (callback)
{
- db.setSub("group:" + groupID, ["pads", padID], 1);
+ that.db.setSub("group:" + groupID, ["pads", padID], 1);
callback();
}
], function(err)
@@ -232,27 +241,28 @@ exports.createGroupPad = function(groupID, padName, text, callback)
if(ERR(err, callback)) return;
callback(null, {padID: padID});
});
-}
+};
-exports.listPads = function(groupID, callback)
+GroupManager.prototype.listPads = function(groupID, callback)
{
- exports.doesGroupExist(groupID, function(err, exists)
+ var that = this;
+ this.doesGroupExist(groupID, function(err, exists)
{
if(ERR(err, callback)) return;
-
+
//group does not exist
- if(exists == false)
+ if(!exists)
{
callback(new customError("groupID does not exist","apierror"));
}
//group exists, let's get the pads
else
{
- db.getSub("group:" + groupID, ["pads"], function(err, pads)
+ that.db.getSub("group:" + groupID, ["pads"], function(err, pads)
{
if(ERR(err, callback)) return;
callback(null, {padIDs: pads});
});
}
});
-}
+};
diff --git a/node/db/Pad.js b/node/db/Pad.js
index 632eebe8c..7ae72597e 100644
--- a/node/db/Pad.js
+++ b/node/db/Pad.js
@@ -1,17 +1,11 @@
/**
- * The pad object, defined with joose
+ * The pad object
*/
var ERR = require("async-stacktrace");
var Changeset = require("../utils/Changeset");
var AttributePoolFactory = require("../utils/AttributePoolFactory");
-var db = require("./DB").db;
var async = require("async");
-var settings = require('../utils/Settings');
-var authorManager = require("./AuthorManager");
-var padManager = require("./PadManager");
-var padMessageHandler = require("../handler/PadMessageHandler");
-var readOnlyManager = require("./ReadOnlyManager");
var crypto = require("crypto");
/**
@@ -73,8 +67,8 @@ Pad.prototype.appendRevision = function appendRevision(aChangeset, author) {
newRevData.meta.atext = this.atext;
}
- db.set("pad:"+this.id+":revs:"+newRev, newRevData);
- db.set("pad:"+this.id, {atext: this.atext,
+ this.db.set("pad:"+this.id+":revs:"+newRev, newRevData);
+ this.db.set("pad:"+this.id, {atext: this.atext,
pool: this.pool.toJsonable(),
head: this.head,
chatHead: this.chatHead,
@@ -83,15 +77,15 @@ Pad.prototype.appendRevision = function appendRevision(aChangeset, author) {
};
Pad.prototype.getRevisionChangeset = function getRevisionChangeset(revNum, callback) {
- db.getSub("pad:"+this.id+":revs:"+revNum, ["changeset"], callback);
+ this.db.getSub("pad:"+this.id+":revs:"+revNum, ["changeset"], callback);
};
Pad.prototype.getRevisionAuthor = function getRevisionAuthor(revNum, callback) {
- db.getSub("pad:"+this.id+":revs:"+revNum, ["meta", "author"], callback);
+ this.db.getSub("pad:"+this.id+":revs:"+revNum, ["meta", "author"], callback);
};
Pad.prototype.getRevisionDate = function getRevisionDate(revNum, callback) {
- db.getSub("pad:"+this.id+":revs:"+revNum, ["meta", "timestamp"], callback);
+ this.db.getSub("pad:"+this.id+":revs:"+revNum, ["meta", "timestamp"], callback);
};
Pad.prototype.getAllAuthors = function getAllAuthors() {
@@ -109,7 +103,7 @@ Pad.prototype.getAllAuthors = function getAllAuthors() {
};
Pad.prototype.getInternalRevisionAText = function getInternalRevisionAText(targetRev, callback) {
- var _this = this;
+ var that = this;
var keyRev = this.getKeyRevisionNumber(targetRev);
var atext;
@@ -132,7 +126,7 @@ Pad.prototype.getInternalRevisionAText = function getInternalRevisionAText(targe
//get the atext of the key revision
function (callback)
{
- db.getSub("pad:"+_this.id+":revs:"+keyRev, ["meta", "atext"], function(err, _atext)
+ that.db.getSub("pad:"+that.id+":revs:"+keyRev, ["meta", "atext"], function(err, _atext)
{
if(ERR(err, callback)) return;
atext = Changeset.cloneAText(_atext);
@@ -144,7 +138,7 @@ Pad.prototype.getInternalRevisionAText = function getInternalRevisionAText(targe
{
async.forEach(neededChangesets, function(item, callback)
{
- _this.getRevisionChangeset(item, function(err, changeset)
+ that.getRevisionChangeset(item, function(err, changeset)
{
if(ERR(err, callback)) return;
changesets[item] = changeset;
@@ -157,7 +151,7 @@ Pad.prototype.getInternalRevisionAText = function getInternalRevisionAText(targe
//apply all changesets to the key changeset
function(callback)
{
- var apool = _this.apool();
+ var apool = that.apool();
var curRev = keyRev;
while (curRev < targetRev)
@@ -200,20 +194,20 @@ Pad.prototype.setText = function setText(newText) {
Pad.prototype.appendChatMessage = function appendChatMessage(text, userId, time) {
this.chatHead++;
//save the chat entry in the database
- db.set("pad:"+this.id+":chat:"+this.chatHead, {"text": text, "userId": userId, "time": time});
+ this.db.set("pad:"+this.id+":chat:"+this.chatHead, {"text": text, "userId": userId, "time": time});
//save the new chat head
- db.setSub("pad:"+this.id, ["chatHead"], this.chatHead);
+ this.db.setSub("pad:"+this.id, ["chatHead"], this.chatHead);
};
Pad.prototype.getChatMessage = function getChatMessage(entryNum, callback) {
- var _this = this;
+ var that = this;
var entry;
async.series([
//get the chat entry
function(callback)
{
- db.get("pad:"+_this.id+":chat:"+entryNum, function(err, _entry)
+ that.db.get("pad:"+that.id+":chat:"+entryNum, function(err, _entry)
{
if(ERR(err, callback)) return;
entry = _entry;
@@ -231,7 +225,7 @@ Pad.prototype.getChatMessage = function getChatMessage(entryNum, callback) {
}
//get the authorName
- authorManager.getAuthorName(entry.userId, function(err, authorName)
+ that.authorManager.getAuthorName(entry.userId, function(err, authorName)
{
if(ERR(err, callback)) return;
entry.userName = authorName;
@@ -253,7 +247,7 @@ Pad.prototype.getLastChatMessages = function getLastChatMessages(count, callback
return;
}
- var _this = this;
+ var that = this;
//works only if we decrement the amount, for some reason
count--;
@@ -279,7 +273,7 @@ Pad.prototype.getLastChatMessages = function getLastChatMessages(count, callback
var entries = [];
async.forEach(neededEntries, function(entryObject, callback)
{
- _this.getChatMessage(entryObject.entryNum, function(err, entry)
+ that.getChatMessage(entryObject.entryNum, function(err, entry)
{
if(ERR(err, callback)) return;
entries[entryObject.order] = entry;
@@ -298,7 +292,7 @@ Pad.prototype.getLastChatMessages = function getLastChatMessages(count, callback
if(entries[i]!=null)
cleanedEntries.push(entries[i]);
else
- console.warn("WARNING: Found broken chat entry in pad " + _this.id);
+ console.warn("WARNING: Found broken chat entry in pad " + that.id);
}
callback(null, cleanedEntries);
@@ -306,50 +300,45 @@ Pad.prototype.getLastChatMessages = function getLastChatMessages(count, callback
};
Pad.prototype.init = function init(text, callback) {
- var _this = this;
+ var that = this;
- //replace text with default text if text isn't set
- if(text == null)
- {
- text = settings.defaultPadText;
- }
//try to load the pad
- db.get("pad:"+this.id, function(err, value)
+ this.db.get("pad:"+this.id, function(err, value)
{
if(ERR(err, callback)) 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);
+ that.head = value.head;
+ that.atext = value.atext;
+ that.pool = that.pool.fromJsonable(value.pool);
//ensure we have a local chatHead variable
if(value.chatHead != null)
- _this.chatHead = value.chatHead;
+ that.chatHead = value.chatHead;
else
- _this.chatHead = -1;
+ that.chatHead = -1;
//ensure we have a local publicStatus variable
if(value.publicStatus != null)
- _this.publicStatus = value.publicStatus;
+ that.publicStatus = value.publicStatus;
else
- _this.publicStatus = false;
+ that.publicStatus = false;
//ensure we have a local passwordHash variable
if(value.passwordHash != null)
- _this.passwordHash = value.passwordHash;
+ that.passwordHash = value.passwordHash;
else
- _this.passwordHash = null;
+ that.passwordHash = null;
}
//this pad doesn't exist, so create it
else
{
var firstChangeset = Changeset.makeSplice("\n", 0, 0, exports.cleanText(text));
- _this.appendRevision(firstChangeset, '');
+ that.appendRevision(firstChangeset, '');
}
callback(null);
@@ -358,10 +347,11 @@ Pad.prototype.init = function init(text, callback) {
Pad.prototype.remove = function remove(callback) {
var padID = this.id;
- var _this = this;
+ var that = this;
//kick everyone from this pad
- padMessageHandler.kickSessionsFromPad(padID);
+ //FIXME do this in the api or somewhere else
+ //padMessageHandler.kickSessionsFromPad(padID);
async.series([
//delete all relations
@@ -376,7 +366,7 @@ Pad.prototype.remove = function remove(callback) {
{
var groupID = padID.substring(0,padID.indexOf("$"));
- db.get("group:" + groupID, function (err, group)
+ that.db.get("group:" + groupID, function (err, group)
{
if(ERR(err, callback)) return;
@@ -384,7 +374,7 @@ Pad.prototype.remove = function remove(callback) {
delete group.pads[padID];
//set the new value
- db.set("group:" + groupID, group);
+ that.db.set("group:" + groupID, group);
callback();
});
@@ -398,12 +388,12 @@ Pad.prototype.remove = function remove(callback) {
//remove the readonly entries
function(callback)
{
- readOnlyManager.getReadOnlyId(padID, function(err, readonlyID)
+ that.readOnlyManager.getReadOnlyId(padID, function(err, readonlyID)
{
if(ERR(err, callback)) return;
- db.remove("pad2readonly:" + padID);
- db.remove("readonly2pad:" + readonlyID);
+ that.db.remove("pad2readonly:" + padID);
+ that.db.remove("readonly2pad:" + readonlyID);
callback();
});
@@ -411,11 +401,11 @@ Pad.prototype.remove = function remove(callback) {
//delete all chat messages
function(callback)
{
- var chatHead = _this.chatHead;
+ var chatHead = that.chatHead;
for(var i=0;i<=chatHead;i++)
{
- db.remove("pad:"+padID+":chat:"+i);
+ that.db.remove("pad:"+padID+":chat:"+i);
}
callback();
@@ -423,11 +413,11 @@ Pad.prototype.remove = function remove(callback) {
//delete all revisions
function(callback)
{
- var revHead = _this.head;
+ var revHead = that.head;
for(var i=0;i<=revHead;i++)
{
- db.remove("pad:"+padID+":revs:"+i);
+ that.db.remove("pad:"+padID+":revs:"+i);
}
callback();
@@ -437,8 +427,8 @@ Pad.prototype.remove = function remove(callback) {
//delete the pad entry and delete pad from padManager
function(callback)
{
- db.remove("pad:"+padID);
- padManager.unloadPad(padID);
+ that.db.remove("pad:"+padID);
+ that.padManager.unloadPad(padID);
callback();
}
], function(err)
@@ -450,12 +440,12 @@ Pad.prototype.remove = function remove(callback) {
//set in db
Pad.prototype.setPublicStatus = function setPublicStatus(publicStatus) {
this.publicStatus = publicStatus;
- db.setSub("pad:"+this.id, ["publicStatus"], this.publicStatus);
+ this.db.setSub("pad:"+this.id, ["publicStatus"], this.publicStatus);
};
Pad.prototype.setPassword = function setPassword(password) {
this.passwordHash = password == null ? null : hash(password, generateSalt());
- db.setSub("pad:"+this.id, ["passwordHash"], this.passwordHash);
+ this.db.setSub("pad:"+this.id, ["passwordHash"], this.passwordHash);
};
Pad.prototype.isCorrectPassword = function isCorrectPassword(password) {
diff --git a/node/db/PadManager.js b/node/db/PadManager.js
index 231aa901f..56d447279 100644
--- a/node/db/PadManager.js
+++ b/node/db/PadManager.js
@@ -21,9 +21,8 @@
var ERR = require("async-stacktrace");
var customError = require("../utils/customError");
var Pad = require("../db/Pad").Pad;
-var db = require("./DB").db;
-/**
+/**
* An Object containing all known Pads. Provides "get" and "set" functions,
* which should be used instead of indexing with brackets. These prepend a
* colon to the key, to avoid conflicting with built-in Object methods or with
@@ -32,12 +31,23 @@ var db = require("./DB").db;
* If this is needed in other places, it would be wise to make this a prototype
* that's defined somewhere more sensible.
*/
-var globalPads = {
- get: function (name) { return this[':'+name]; },
- set: function (name, value) { this[':'+name] = value; },
- remove: function (name) { delete this[':'+name]; }
+
+var PadManager = function PadManager(settings, db, authorManager, readOnlyManager) {
+ this.settings = settings;
+ this.db = db;
+ this.authorManager = authorManager;
+ this.readOnlyManager = readOnlyManager;
+
+ this.globalPads = {
+ get: function (name) { return this[':'+name]; },
+ set: function (name, value) { this[':'+name] = value; },
+ remove: function (name) { delete this[':'+name]; }
+ };
+
};
+exports.PadManager = PadManager;
+
/**
* An array of padId transformations. These represent changes in pad name policy over
* time, and allow us to "play back" these changes so legacy padIds can be found.
@@ -49,24 +59,27 @@ var padIdTransforms = [
/**
* Returns a Pad Object with the callback
* @param id A String with the id of the pad
- * @param {Function} callback
+ * @param {Function} callback
*/
-exports.getPad = function(id, text, callback)
-{
+PadManager.prototype.getPad = function getPad(id, text, callback)
+{
+ var that = this;
+
+ //TODO remove api specific shit
//check if this is a valid padId
- if(!exports.isValidPadId(id))
+ if(!this.isValidPadId(id))
{
callback(new customError(id + " is not a valid padId","apierror"));
return;
}
-
+
//make text an optional parameter
if(typeof text == "function")
{
callback = text;
text = null;
}
-
+
//check if this is a valid text
if(text != null)
{
@@ -76,7 +89,7 @@ exports.getPad = function(id, text, callback)
callback(new customError("text is not a string","apierror"));
return;
}
-
+
//check if text is less than 100k chars
if(text.length > 100000)
{
@@ -84,42 +97,50 @@ exports.getPad = function(id, text, callback)
return;
}
}
-
- var pad = globalPads.get(id);
-
+
+ var pad = this.globalPads.get(id);
+
//return pad if its already loaded
- if(pad != null)
- {
+ if(pad != null) {
callback(null, pad);
}
//try to load pad
- else
- {
+ else {
pad = new Pad(id);
-
+
+ //add some references
+ //TODO is this realy nice?
+ pad.padManager = this;
+ pad.db = this.db;
+ pad.readOnlyManager = this.readOnlyManager;
+ pad.authorManager = this.authorManager;
+
//initalize the pad
- pad.init(text, function(err)
- {
+ if(!text) {
+ text = this.settings.defaultPadText;
+ }
+ pad.init(text, function(err) {
if(ERR(err, callback)) return;
-
- globalPads.set(id, pad);
+
+ that.globalPads.set(id, pad);
callback(null, pad);
});
}
-}
+};
//checks if a pad exists
-exports.doesPadExists = function(padId, callback)
+PadManager.prototype.doesPadExists = function doesPadExists(padId, callback)
{
- db.get("pad:"+padId, function(err, value)
+ this.db.get("pad:"+padId, function(err, value)
{
if(ERR(err, callback)) return;
- callback(null, value != null);
+ callback(null, value != null);
});
-}
+};
//returns a sanitized padId, respecting legacy pad id formats
-exports.sanitizePadId = function(padId, callback) {
+PadManager.prototype.sanitizePadId = function sanitizePadId(padId, callback) {
+ var that = this;
var transform_index = arguments[2] || 0;
//we're out of possible transformations, so just return it
if(transform_index >= padIdTransforms.length)
@@ -129,7 +150,7 @@ exports.sanitizePadId = function(padId, callback) {
//check if padId exists
else
{
- exports.doesPadExists(padId, function(junk, exists)
+ this.doesPadExists(padId, function(junk, exists)
{
if(exists)
{
@@ -145,20 +166,18 @@ exports.sanitizePadId = function(padId, callback) {
transform_index += 1;
}
//check the next transform
- exports.sanitizePadId(transformedPadId, callback, transform_index);
+ that.sanitizePadId(transformedPadId, callback, transform_index);
}
});
}
-}
+};
-exports.isValidPadId = function(padId)
-{
- return /^(g.[a-zA-Z0-9]{16}\$)?[^$]{1,50}$/.test(padId);
-}
+PadManager.prototype.isValidPadId = function(padId) {
+ return (/^(g.[a-zA-Z0-9]{16}\$)?[^$]{1,50}$/).test(padId);
+};
//removes a pad from the array
-exports.unloadPad = function(padId)
-{
- if(globalPads.get(padId))
- globalPads.remove(padId);
-}
+Pad.prototype.unloadPad = function unloadPad(padId) {
+ if(this.globalPads.get(padId))
+ this.globalPads.remove(padId);
+};
diff --git a/node/db/ReadOnlyManager.js b/node/db/ReadOnlyManager.js
index 1e5079c52..df4175ff2 100644
--- a/node/db/ReadOnlyManager.js
+++ b/node/db/ReadOnlyManager.js
@@ -19,24 +19,31 @@
*/
var ERR = require("async-stacktrace");
-var db = require("./DB").db;
var async = require("async");
var randomString = require("../utils/randomstring");
+
+var ReadOnlyManager = function ReadOnlyManager(db) {
+ this.db = db;
+};
+
+exports.ReadOnlyManager = ReadOnlyManager;
+
/**
* returns a read only id for a pad
* @param {String} padId the id of the pad
*/
-exports.getReadOnlyId = function (padId, callback)
-{
+ReadOnlyManager.prototype.getReadOnlyId = function getReadOnlyId(padId, callback) {
+ var that = this;
+
var readOnlyId;
-
+
async.waterfall([
//check if there is a pad2readonly entry
function(callback)
{
- db.get("pad2readonly:" + padId, callback);
+ that.db.get("pad2readonly:" + padId, callback);
},
function(dbReadOnlyId, callback)
{
@@ -44,16 +51,16 @@ exports.getReadOnlyId = function (padId, callback)
if(dbReadOnlyId == null)
{
readOnlyId = "r." + randomString(16);
-
- db.set("pad2readonly:" + padId, readOnlyId);
- db.set("readonly2pad:" + readOnlyId, padId);
+
+ that.db.set("pad2readonly:" + padId, readOnlyId);
+ that.db.set("readonly2pad:" + readOnlyId, padId);
}
//there is a readOnly Entry in the database, let's take this one
else
{
readOnlyId = dbReadOnlyId;
}
-
+
callback();
}
], function(err)
@@ -61,14 +68,13 @@ exports.getReadOnlyId = function (padId, callback)
if(ERR(err, callback)) return;
//return the results
callback(null, readOnlyId);
- })
-}
+ });
+};
/**
* returns a the padId for a read only id
* @param {String} readOnlyId read only id
*/
-exports.getPadId = function(readOnlyId, callback)
-{
- db.get("readonly2pad:" + readOnlyId, callback);
-}
+ReadOnlyManager.prototype.getPadId = function(readOnlyId, callback) {
+ this.db.get("readonly2pad:" + readOnlyId, callback);
+};
diff --git a/node/db/SecurityManager.js b/node/db/SecurityManager.js
index 4b86d868a..2ae58ad00 100644
--- a/node/db/SecurityManager.js
+++ b/node/db/SecurityManager.js
@@ -19,15 +19,20 @@
*/
var ERR = require("async-stacktrace");
-var db = require("./DB").db;
var async = require("async");
-var authorManager = require("./AuthorManager");
-var padManager = require("./PadManager");
-var sessionManager = require("./SessionManager");
-var settings = require("../utils/Settings")
var randomString = require("../utils/randomstring");
+var SecurityManager = function SecurityManager(settings, db, authorManager, padManager, sessionManager) {
+ this.db = db;
+ this.settings = settings;
+ this.authorManager = authorManager;
+ this.padManager = padManager;
+ this.sessionManager = sessionManager;
+};
+
+exports.SecurityManager = SecurityManager;
+
/**
* This function controlls the access to a pad, it checks if the user can access a pad.
* @param padID the pad the user wants to access
@@ -36,12 +41,13 @@ var randomString = require("../utils/randomstring");
* @param password the password the user has given to access this pad, can be null
* @param callback will be called with (err, {accessStatus: grant|deny|wrongPassword|needPassword, authorID: a.xxxxxx})
*/
-exports.checkAccess = function (padID, sessionID, token, password, callback)
+SecurityManager.prototype.checkAccess = function checkAccess(padID, sessionID, token, password, callback)
{
+ var that = this;
var statusObject;
// a valid session is required (api-only mode)
- if(settings.requireSession)
+ if(this.settings.requireSession)
{
// no sessionID, access is denied
if(!sessionID)
@@ -57,17 +63,17 @@ exports.checkAccess = function (padID, sessionID, token, password, callback)
if(padID.indexOf("$") == -1)
{
//get author for this token
- authorManager.getAuthor4Token(token, function(err, author)
+ this.authorManager.getAuthor4Token(token, function(err, author)
{
if(ERR(err, callback)) return;
// assume user has access
statusObject = {accessStatus: "grant", authorID: author};
// user can't create pads
- if(settings.editOnly)
+ if(that.settings.editOnly)
{
// check if pad exists
- padManager.doesPadExists(padID, function(err, exists)
+ that.padManager.doesPadExists(padID, function(err, exists)
{
if(ERR(err, callback)) return;
@@ -107,7 +113,7 @@ exports.checkAccess = function (padID, sessionID, token, password, callback)
//does pad exists
function(callback)
{
- padManager.doesPadExists(padID, function(err, exists)
+ that.padManager.doesPadExists(padID, function(err, exists)
{
if(ERR(err, callback)) return;
padExists = exists;
@@ -117,7 +123,7 @@ exports.checkAccess = function (padID, sessionID, token, password, callback)
//get informations about this session
function(callback)
{
- sessionManager.getSessionInfo(sessionID, function(err, sessionInfo)
+ that.sessionManager.getSessionInfo(sessionID, function(err, sessionInfo)
{
//skip session validation if the session doesn't exists
if(err && err.message == "sessionID does not exist")
@@ -145,7 +151,7 @@ exports.checkAccess = function (padID, sessionID, token, password, callback)
function(callback)
{
//get author for this token
- authorManager.getAuthor4Token(token, function(err, author)
+ that.authorManager.getAuthor4Token(token, function(err, author)
{
if(ERR(err, callback)) return;
tokenAuthor = author;
@@ -164,7 +170,7 @@ exports.checkAccess = function (padID, sessionID, token, password, callback)
return;
}
- padManager.getPad(padID, function(err, pad)
+ that.padManager.getPad(padID, function(err, pad)
{
if(ERR(err, callback)) return;
@@ -223,7 +229,7 @@ exports.checkAccess = function (padID, sessionID, token, password, callback)
//--> grant access
statusObject = {accessStatus: "grant", authorID: sessionAuthor};
//--> deny access if user isn't allowed to create the pad
- if(settings.editOnly) statusObject.accessStatus = "deny";
+ if(that.settings.editOnly) statusObject.accessStatus = "deny";
}
// there is no valid session avaiable AND pad exists
else if(!validSession && padExists)
@@ -277,4 +283,4 @@ exports.checkAccess = function (padID, sessionID, token, password, callback)
if(ERR(err, callback)) return;
callback(null, statusObject);
});
-}
+};
diff --git a/node/db/SessionManager.js b/node/db/SessionManager.js
index 084d4a695..f83c360aa 100644
--- a/node/db/SessionManager.js
+++ b/node/db/SessionManager.js
@@ -17,40 +17,47 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
+
var ERR = require("async-stacktrace");
var customError = require("../utils/customError");
var randomString = require("../utils/randomstring");
-var db = require("./DB").db;
var async = require("async");
-var groupMangager = require("./GroupManager");
-var authorMangager = require("./AuthorManager");
-
-exports.doesSessionExist = function(sessionID, callback)
+
+
+var SessionManager = function SessionManager(db, groupManager, authorManager) {
+ this.db = db;
+ this.groupManager = groupManager;
+ this.authorManager = authorManager;
+};
+
+exports.SessionManager = SessionManager;
+
+SessionManager.prototype.doesSessionExist = function(sessionID, callback)
{
//check if the database entry of this session exists
- db.get("session:" + sessionID, function (err, session)
+ this.db.get("session:" + sessionID, function (err, session)
{
if(ERR(err, callback)) return;
callback(null, session != null);
});
-}
-
+};
+
/**
* Creates a new session between an author and a group
*/
-exports.createSession = function(groupID, authorID, validUntil, callback)
+SessionManager.prototype.createSession = function(groupID, authorID, validUntil, callback)
{
var sessionID;
+ var that = this;
async.series([
//check if group exists
function(callback)
{
- groupMangager.doesGroupExist(groupID, function(err, exists)
+ that.groupMangager.doesGroupExist(groupID, function(err, exists)
{
if(ERR(err, callback)) return;
-
+
//group does not exist
if(exists == false)
{
@@ -66,10 +73,10 @@ exports.createSession = function(groupID, authorID, validUntil, callback)
//check if author exists
function(callback)
{
- authorMangager.doesAuthorExists(authorID, function(err, exists)
+ that.authorMangager.doesAuthorExists(authorID, function(err, exists)
{
if(ERR(err, callback)) return;
-
+
//author does not exist
if(exists == false)
{
@@ -99,56 +106,56 @@ exports.createSession = function(groupID, authorID, validUntil, callback)
return;
}
}
-
+
//ensure this is not a negativ number
if(validUntil < 0)
{
callback(new customError("validUntil is a negativ number","apierror"));
return;
}
-
+
//ensure this is not a float value
if(!is_int(validUntil))
{
callback(new customError("validUntil is a float value","apierror"));
return;
}
-
+
//check if validUntil is in the future
if(Math.floor(new Date().getTime()/1000) > validUntil)
{
callback(new customError("validUntil is in the past","apierror"));
return;
}
-
+
//generate sessionID
sessionID = "s." + randomString(16);
-
+
//set the session into the database
- db.set("session:" + sessionID, {"groupID": groupID, "authorID": authorID, "validUntil": validUntil});
-
+ that.db.set("session:" + sessionID, {"groupID": groupID, "authorID": authorID, "validUntil": validUntil});
+
callback();
},
//set the group2sessions entry
function(callback)
{
//get the entry
- db.get("group2sessions:" + groupID, function(err, group2sessions)
+ that.db.get("group2sessions:" + groupID, function(err, group2sessions)
{
if(ERR(err, callback)) return;
-
+
//the entry doesn't exist so far, let's create it
if(group2sessions == null)
{
group2sessions = {sessionIDs : {}};
}
-
+
//add the entry for this session
group2sessions.sessionIDs[sessionID] = 1;
-
+
//save the new element back
- db.set("group2sessions:" + groupID, group2sessions);
-
+ that.db.set("group2sessions:" + groupID, group2sessions);
+
callback();
});
},
@@ -156,45 +163,45 @@ exports.createSession = function(groupID, authorID, validUntil, callback)
function(callback)
{
//get the entry
- db.get("author2sessions:" + authorID, function(err, author2sessions)
+ that.db.get("author2sessions:" + authorID, function(err, author2sessions)
{
if(ERR(err, callback)) return;
-
+
//the entry doesn't exist so far, let's create it
if(author2sessions == null)
{
author2sessions = {sessionIDs : {}};
}
-
+
//add the entry for this session
author2sessions.sessionIDs[sessionID] = 1;
-
+
//save the new element back
- db.set("author2sessions:" + authorID, author2sessions);
-
+ that.db.set("author2sessions:" + authorID, author2sessions);
+
callback();
});
}
], function(err)
{
if(ERR(err, callback)) return;
-
+
//return error and sessionID
callback(null, {sessionID: sessionID});
})
}
-exports.getSessionInfo = function(sessionID, callback)
+SessionManager.prototype.getSessionInfo = function(sessionID, callback)
{
//check if the database entry of this session exists
- db.get("session:" + sessionID, function (err, session)
+ this.db.get("session:" + sessionID, function (err, session)
{
if(ERR(err, callback)) return;
-
+
//session does not exists
if(session == null)
{
- callback(new customError("sessionID does not exist","apierror"))
+ callback(new customError("sessionID does not exist","apierror"));
}
//everything is fine, return the sessioninfos
else
@@ -202,7 +209,7 @@ exports.getSessionInfo = function(sessionID, callback)
callback(null, session);
}
});
-}
+};
/**
* Deletes a session
@@ -211,15 +218,16 @@ exports.deleteSession = function(sessionID, callback)
{
var authorID, groupID;
var group2sessions, author2sessions;
+ var that = this;
async.series([
function(callback)
{
//get the session entry
- db.get("session:" + sessionID, function (err, session)
+ that.db.get("session:" + sessionID, function (err, session)
{
if(ERR(err, callback)) return;
-
+
//session does not exists
if(session == null)
{
@@ -230,7 +238,7 @@ exports.deleteSession = function(sessionID, callback)
{
authorID = session.authorID;
groupID = session.groupID;
-
+
callback();
}
});
@@ -238,7 +246,7 @@ exports.deleteSession = function(sessionID, callback)
//get the group2sessions entry
function(callback)
{
- db.get("group2sessions:" + groupID, function (err, _group2sessions)
+ that.db.get("group2sessions:" + groupID, function (err, _group2sessions)
{
if(ERR(err, callback)) return;
group2sessions = _group2sessions;
@@ -248,7 +256,7 @@ exports.deleteSession = function(sessionID, callback)
//get the author2sessions entry
function(callback)
{
- db.get("author2sessions:" + authorID, function (err, _author2sessions)
+ that.db.get("author2sessions:" + authorID, function (err, _author2sessions)
{
if(ERR(err, callback)) return;
author2sessions = _author2sessions;
@@ -259,16 +267,16 @@ exports.deleteSession = function(sessionID, callback)
function(callback)
{
//remove the session
- db.remove("session:" + sessionID);
-
+ that.db.remove("session:" + sessionID);
+
//remove session from group2sessions
delete group2sessions.sessionIDs[sessionID];
- db.set("group2sessions:" + groupID, group2sessions);
-
+ that.db.set("group2sessions:" + groupID, group2sessions);
+
//remove session from author2sessions
delete author2sessions.sessionIDs[sessionID];
- db.set("author2sessions:" + authorID, author2sessions);
-
+ that.db.set("author2sessions:" + authorID, author2sessions);
+
callback();
}
], function(err)
@@ -276,14 +284,15 @@ exports.deleteSession = function(sessionID, callback)
if(ERR(err, callback)) return;
callback();
})
-}
+};
-exports.listSessionsOfGroup = function(groupID, callback)
+SessionManager.prototype.listSessionsOfGroup = function(groupID, callback)
{
- groupMangager.doesGroupExist(groupID, function(err, exists)
+ var that = this;
+ this.groupMangager.doesGroupExist(groupID, function(err, exists)
{
if(ERR(err, callback)) return;
-
+
//group does not exist
if(exists == false)
{
@@ -292,17 +301,18 @@ exports.listSessionsOfGroup = function(groupID, callback)
//everything is fine, continue
else
{
- listSessionsWithDBKey("group2sessions:" + groupID, callback);
+ that.listSessionsWithDBKey("group2sessions:" + groupID, callback);
}
});
-}
+};
-exports.listSessionsOfAuthor = function(authorID, callback)
-{
- authorMangager.doesAuthorExists(authorID, function(err, exists)
+SessionManager.prototype.listSessionsOfAuthor = function(authorID, callback)
+{
+ var that = this;
+ this.authorMangager.doesAuthorExists(authorID, function(err, exists)
{
if(ERR(err, callback)) return;
-
+
//group does not exist
if(exists == false)
{
@@ -311,21 +321,21 @@ exports.listSessionsOfAuthor = function(authorID, callback)
//everything is fine, continue
else
{
- listSessionsWithDBKey("author2sessions:" + authorID, callback);
+ that.listSessionsWithDBKey("author2sessions:" + authorID, callback);
}
});
-}
+};
//this function is basicly the code listSessionsOfAuthor and listSessionsOfGroup has in common
-function listSessionsWithDBKey (dbkey, callback)
+SessionManager.prototype.listSessionsWithDBKey = function listSessionsWithDBKey (dbkey, callback)
{
var sessions;
-
+ var that = this;
async.series([
function(callback)
{
//get the group2sessions entry
- db.get(dbkey, function(err, sessionObject)
+ that.db.get(dbkey, function(err, sessionObject)
{
if(ERR(err, callback)) return;
sessions = sessionObject ? sessionObject.sessionIDs : null;
@@ -333,18 +343,18 @@ function listSessionsWithDBKey (dbkey, callback)
});
},
function(callback)
- {
+ {
//collect all sessionIDs in an arrary
var sessionIDs = [];
for (var i in sessions)
{
sessionIDs.push(i);
}
-
+
//foreach trough the sessions and get the sessioninfos
async.forEach(sessionIDs, function(sessionID, callback)
{
- exports.getSessionInfo(sessionID, function(err, sessionInfo)
+ that.getSessionInfo(sessionID, function(err, sessionInfo)
{
if(ERR(err, callback)) return;
sessions[sessionID] = sessionInfo;
@@ -360,7 +370,6 @@ function listSessionsWithDBKey (dbkey, callback)
}
//checks if a number is an int
-function is_int(value)
-{
- return (parseFloat(value) == parseInt(value)) && !isNaN(value)
+function is_int(value) {
+ return (parseFloat(value) == parseInt(value, 10)) && !isNaN(value);
}
diff --git a/node/handler/PadMessageHandler.js b/node/handler/PadMessageHandler.js
index 4a078542c..cd35d2f09 100644
--- a/node/handler/PadMessageHandler.js
+++ b/node/handler/PadMessageHandler.js
@@ -1,6 +1,6 @@
/**
- * The MessageHandler handles all Messages that comes from Socket.IO and controls the sessions
- */
+ * The MessageHandler handles all Messages that comes from Socket.IO and controls the sessions
+ */
/*
* Copyright 2009 Google Inc., 2011 Peter 'Pita' Martischka (Primary Technology Ltd)
@@ -20,95 +20,104 @@
var ERR = require("async-stacktrace");
var async = require("async");
-var padManager = require("../db/PadManager");
var Changeset = require("../utils/Changeset");
var AttributePoolFactory = require("../utils/AttributePoolFactory");
-var authorManager = require("../db/AuthorManager");
-var readOnlyManager = require("../db/ReadOnlyManager");
-var settings = require('../utils/Settings');
-var securityManager = require("../db/SecurityManager");
var log4js = require('log4js');
var messageLogger = log4js.getLogger("message");
-/**
- * A associative array that translates a session to a pad
- */
-var session2pad = {};
-/**
- * A associative array that saves which sessions belong to a pad
- */
-var pad2sessions = {};
-/**
- * A associative array that saves some general informations about a session
- * key = sessionId
- * values = author, rev
- * rev = That last revision that was send to this client
- * author = the author name of this session
- */
-var sessioninfos = {};
+var PadMessageHandler = function(settings, padManager, authorManager, readOnlyManager, securityManager) {
+ this.settings = settings;
+ this.padManager = padManager;
+ this.authorManager = authorManager;
+ this.readOnlyManager = readOnlyManager;
+ this.securityManager = securityManager;
+
+ /**
+ * A associative array that translates a session to a pad
+ */
+ this.session2pad = {};
+ /**
+ * A associative array that saves which sessions belong to a pad
+ */
+ this.pad2sessions = {};
+
+ /**
+ * A associative array that saves some general informations about a session
+ * key = sessionId
+ * values = author, rev
+ * rev = That last revision that was send to this client
+ * author = the author name of this session
+ */
+ this.sessioninfos = {};
+
+ /**
+ * Saves the Socket class we need to send and recieve data from the client
+ */
+ this.socketio = undefined;
+
+};
+
+exports.PadMessageHandler = PadMessageHandler;
-/**
- * Saves the Socket class we need to send and recieve data from the client
- */
-var socketio;
/**
* This Method is called by server.js to tell the message handler on which socket it should send
* @param socket_io The Socket
*/
-exports.setSocketIO = function(socket_io)
+PadMessageHandler.prototype.setSocketIO = function(socket_io)
{
- socketio=socket_io;
-}
+ this.socketio=socket_io;
+};
/**
* Handles the connection of a new user
* @param client the new client
*/
-exports.handleConnect = function(client)
-{
+PadMessageHandler.prototype.handleConnect = function(client)
+{
//Initalize session2pad and sessioninfos for this new session
- session2pad[client.id]=null;
- sessioninfos[client.id]={};
-}
+ this.session2pad[client.id]=null;
+ this.sessioninfos[client.id]={};
+};
/**
* Kicks all sessions from a pad
* @param client the new client
*/
-exports.kickSessionsFromPad = function(padID)
+PadMessageHandler.prototype.kickSessionsFromPad = function(padID)
{
//skip if there is nobody on this pad
- if(!pad2sessions[padID])
+ if(!this.pad2sessions[padID])
return;
//disconnect everyone from this pad
- for(var i in pad2sessions[padID])
+ for(var i in this.pad2sessions[padID])
{
- socketio.sockets.sockets[pad2sessions[padID][i]].json.send({disconnect:"deleted"});
+ this.socketio.sockets.sockets[this.pad2sessions[padID][i]].json.send({disconnect:"deleted"});
}
-}
+};
/**
* Handles the disconnection of a user
* @param client the client that leaves
*/
-exports.handleDisconnect = function(client)
-{
+PadMessageHandler.prototype.handleDisconnect = function(client)
+{
+ var that = this;
//save the padname of this session
- var sessionPad=session2pad[client.id];
-
+ var sessionPad = this.session2pad[client.id];
+
//if this connection was already etablished with a handshake, send a disconnect message to the others
- if(sessioninfos[client.id] && sessioninfos[client.id].author)
+ if(this.sessioninfos[client.id] && this.sessioninfos[client.id].author)
{
- var author = sessioninfos[client.id].author;
-
+ var author = this.sessioninfos[client.id].author;
+
//get the author color out of the db
- authorManager.getAuthorColorId(author, function(err, color)
+ this.authorManager.getAuthorColorId(author, function(err, color)
{
ERR(err);
-
+
//prepare the notification for the other users on the pad, that this user left
var messageToTheOtherUsers = {
"type": "COLLABROOM",
@@ -122,37 +131,37 @@ exports.handleDisconnect = function(client)
}
}
};
-
+
//Go trough all user that are still on the pad, and send them the USER_LEAVE message
- for(i in pad2sessions[sessionPad])
+ for(var i in that.pad2sessions[sessionPad])
{
- socketio.sockets.sockets[pad2sessions[sessionPad][i]].json.send(messageToTheOtherUsers);
+ that.socketio.sockets.sockets[that.pad2sessions[sessionPad][i]].json.send(messageToTheOtherUsers);
}
- });
+ });
}
-
+
//Go trough all sessions of this pad, search and destroy the entry of this client
- for(i in pad2sessions[sessionPad])
+ for(var i in that.pad2sessions[sessionPad])
{
- if(pad2sessions[sessionPad][i] == client.id)
+ if(that.pad2sessions[sessionPad][i] == client.id)
{
- pad2sessions[sessionPad].splice(i, 1);
+ that.pad2sessions[sessionPad].splice(i, 1);
break;
}
}
-
+
//Delete the session2pad and sessioninfos entrys of this session
- delete session2pad[client.id];
- delete sessioninfos[client.id];
-}
+ delete that.session2pad[client.id];
+ delete that.sessioninfos[client.id];
+};
/**
* Handles a message from a user
* @param client the client that send this message
* @param message the message from the client
*/
-exports.handleMessage = function(client, message)
-{
+PadMessageHandler.prototype.handleMessage = function(client, message)
+{
if(message == null)
{
messageLogger.warn("Message is null!");
@@ -163,60 +172,62 @@ exports.handleMessage = function(client, message)
messageLogger.warn("Message has no type attribute!");
return;
}
-
+
//Check what type of message we get and delegate to the other methodes
if(message.type == "CLIENT_READY")
{
- handleClientReady(client, message);
+ this.handleClientReady(client, message);
}
- else if(message.type == "COLLABROOM" &&
+ else if(message.type == "COLLABROOM" &&
message.data.type == "USER_CHANGES")
{
- handleUserChanges(client, message);
+ this.handleUserChanges(client, message);
}
- else if(message.type == "COLLABROOM" &&
+ else if(message.type == "COLLABROOM" &&
message.data.type == "USERINFO_UPDATE")
{
- handleUserInfoUpdate(client, message);
+ this.handleUserInfoUpdate(client, message);
}
- else if(message.type == "COLLABROOM" &&
+ else if(message.type == "COLLABROOM" &&
message.data.type == "CHAT_MESSAGE")
{
- handleChatMessage(client, message);
+ this.handleChatMessage(client, message);
}
- else if(message.type == "COLLABROOM" &&
+ else if(message.type == "COLLABROOM" &&
message.data.type == "CLIENT_MESSAGE" &&
message.data.payload.type == "suggestUserName")
{
- handleSuggestUserName(client, message);
+ this.handleSuggestUserName(client, message);
}
//if the message type is unknown, throw an exception
else
{
messageLogger.warn("Dropped message, unknown Message Type " + message.type);
}
-}
+};
/**
* Handles a Chat Message
* @param client the client that send this message
* @param message the message from the client
*/
-function handleChatMessage(client, message)
+PadMessageHandler.prototype.handleChatMessage = function handleChatMessage(client, message)
{
var time = new Date().getTime();
- var userId = sessioninfos[client.id].author;
+ var userId = this.sessioninfos[client.id].author;
var text = message.data.text;
- var padId = session2pad[client.id];
-
+ var padId = this.session2pad[client.id];
+
var pad;
var userName;
+ var that = this;
+
async.series([
//get the pad
function(callback)
{
- padManager.getPad(padId, function(err, _pad)
+ that.padManager.getPad(padId, function(err, _pad)
{
if(ERR(err, callback)) return;
pad = _pad;
@@ -225,7 +236,7 @@ function handleChatMessage(client, message)
},
function(callback)
{
- authorManager.getAuthorName(userId, function(err, _userName)
+ that.authorManager.getAuthorName(userId, function(err, _userName)
{
if(ERR(err, callback)) return;
userName = _userName;
@@ -237,7 +248,7 @@ function handleChatMessage(client, message)
{
//save the chat message
pad.appendChatMessage(text, userId, time);
-
+
var msg = {
type: "COLLABROOM",
data: {
@@ -248,20 +259,20 @@ function handleChatMessage(client, message)
text: text
}
};
-
+
//broadcast the chat message to everyone on the pad
- for(var i in pad2sessions[padId])
+ for(var i in that.pad2sessions[padId])
{
- socketio.sockets.sockets[pad2sessions[padId][i]].json.send(msg);
+ that.socketio.sockets.sockets[that.pad2sessions[padId][i]].json.send(msg);
}
-
+
callback();
}
], function(err)
{
ERR(err);
});
-}
+};
/**
@@ -269,7 +280,7 @@ function handleChatMessage(client, message)
* @param client the client that send this message
* @param message the message from the client
*/
-function handleSuggestUserName(client, message)
+PadMessageHandler.prototype.handleSuggestUserName = function handleSuggestUserName(client, message)
{
//check if all ok
if(message.data.payload.newName == null)
@@ -282,26 +293,26 @@ function handleSuggestUserName(client, message)
messageLogger.warn("Dropped message, suggestUserName Message has no unnamedId!");
return;
}
-
- var padId = session2pad[client.id];
-
+
+ var padId = this.session2pad[client.id];
+
//search the author and send him this message
- for(var i in pad2sessions[padId])
+ for(var i in this.pad2sessions[padId])
{
- if(sessioninfos[pad2sessions[padId][i]].author == message.data.payload.unnamedId)
+ if(this.sessioninfos[this.pad2sessions[padId][i]].author == message.data.payload.unnamedId)
{
- socketio.sockets.sockets[pad2sessions[padId][i]].send(message);
+ this.socketio.sockets.sockets[this.pad2sessions[padId][i]].send(message);
break;
}
}
-}
+};
/**
* Handles a USERINFO_UPDATE, that means that a user have changed his color or name. Anyway, we get both informations
* @param client the client that send this message
* @param message the message from the client
*/
-function handleUserInfoUpdate(client, message)
+PadMessageHandler.prototype.handleUserInfoUpdate = function handleUserInfoUpdate(client, message)
{
//check if all ok
if(message.data.userInfo.colorId == null)
@@ -309,34 +320,34 @@ function handleUserInfoUpdate(client, message)
messageLogger.warn("Dropped message, USERINFO_UPDATE Message has no colorId!");
return;
}
-
+
//Find out the author name of this session
- var author = sessioninfos[client.id].author;
-
+ var author = this.sessioninfos[client.id].author;
+
//Tell the authorManager about the new attributes
- authorManager.setAuthorColorId(author, message.data.userInfo.colorId);
- authorManager.setAuthorName(author, message.data.userInfo.name);
-
- var padId = session2pad[client.id];
-
+ this.authorManager.setAuthorColorId(author, message.data.userInfo.colorId);
+ this.authorManager.setAuthorName(author, message.data.userInfo.name);
+
+ var padId = this.session2pad[client.id];
+
//set a null name, when there is no name set. cause the client wants it null
if(message.data.userInfo.name == null)
{
message.data.userInfo.name = null;
}
-
+
//The Client don't know about a USERINFO_UPDATE, it can handle only new user_newinfo, so change the message type
message.data.type = "USER_NEWINFO";
-
+
//Send the other clients on the pad the update message
- for(var i in pad2sessions[padId])
+ for(var i in this.pad2sessions[padId])
{
- if(pad2sessions[padId][i] != client.id)
+ if(this.pad2sessions[padId][i] != client.id)
{
- socketio.sockets.sockets[pad2sessions[padId][i]].json.send(message);
+ this.socketio.sockets.sockets[this.pad2sessions[padId][i]].json.send(message);
}
}
-}
+};
/**
* Handles a USERINFO_UPDATE, that means that a user have changed his color or name. Anyway, we get both informations
@@ -345,7 +356,7 @@ function handleUserInfoUpdate(client, message)
* @param client the client that send this message
* @param message the message from the client
*/
-function handleUserChanges(client, message)
+PadMessageHandler.prototype.handleUserChanges = function handleUserChanges(client, message)
{
//check if all ok
if(message.data.baseRev == null)
@@ -363,19 +374,20 @@ function handleUserChanges(client, message)
messageLogger.warn("Dropped message, USER_CHANGES Message has no changeset!");
return;
}
-
+
//get all Vars we need
var baseRev = message.data.baseRev;
var wireApool = (AttributePoolFactory.createAttributePool()).fromJsonable(message.data.apool);
var changeset = message.data.changeset;
-
+
var r, apool, pad;
-
+ var that = this;
+
async.series([
//get the pad
function(callback)
{
- padManager.getPad(session2pad[client.id], function(err, value)
+ that.padManager.getPad(that.session2pad[client.id], function(err, value)
{
if(ERR(err, callback)) return;
pad = value;
@@ -386,13 +398,13 @@ function handleUserChanges(client, message)
function(callback)
{
//ex. _checkChangesetAndPool
-
+
//Copied from Etherpad, don't know what it does exactly
try
{
//this looks like a changeset check, it throws errors sometimes
Changeset.checkRep(changeset);
-
+
Changeset.eachAttribNumber(changeset, function(n) {
if (! wireApool.getAttrib(n)) {
throw "Attribute pool is missing attribute "+n+" for changeset "+changeset;
@@ -406,27 +418,27 @@ function handleUserChanges(client, message)
client.json.send({disconnect:"badChangeset"});
return;
}
-
+
//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(err, callback)) return;
-
+
changeset = Changeset.follow(c, changeset, false, apool);
callback(null);
});
@@ -439,51 +451,52 @@ function handleUserChanges(client, message)
function (callback)
{
var prevText = pad.text();
-
- if (Changeset.oldLen(changeset) != prevText.length)
+
+ if (Changeset.oldLen(changeset) != prevText.length)
{
console.warn("Can't apply USER_CHANGES "+changeset+" with oldLen " + Changeset.oldLen(changeset) + " to document of length " + prevText.length);
client.json.send({disconnect:"badChangeset"});
callback();
return;
}
-
- var thisAuthor = sessioninfos[client.id].author;
-
+
+ var thisAuthor = that.sessioninfos[client.id].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);
}
-
- exports.updatePadClients(pad, callback);
+
+ that.updatePadClients(pad, callback);
}
], function(err)
{
ERR(err);
});
-}
+};
-exports.updatePadClients = function(pad, callback)
-{
+PadMessageHandler.prototype.updatePadClients = function(pad, callback)
+{
+ var that = this;
//skip this step if noone is on this pad
- if(!pad2sessions[pad.id])
+ if(!this.pad2sessions[pad.id])
{
callback();
return;
}
-
+
//go trough all sessions on this pad
- async.forEach(pad2sessions[pad.id], function(session, callback)
+ async.forEach(that.pad2sessions[pad.id], function(session, callback)
{
- var lastRev = sessioninfos[session].rev;
-
+ var lastRev = that.sessioninfos[session].rev;
+
//https://github.com/caolan/async#whilst
//send them all new changesets
async.whilst(
@@ -491,9 +504,9 @@ exports.updatePadClients = function(pad, callback)
function(callback)
{
var author, revChangeset;
-
+
var r = ++lastRev;
-
+
async.parallel([
function (callback)
{
@@ -517,14 +530,14 @@ exports.updatePadClients = function(pad, callback)
{
if(ERR(err, callback)) return;
// next if session has not been deleted
- if(sessioninfos[session] == null)
+ if(that.sessioninfos[session] == null)
{
callback(null);
return;
}
- if(author == sessioninfos[session].author)
+ if(author == that.sessioninfos[session].author)
{
- socketio.sockets.sockets[session].json.send({"type":"COLLABROOM","data":{type:"ACCEPT_COMMIT", newRev:r}});
+ that.socketio.sockets.sockets[session].json.send({"type":"COLLABROOM","data":{type:"ACCEPT_COMMIT", newRev:r}});
}
else
{
@@ -532,23 +545,23 @@ exports.updatePadClients = function(pad, callback)
var wireMsg = {"type":"COLLABROOM","data":{type:"NEW_CHANGES", newRev:r,
changeset: forWire.translated,
apool: forWire.pool,
- author: author}};
-
- socketio.sockets.sockets[session].json.send(wireMsg);
+ author: author}};
+
+ that.socketio.sockets.sockets[session].json.send(wireMsg);
}
-
+
callback(null);
});
},
callback
);
-
- if(sessioninfos[session] != null)
+
+ if(that.sessioninfos[session] != null)
{
- sessioninfos[session].rev = pad.getHeadRevisionNumber();
+ that.sessioninfos[session].rev = pad.getHeadRevisionNumber();
}
- },callback);
-}
+ },callback);
+};
/**
* Copied from the Etherpad Source Code. Don't know what this methode does excatly...
@@ -593,12 +606,12 @@ function _correctMarkersInPad(atext, apool) {
}
/**
- * Handles a CLIENT_READY. A CLIENT_READY is the first message from the client to the server. The Client sends his token
+ * Handles a CLIENT_READY. A CLIENT_READY is the first message from the client to the server. The Client sends his token
* and the pad it wants to enter. The Server answers with the inital values (clientVars) of the pad
* @param client the client that send this message
* @param message the message from the client
*/
-function handleClientReady(client, message)
+PadMessageHandler.prototype.handleClientReady = function handleClientReady(client, message)
{
//check if all ok
if(!message.token)
@@ -629,15 +642,16 @@ function handleClientReady(client, message)
var historicalAuthorData = {};
var readOnlyId;
var chatMessages;
+ var that = this;
async.series([
//check permissions
function(callback)
{
- securityManager.checkAccess (message.padId, message.sessionID, message.token, message.password, function(err, statusObject)
+ that.securityManager.checkAccess (message.padId, message.sessionID, message.token, message.password, function(err, statusObject)
{
if(ERR(err, callback)) return;
-
+
//access was granted
if(statusObject.accessStatus == "grant")
{
@@ -650,7 +664,7 @@ function handleClientReady(client, message)
client.json.send({accessStatus: statusObject.accessStatus})
}
});
- },
+ },
//get all authordata of this new user
function(callback)
{
@@ -658,7 +672,7 @@ function handleClientReady(client, message)
//get colorId
function(callback)
{
- authorManager.getAuthorColorId(author, function(err, value)
+ that.authorManager.getAuthorColorId(author, function(err, value)
{
if(ERR(err, callback)) return;
authorColorId = value;
@@ -668,7 +682,7 @@ function handleClientReady(client, message)
//get author name
function(callback)
{
- authorManager.getAuthorName(author, function(err, value)
+ that.authorManager.getAuthorName(author, function(err, value)
{
if(ERR(err, callback)) return;
authorName = value;
@@ -677,7 +691,7 @@ function handleClientReady(client, message)
},
function(callback)
{
- padManager.getPad(message.padId, function(err, value)
+ that.padManager.getPad(message.padId, function(err, value)
{
if(ERR(err, callback)) return;
pad = value;
@@ -686,7 +700,7 @@ function handleClientReady(client, message)
},
function(callback)
{
- readOnlyManager.getReadOnlyId(message.padId, function(err, value)
+ that.readOnlyManager.getReadOnlyId(message.padId, function(err, value)
{
if(ERR(err, callback)) return;
readOnlyId = value;
@@ -699,14 +713,14 @@ function handleClientReady(client, message)
function(callback)
{
var authors = pad.getAllAuthors();
-
+
async.parallel([
//get all author data out of the database
function(callback)
{
async.forEach(authors, function(authorId, callback)
{
- authorManager.getAuthor(authorId, function(err, author)
+ that.authorManager.getAuthor(authorId, function(err, author)
{
if(ERR(err, callback)) return;
delete author.timestamp;
@@ -726,42 +740,42 @@ function handleClientReady(client, message)
});
}
], callback);
-
-
+
+
},
function(callback)
{
//Check if this author is already on the pad, if yes, kick the other sessions!
- if(pad2sessions[message.padId])
+ if(that.pad2sessions[message.padId])
{
- for(var i in pad2sessions[message.padId])
+ for(var i in that.pad2sessions[message.padId])
{
- if(sessioninfos[pad2sessions[message.padId][i]].author == author)
+ if(that.sessioninfos[that.pad2sessions[message.padId][i]].author == author)
{
- socketio.sockets.sockets[pad2sessions[message.padId][i]].json.send({disconnect:"userdup"});
+ that.socketio.sockets.sockets[that.pad2sessions[message.padId][i]].json.send({disconnect:"userdup"});
}
}
}
-
+
//Save in session2pad that this session belonges to this pad
var sessionId=String(client.id);
- session2pad[sessionId] = message.padId;
-
+ that.session2pad[sessionId] = message.padId;
+
//check if there is already a pad2sessions entry, if not, create one
- if(!pad2sessions[message.padId])
+ if(!that.pad2sessions[message.padId])
{
- pad2sessions[message.padId] = [];
+ that.pad2sessions[message.padId] = [];
}
-
+
//Saves in pad2sessions that this session belongs to this pad
- pad2sessions[message.padId].push(sessionId);
-
+ that.pad2sessions[message.padId].push(sessionId);
+
//prepare all values for the wire
var atext = Changeset.cloneAText(pad.atext);
var attribsForWire = Changeset.prepareForWire(atext.attribs, pad.pool);
var apool = attribsForWire.pool.toJsonable();
atext.attribs = attribsForWire.translated;
-
+
var clientVars = {
"accountPrivs": {
"maxRevisions": 100
@@ -788,7 +802,7 @@ function handleClientReady(client, message)
"initialTitle": "Pad: " + message.padId,
"opts": {},
"chatHistory": chatMessages,
- "numConnectedUsers": pad2sessions[message.padId].length,
+ "numConnectedUsers": that.pad2sessions[message.padId].length,
"isProPad": false,
"readOnlyId": readOnlyId,
"serverTimestamp": new Date().getTime(),
@@ -798,23 +812,23 @@ function handleClientReady(client, message)
"fullWidth": false,
"hideSidebar": false
},
- "abiwordAvailable": settings.abiwordAvailable(),
+ "abiwordAvailable": that.settings.abiwordAvailable(),
"hooks": {}
}
-
+
//Add a username to the clientVars if one avaiable
if(authorName != null)
{
clientVars.userName = authorName;
}
-
- if(sessioninfos[client.id] !== undefined)
+
+ if(that.sessioninfos[client.id] !== undefined)
{
//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;
+ that.sessioninfos[client.id].rev = message.client_rev;
}
//This is a normal first connect
else
@@ -822,13 +836,13 @@ function handleClientReady(client, message)
//Send the clientVars to the Client
client.json.send(clientVars);
//Save the revision in sessioninfos
- sessioninfos[client.id].rev = pad.getHeadRevisionNumber();
+ that.sessioninfos[client.id].rev = pad.getHeadRevisionNumber();
}
-
+
//Save the revision and the author id in sessioninfos
- sessioninfos[client.id].author = author;
+ that.sessioninfos[client.id].author = author;
}
-
+
//prepare the notification for the other users on the pad, that this user joined
var messageToTheOtherUsers = {
"type": "COLLABROOM",
@@ -842,18 +856,18 @@ function handleClientReady(client, message)
}
}
};
-
+
//Add the authorname of this new User, if avaiable
if(authorName != null)
{
messageToTheOtherUsers.data.userInfo.name = authorName;
}
-
+
//Run trough all sessions of this pad
- async.forEach(pad2sessions[message.padId], function(sessionID, callback)
+ async.forEach(that.pad2sessions[message.padId], function(sessionID, callback)
{
var sessionAuthorName, sessionAuthorColorId;
-
+
async.series([
//get the authorname & colorId
function(callback)
@@ -861,32 +875,32 @@ function handleClientReady(client, message)
async.parallel([
function(callback)
{
- authorManager.getAuthorColorId(sessioninfos[sessionID].author, function(err, value)
+ that.authorManager.getAuthorColorId(that.sessioninfos[sessionID].author, function(err, value)
{
if(ERR(err, callback)) return;
sessionAuthorColorId = value;
callback();
- })
+ });
},
function(callback)
{
- authorManager.getAuthorName(sessioninfos[sessionID].author, function(err, value)
+ that.authorManager.getAuthorName(that.sessioninfos[sessionID].author, function(err, value)
{
if(ERR(err, callback)) return;
sessionAuthorName = value;
callback();
- })
+ });
}
],callback);
- },
+ },
function (callback)
{
//Jump over, if this session is the connection session
if(sessionID != client.id)
{
//Send this Session the Notification about the new user
- socketio.sockets.sockets[sessionID].json.send(messageToTheOtherUsers);
-
+ that.socketio.sockets.sockets[sessionID].json.send(messageToTheOtherUsers);
+
//Send the new User a Notification about this other user
var messageToNotifyTheClientAboutTheOthers = {
"type": "COLLABROOM",
@@ -897,18 +911,18 @@ function handleClientReady(client, message)
"colorId": sessionAuthorColorId,
"name": sessionAuthorName,
"userAgent": "Anonymous",
- "userId": sessioninfos[sessionID].author
+ "userId": that.sessioninfos[sessionID].author
}
}
};
client.json.send(messageToNotifyTheClientAboutTheOthers);
}
}
- ], callback);
+ ], callback);
}, callback);
}
],function(err)
{
ERR(err);
});
-}
+};
diff --git a/node/handler/SocketIORouter.js b/node/handler/SocketIORouter.js
index f3b82b8c7..773c8577b 100644
--- a/node/handler/SocketIORouter.js
+++ b/node/handler/SocketIORouter.js
@@ -1,5 +1,5 @@
/**
- * This is the Socket.IO Router. It routes the Messages between the
+ * This is the Socket.IO Router. It routes the Messages between the
* components of the Server. The components are at the moment: pad and timeslider
*/
@@ -24,71 +24,78 @@ var log4js = require('log4js');
var messageLogger = log4js.getLogger("message");
var securityManager = require("../db/SecurityManager");
-/**
- * Saves all components
- * key is the component name
- * value is the component module
- */
-var components = {};
+var SocketIORouter = function SocketIORouter(securityManager) {
+ this.securityManager = securityManager;
+ /**
+ * Saves all components
+ * key is the component name
+ * value is the component module
+ */
+ this.components = {};
-var socket;
-
-/**
- * adds a component
- */
-exports.addComponent = function(moduleName, module)
+ this.socket = undefined;
+
+ /**
+ * adds a component
+ */
+};
+
+exports.SocketIORouter = SocketIORouter;
+
+SocketIORouter.prototype.addComponent = function(moduleName, module)
{
//save the component
- components[moduleName] = module;
-
+ this.components[moduleName] = module;
+
//give the module the socket
- module.setSocketIO(socket);
-}
+ module.setSocketIO(this.socket);
+};
/**
* sets the socket.io and adds event functions for routing
*/
-exports.setSocketIO = function(_socket)
+SocketIORouter.prototype.setSocketIO = function(_socket)
{
+ var that = this;
//save this socket internaly
- socket = _socket;
-
- socket.sockets.on('connection', function(client)
+ this.socket = _socket;
+
+ this.socket.sockets.on('connection', function(client)
{
var clientAuthorized = false;
-
+
//wrap the original send function to log the messages
client._send = client.send;
client.send = function(message)
{
messageLogger.info("to " + client.id + ": " + stringifyWithoutPassword(message));
client._send(message);
- }
-
+ };
+
//tell all components about this connect
- for(var i in components)
+ for(var i in that.components)
{
- components[i].handleConnect(client);
+ that.components[i].handleConnect(client);
}
-
+
//try to handle the message of this client
function handleMessage(message)
{
- if(message.component && components[message.component])
+ if(message.component && that.components[message.component])
{
- //check if component is registered in the components array
- if(components[message.component])
+ //check if component is registered in the components array
+ if(that.components[message.component])
{
messageLogger.info("from " + client.id + ": " + stringifyWithoutPassword(message));
- components[message.component].handleMessage(client, message);
+ that.components[message.component].handleMessage(client, message);
}
}
else
{
messageLogger.error("Can't route the message:" + stringifyWithoutPassword(message));
}
- }
-
+ }
+
client.on('message', function(message)
{
if(message.protocolVersion && message.protocolVersion != 2)
@@ -108,10 +115,10 @@ exports.setSocketIO = function(_socket)
//this message has everything to try an authorization
if(message.padId !== undefined && message.sessionID !== undefined && message.token !== undefined && message.password !== undefined)
{
- securityManager.checkAccess (message.padId, message.sessionID, message.token, message.password, function(err, statusObject)
+ that.securityManager.checkAccess (message.padId, message.sessionID, message.token, message.password, function(err, statusObject)
{
ERR(err);
-
+
//access was granted, mark the client as authorized and handle the message
if(statusObject.accessStatus == "grant")
{
@@ -137,27 +144,30 @@ exports.setSocketIO = function(_socket)
client.on('disconnect', function()
{
//tell all components about this disconnect
- for(var i in components)
+ for(var i in that.components)
{
- components[i].handleDisconnect(client);
+ that.components[i].handleDisconnect(client);
}
});
});
-}
+};
+
//returns a stringified representation of a message, removes the password
//this ensures there are no passwords in the log
function stringifyWithoutPassword(message)
{
var newMessage = {};
-
+ //FIXME LOL
for(var i in message)
{
- if(i == "password" && message[i] != null)
+ if(i == "password" && message[i] != null) {
newMessage["password"] = "xxx";
- else
+ }
+ else {
newMessage[i]=message[i];
+ }
}
-
+
return JSON.stringify(newMessage);
}
diff --git a/node/routes/api.js b/node/routes/api.js
new file mode 100644
index 000000000..064abddf9
--- /dev/null
+++ b/node/routes/api.js
@@ -0,0 +1,48 @@
+var formidable = require('formidable');
+var log4js = require('log4js');
+
+module.exports = function(app)
+{
+ var apiLogger = log4js.getLogger("API");
+
+ //This is for making an api call, collecting all post information and passing it to the apiHandler
+ var apiCaller = function(req, res, fields)
+ {
+ res.header("Content-Type", "application/json; charset=utf-8");
+
+ apiLogger.info("REQUEST, " + req.params.func + ", " + JSON.stringify(fields));
+
+ //wrap the send function so we can log the response
+ res._send = res.send;
+ res.send = function(response)
+ {
+ response = JSON.stringify(response);
+ apiLogger.info("RESPONSE, " + req.params.func + ", " + response);
+
+ //is this a jsonp call, if yes, add the function call
+ if(req.query.jsonp)
+ response = req.query.jsonp + "(" + response + ")";
+
+ res._send(response);
+ };
+
+ //call the api handler
+ app.apiHandler.handle(req.params.func, fields, req, res);
+ };
+
+ //This is a api GET call, collect all post informations and pass it to the apiHandler
+ app.get('/api/1/:func', function(req, res)
+ {
+ apiCaller(req, res, req.query);
+ });
+
+ //This is a api POST call, collect all post informations and pass it to the apiHandler
+ app.post('/api/1/:func', function(req, res)
+ {
+ new formidable.IncomingForm().parse(req, function(err, fields, files)
+ {
+ apiCaller(req, res, fields);
+ });
+ });
+
+};
diff --git a/node/routes/debug.js b/node/routes/debug.js
new file mode 100644
index 000000000..ad0184fdc
--- /dev/null
+++ b/node/routes/debug.js
@@ -0,0 +1,25 @@
+var formidable = require('formidable');
+
+module.exports = function(app)
+{
+ //The Etherpad client side sends information about how a disconnect happen
+ app.post('/ep/pad/connection-diagnostic-info', function(req, res)
+ {
+ new formidable.IncomingForm().parse(req, function(err, fields, files)
+ {
+ console.log("DIAGNOSTIC-INFO: " + fields.diagnosticInfo);
+ res.end("OK");
+ });
+ });
+
+ //The Etherpad client side sends information about client side javscript errors
+ app.post('/jserror', function(req, res)
+ {
+ new formidable.IncomingForm().parse(req, function(err, fields, files)
+ {
+ console.error("CLIENT SIDE JAVASCRIPT ERROR: " + fields.errorInfo);
+ res.end("OK");
+ });
+ });
+
+};
diff --git a/node/routes/export.js b/node/routes/export.js
new file mode 100644
index 000000000..9984e83aa
--- /dev/null
+++ b/node/routes/export.js
@@ -0,0 +1,34 @@
+var ERR = require("async-stacktrace");
+
+module.exports = function(app)
+{
+ var hasPadAccess = require('./preconditions').hasPadAccess(app);
+
+ //serve timeslider.html under /p/$padname/timeslider
+ app.get('/p/:pad/:rev?/export/:type', function(req, res, next)
+ {
+ var types = ["pdf", "doc", "txt", "html", "odt", "dokuwiki"];
+ //send a 404 if we don't support this filetype
+ if(types.indexOf(req.params.type) == -1)
+ {
+ next();
+ return;
+ }
+
+ //if abiword is disabled, and this is a format we only support with abiword, output a message
+ if(app.settings.abiword == null &&
+ ["odt", "pdf", "doc"].indexOf(req.params.type) !== -1)
+ {
+ res.send("Abiword is not enabled at this Etherpad Lite instance. Set the path to Abiword in settings.json to enable this feature");
+ return;
+ }
+
+ res.header("Access-Control-Allow-Origin", "*");
+
+ hasPadAccess(req, res, function()
+ {
+ app.exportHandler.doExport(req, res, req.params.pad, req.params.type);
+ });
+ });
+
+};
diff --git a/node/routes/import.js b/node/routes/import.js
new file mode 100644
index 000000000..f0f6e272f
--- /dev/null
+++ b/node/routes/import.js
@@ -0,0 +1,23 @@
+var ERR = require("async-stacktrace");
+
+module.exports = function(app)
+{
+
+ var hasPadAccess = require('./preconditions').hasPadAccess(app);
+
+ //handle import requests
+ app.post('/p/:pad/import', function(req, res, next)
+ {
+ //if abiword is disabled, skip handling this request
+ if(app.settings.abiword == null)
+ {
+ next();
+ return;
+ }
+
+ hasPadAccess(req, res, function()
+ {
+ app.importHandler.doImport(req, res, req.params.pad);
+ });
+ });
+};
diff --git a/node/routes/preconditions.js b/node/routes/preconditions.js
new file mode 100644
index 000000000..219f17c39
--- /dev/null
+++ b/node/routes/preconditions.js
@@ -0,0 +1,57 @@
+var ERR = require('async-stacktrace');
+
+module.exports = function(app)
+{
+ //redirects browser to the pad's sanitized url if needed. otherwise, renders the html
+ app.param('pad', function (req, res, next, padId) {
+ //ensure the padname is valid and the url doesn't end with a /
+ if(!app.padManager.isValidPadId(padId) || /\/$/.test(req.url))
+ {
+ res.send('Such a padname is forbidden', 404);
+ }
+ else
+ {
+ app.padManager.sanitizePadId(padId, function(sanitizedPadId) {
+ //the pad id was sanitized, so we redirect to the sanitized version
+ if(sanitizedPadId != padId)
+ {
+ var real_path = req.path.replace(/^\/p\/[^\/]+/, '/p/' + sanitizedPadId);
+ res.header('Location', real_path);
+ res.send('You should be redirected to ' + real_path + '', 302);
+ }
+ //the pad id was fine, so just render it
+ else
+ {
+ next();
+ }
+ });
+ }
+ });
+};
+
+module.exports.hasPadAccess = function(app)
+{
+
+ //checks for padAccess
+ var hasPadAccess = function hasPadAccess(req, res, callback)
+ {
+ app.securityManager.checkAccess(req.params.pad, req.cookies.sessionid, req.cookies.token, req.cookies.password, function(err, accessObj)
+ {
+ if(ERR(err, callback)) return;
+
+ //there is access, continue
+ if(accessObj.accessStatus == "grant")
+ {
+ callback();
+ }
+ //no access
+ else
+ {
+ res.send("403 - Can't touch this", 403);
+ }
+ });
+ };
+
+ return hasPadAccess;
+
+};
diff --git a/node/routes/readonly.js b/node/routes/readonly.js
new file mode 100644
index 000000000..3970444f8
--- /dev/null
+++ b/node/routes/readonly.js
@@ -0,0 +1,65 @@
+var async = require('async');
+var ERR = require('async-stacktrace');
+
+module.exports = function(app)
+{
+
+ var hasPadAccess = require('./preconditions').hasPadAccess(app);
+
+ //serve read only pad
+ app.get('/ro/:id', function(req, res)
+ {
+ var html;
+ var padId;
+ var pad;
+
+ async.series([
+ //translate the read only pad to a padId
+ function(callback)
+ {
+ app.readOnlyManager.getPadId(req.params.id, function(err, _padId)
+ {
+ if(ERR(err, callback)) return;
+
+ padId = _padId;
+
+ //we need that to tell hasPadAcess about the pad
+ req.params.pad = padId;
+
+ callback();
+ });
+ },
+ //render the html document
+ function(callback)
+ {
+ //return if the there is no padId
+ if(padId == null)
+ {
+ callback("notfound");
+ return;
+ }
+
+ hasPadAccess(req, res, function()
+ {
+ //render the html document
+ app.exporthtml.getPadHTMLDocument(padId, null, false, function(err, _html)
+ {
+ if(ERR(err, callback)) return;
+ html = _html;
+ callback();
+ });
+ });
+ }
+ ], function(err)
+ {
+ //throw any unexpected error
+ if(err && err != "notfound")
+ ERR(err);
+
+ if(err == "notfound")
+ res.send('404 - Not Found', 404);
+ else
+ res.send(html);
+ });
+ });
+};
diff --git a/node/routes/static.js b/node/routes/static.js
new file mode 100644
index 000000000..4dbeefebe
--- /dev/null
+++ b/node/routes/static.js
@@ -0,0 +1,55 @@
+var path = require('path');
+
+module.exports = function(app)
+{
+
+ app.get('/static/*', function(req, res)
+ {
+ var filePath = path.normalize(__dirname + "/../.." +
+ req.url.replace(/\.\./g, '').split("?")[0]);
+ res.sendfile(filePath, { maxAge: app.maxAge });
+ });
+
+ //serve pad.html under /p
+ app.get('/p/:pad', function(req, res, next)
+ {
+ var filePath = path.normalize(__dirname + "/../../static/pad.html");
+ res.sendfile(filePath, { maxAge: app.maxAge });
+ });
+
+ //serve timeslider.html under /p/$padname/timeslider
+ app.get('/p/:pad/timeslider', function(req, res, next)
+ {
+ var filePath = path.normalize(__dirname + "/../../static/timeslider.html");
+ res.sendfile(filePath, { maxAge: app.maxAge });
+ });
+
+ //serve index.html under /
+ app.get('/', function(req, res)
+ {
+ var filePath = path.normalize(__dirname + "/../../static/index.html");
+ res.sendfile(filePath, { maxAge: app.maxAge });
+ });
+
+ //serve robots.txt
+ app.get('/robots.txt', function(req, res)
+ {
+ var filePath = path.normalize(__dirname + "/../../static/robots.txt");
+ res.sendfile(filePath, { maxAge: app.maxAge });
+ });
+
+ //serve favicon.ico
+ app.get('/favicon.ico', function(req, res)
+ {
+ var filePath = path.normalize(__dirname + "/../../static/custom/favicon.ico");
+ res.sendfile(filePath, { maxAge: app.maxAge }, function(err)
+ {
+ //there is no custom favicon, send the default favicon
+ if(err)
+ {
+ filePath = path.normalize(__dirname + "/../../static/favicon.ico");
+ res.sendfile(filePath, { maxAge: app.maxAge });
+ }
+ });
+ });
+};
diff --git a/node/server.js b/node/server.js
index a6a57497a..568ef4b5a 100644
--- a/node/server.js
+++ b/node/server.js
@@ -1,6 +1,6 @@
/**
- * This module is started with bin/run.sh. It sets up a Express HTTP and a Socket.IO Server.
- * Static file Requests are answered directly from this module, Socket.IO messages are passed
+ * This module is started with bin/run.sh. It sets up a Express HTTP and a Socket.IO Server.
+ * Static file Requests are answered directly from this module, Socket.IO messages are passed
* to MessageHandler and minfied requests are passed to minified.
*/
@@ -20,162 +20,131 @@
* limitations under the License.
*/
-var ERR = require("async-stacktrace");
var log4js = require('log4js');
var os = require("os");
var socketio = require('socket.io');
var fs = require('fs');
-var settings = require('./utils/Settings');
+var parseSettings = require('./utils/Settings').parseSettings;
var db = require('./db/DB');
var async = require('async');
var express = require('express');
var path = require('path');
var minify = require('./utils/Minify');
-var formidable = require('formidable');
-var apiHandler;
-var exportHandler;
-var importHandler;
-var exporthtml;
-var readOnlyManager;
-var padManager;
-var securityManager;
-var socketIORouter;
//try to get the git version
var version = "";
try
{
- var rootPath = path.normalize(__dirname + "/../")
+ var rootPath = path.normalize(__dirname + "/../");
var ref = fs.readFileSync(rootPath + ".git/HEAD", "utf-8");
var refPath = rootPath + ".git/" + ref.substring(5, ref.indexOf("\n"));
version = fs.readFileSync(refPath, "utf-8");
version = version.substring(0, 7);
console.log("Your Etherpad Lite git version is " + version);
}
-catch(e)
+catch(e)
{
- console.warn("Can't get git version for server header\n" + e.message)
+ console.warn("Can't get git version for server header\n" + e.message);
}
-console.log("Report bugs at https://github.com/Pita/etherpad-lite/issues")
+console.log("Report bugs at https://github.com/Pita/etherpad-lite/issues");
var serverName = "Etherpad-Lite " + version + " (http://j.mp/ep-lite)";
+var settings = parseSettings(__dirname + '/../settings.json');
+
//cache 6 hours
exports.maxAge = 1000*60*60*6;
//set loglevel
-log4js.setGlobalLogLevel(settings.loglevel);
+log4js.setGlobalLogLevel(settings.logLevel);
async.waterfall([
//initalize the database
function (callback)
{
- db.init(callback);
+ db.init(settings, callback);
},
+ //TOD rename dbInstance
//initalize the http server
- function (callback)
- {
+ function (dbInstance, callback) {
//create server
var app = express.createServer();
+ app.maxAge = exports.maxAge;
+ app.settings = settings;
+
app.use(function (req, res, next) {
res.header("Server", serverName);
next();
});
-
- //redirects browser to the pad's sanitized url if needed. otherwise, renders the html
- app.param('pad', function (req, res, next, padId) {
- //ensure the padname is valid and the url doesn't end with a /
- if(!padManager.isValidPadId(padId) || /\/$/.test(req.url))
- {
- res.send('Such a padname is forbidden', 404);
- }
- else
- {
- padManager.sanitizePadId(padId, function(sanitizedPadId) {
- //the pad id was sanitized, so we redirect to the sanitized version
- if(sanitizedPadId != padId)
- {
- var real_path = req.path.replace(/^\/p\/[^\/]+/, '/p/' + sanitizedPadId);
- res.header('Location', real_path);
- res.send('You should be redirected to ' + real_path + '', 302);
- }
- //the pad id was fine, so just render it
- else
- {
- next();
- }
- });
- }
- });
+ //preconditions i.e. sanitize urls
+ require('./routes/preconditions')(app);
+
+ var PadManager = require('./db/PadManager').PadManager;
+
+ var ReadOnlyManager = require('./db/ReadOnlyManager').ReadOnlyManager;
+
+ var SecurityManager = require('./db/SecurityManager').SecurityManager;
+
+ var AuthorManager = require('./db/AuthorManager').AuthorManager;
+
+ var GroupManager = require('./db/GroupManager').GroupManager;
+
+ var SessionManager = require('./db/SessionManager').SessionManager;
//load modules that needs a initalized db
- readOnlyManager = require("./db/ReadOnlyManager");
- exporthtml = require("./utils/ExportHtml");
- exportHandler = require('./handler/ExportHandler');
- importHandler = require('./handler/ImportHandler');
- apiHandler = require('./handler/APIHandler');
- padManager = require('./db/PadManager');
- securityManager = require('./db/SecurityManager');
- socketIORouter = require("./handler/SocketIORouter");
-
- //install logging
+ app.readOnlyManager = new ReadOnlyManager(dbInstance);
+
+ app.authorManager = new AuthorManager(dbInstance);
+
+ app.padManager = new PadManager(settings, dbInstance, app.authorManager, app.readOnlyManager);
+
+ app.groupManager = new GroupManager(dbInstance, app.padManager, null);
+
+ app.sessionManager = new SessionManager(dbInstance, app.groupManager, app.authorManager);
+
+ app.securityManager = new SecurityManager(settings, dbInstance, app.authorManager, app.padManager, app.sessionManager);
+
+ app.exporthtml = require("./utils/ExportHtml");
+ app.exportHandler = require('./handler/ExportHandler');
+ app.importHandler = require('./handler/ImportHandler');
+
+ //app.apiHandler = require('./handler/APIHandler');
+
+ var SocketIORouter = require("./handler/SocketIORouter").SocketIORouter;
+
+ //install logging
var httpLogger = log4js.getLogger("http");
- app.configure(function()
+ app.configure(function()
{
// Activate http basic auth if it has been defined in settings.json
if(settings.httpAuth != null) app.use(basic_auth);
// If the log level specified in the config file is WARN or ERROR the application server never starts listening to requests as reported in issue #158.
// Not installing the log4js connect logger when the log level has a higher severity than INFO since it would not log at that level anyway.
- if (!(settings.loglevel === "WARN" || settings.loglevel == "ERROR"))
+ if (!(settings.loglevel === "WARN" || settings.loglevel == "ERROR")) {
app.use(log4js.connectLogger(httpLogger, { level: log4js.levels.INFO, format: ':status, :method :url'}));
+ }
app.use(express.cookieParser());
});
-
+
app.error(function(err, req, res, next){
res.send(500);
console.error(err.stack ? err.stack : err.toString());
gracefulShutdown();
});
-
+
//serve static files
app.get('/static/js/require-kernel.js', function (req, res, next) {
res.header("Content-Type","application/javascript; charset: utf-8");
res.write(minify.requireDefinition());
res.end();
});
- app.get('/static/*', function(req, res)
- {
- var filePath = path.normalize(__dirname + "/.." +
- req.url.replace(/\.\./g, '').split("?")[0]);
- res.sendfile(filePath, { maxAge: exports.maxAge });
- });
-
+
//serve minified files
app.get('/minified/:filename', minify.minifyJS);
-
- //checks for padAccess
- function hasPadAccess(req, res, callback)
- {
- securityManager.checkAccess(req.params.pad, req.cookies.sessionid, req.cookies.token, req.cookies.password, function(err, accessObj)
- {
- if(ERR(err, callback)) return;
-
- //there is access, continue
- if(accessObj.accessStatus == "grant")
- {
- callback();
- }
- //no access
- else
- {
- res.send("403 - Can't touch this", 403);
- }
- });
- }
//checks for basic http auth
function basic_auth (req, res, next) {
@@ -186,7 +155,7 @@ async.waterfall([
return;
}
}
-
+
res.header('WWW-Authenticate', 'Basic realm="Protected Area"');
if (req.headers.authorization) {
setTimeout(function () {
@@ -196,212 +165,19 @@ async.waterfall([
res.send('Authentication required', 401);
}
}
-
- //serve read only pad
- app.get('/ro/:id', function(req, res)
- {
- var html;
- var padId;
- var pad;
-
- async.series([
- //translate the read only pad to a padId
- function(callback)
- {
- readOnlyManager.getPadId(req.params.id, function(err, _padId)
- {
- if(ERR(err, callback)) return;
-
- padId = _padId;
-
- //we need that to tell hasPadAcess about the pad
- req.params.pad = padId;
-
- callback();
- });
- },
- //render the html document
- function(callback)
- {
- //return if the there is no padId
- if(padId == null)
- {
- callback("notfound");
- return;
- }
-
- hasPadAccess(req, res, function()
- {
- //render the html document
- exporthtml.getPadHTMLDocument(padId, null, false, function(err, _html)
- {
- if(ERR(err, callback)) return;
- html = _html;
- callback();
- });
- });
- }
- ], function(err)
- {
- //throw any unexpected error
- if(err && err != "notfound")
- ERR(err);
-
- if(err == "notfound")
- res.send('404 - Not Found', 404);
- else
- res.send(html);
- });
- });
-
- //serve pad.html under /p
- app.get('/p/:pad', function(req, res, next)
- {
- var filePath = path.normalize(__dirname + "/../static/pad.html");
- res.sendfile(filePath, { maxAge: exports.maxAge });
- });
-
- //serve timeslider.html under /p/$padname/timeslider
- app.get('/p/:pad/timeslider', function(req, res, next)
- {
- var filePath = path.normalize(__dirname + "/../static/timeslider.html");
- res.sendfile(filePath, { maxAge: exports.maxAge });
- });
-
- //serve timeslider.html under /p/$padname/timeslider
- app.get('/p/:pad/:rev?/export/:type', function(req, res, next)
- {
- var types = ["pdf", "doc", "txt", "html", "odt", "dokuwiki"];
- //send a 404 if we don't support this filetype
- if(types.indexOf(req.params.type) == -1)
- {
- next();
- return;
- }
-
- //if abiword is disabled, and this is a format we only support with abiword, output a message
- if(settings.abiword == null &&
- ["odt", "pdf", "doc"].indexOf(req.params.type) !== -1)
- {
- res.send("Abiword is not enabled at this Etherpad Lite instance. Set the path to Abiword in settings.json to enable this feature");
- return;
- }
-
- res.header("Access-Control-Allow-Origin", "*");
-
- hasPadAccess(req, res, function()
- {
- exportHandler.doExport(req, res, req.params.pad, req.params.type);
- });
- });
-
- //handle import requests
- app.post('/p/:pad/import', function(req, res, next)
- {
- //if abiword is disabled, skip handling this request
- if(settings.abiword == null)
- {
- next();
- return;
- }
-
- hasPadAccess(req, res, function()
- {
- importHandler.doImport(req, res, req.params.pad);
- });
- });
-
- var apiLogger = log4js.getLogger("API");
- //This is for making an api call, collecting all post information and passing it to the apiHandler
- var apiCaller = function(req, res, fields)
- {
- res.header("Content-Type", "application/json; charset=utf-8");
-
- apiLogger.info("REQUEST, " + req.params.func + ", " + JSON.stringify(fields));
-
- //wrap the send function so we can log the response
- res._send = res.send;
- res.send = function(response)
- {
- response = JSON.stringify(response);
- apiLogger.info("RESPONSE, " + req.params.func + ", " + response);
-
- //is this a jsonp call, if yes, add the function call
- if(req.query.jsonp)
- response = req.query.jsonp + "(" + response + ")";
-
- res._send(response);
- }
-
- //call the api handler
- apiHandler.handle(req.params.func, fields, req, res);
- }
-
- //This is a api GET call, collect all post informations and pass it to the apiHandler
- app.get('/api/1/:func', function(req, res)
- {
- apiCaller(req, res, req.query)
- });
+ require('./routes/readonly')(app);
+
+ require('./routes/import')(app);
+
+ require('./routes/export')(app);
+
+ //require('./routes/api')(app);
+
+ require('./routes/debug')(app);
+
+ require('./routes/static')(app);
- //This is a api POST call, collect all post informations and pass it to the apiHandler
- app.post('/api/1/:func', function(req, res)
- {
- new formidable.IncomingForm().parse(req, function(err, fields, files)
- {
- apiCaller(req, res, fields)
- });
- });
-
- //The Etherpad client side sends information about how a disconnect happen
- app.post('/ep/pad/connection-diagnostic-info', function(req, res)
- {
- new formidable.IncomingForm().parse(req, function(err, fields, files)
- {
- console.log("DIAGNOSTIC-INFO: " + fields.diagnosticInfo);
- res.end("OK");
- });
- });
-
- //The Etherpad client side sends information about client side javscript errors
- app.post('/jserror', function(req, res)
- {
- new formidable.IncomingForm().parse(req, function(err, fields, files)
- {
- console.error("CLIENT SIDE JAVASCRIPT ERROR: " + fields.errorInfo);
- res.end("OK");
- });
- });
-
- //serve index.html under /
- app.get('/', function(req, res)
- {
- var filePath = path.normalize(__dirname + "/../static/index.html");
- res.sendfile(filePath, { maxAge: exports.maxAge });
- });
-
- //serve robots.txt
- app.get('/robots.txt', function(req, res)
- {
- var filePath = path.normalize(__dirname + "/../static/robots.txt");
- res.sendfile(filePath, { maxAge: exports.maxAge });
- });
-
- //serve favicon.ico
- app.get('/favicon.ico', function(req, res)
- {
- var filePath = path.normalize(__dirname + "/../static/custom/favicon.ico");
- res.sendfile(filePath, { maxAge: exports.maxAge }, function(err)
- {
- //there is no custom favicon, send the default favicon
- if(err)
- {
- filePath = path.normalize(__dirname + "/../static/favicon.ico");
- res.sendfile(filePath, { maxAge: exports.maxAge });
- }
- });
- });
-
//let the server listen
app.listen(settings.port, settings.ip);
console.log("Server is listening at " + settings.ip + ":" + settings.port);
@@ -417,24 +193,24 @@ async.waterfall([
{
console.error(err);
}
-
+
//ensure there is only one graceful shutdown running
if(onShutdown) return;
onShutdown = true;
-
+
console.log("graceful shutdown...");
-
+
//stop the http server
app.close();
//do the db shutdown
- db.db.doShutdown(function()
+ dbInstance.doShutdown(function()
{
console.log("db sucessfully closed.");
-
+
process.exit(0);
});
-
+
setTimeout(function(){
process.exit(1);
}, 3000);
@@ -447,22 +223,22 @@ async.waterfall([
//https://github.com/joyent/node/issues/1553
process.on('SIGINT', gracefulShutdown);
}
-
+
process.on('uncaughtException', gracefulShutdown);
//init socket.io and redirect all requests to the MessageHandler
var io = socketio.listen(app);
-
+
//this is only a workaround to ensure it works with all browers behind a proxy
//we should remove this when the new socket.io version is more stable
io.set('transports', ['xhr-polling']);
-
+
var socketIOLogger = log4js.getLogger("socket.io");
io.set('logger', {
debug: function (str)
{
socketIOLogger.debug.apply(socketIOLogger, arguments);
- },
+ },
info: function (str)
{
socketIOLogger.info.apply(socketIOLogger, arguments);
@@ -476,19 +252,25 @@ async.waterfall([
socketIOLogger.error.apply(socketIOLogger, arguments);
},
});
-
+
//minify socket.io javascript
if(settings.minify)
io.enable('browser client minification');
-
- var padMessageHandler = require("./handler/PadMessageHandler");
- var timesliderMessageHandler = require("./handler/TimesliderMessageHandler");
-
+
+ var PadMessageHandler = require("./handler/PadMessageHandler").PadMessageHandler;
+
+ app.padMessageHandler = new PadMessageHandler(app.settings, app.padManager, app.authorManager, app.readOnlyManager, app.securityManager);
+
+ //var timesliderMessageHandler = require("./handler/TimesliderMessageHandler");
+
+
//Initalize the Socket.IO Router
- socketIORouter.setSocketIO(io);
- socketIORouter.addComponent("pad", padMessageHandler);
- socketIORouter.addComponent("timeslider", timesliderMessageHandler);
-
- callback(null);
+ //
+ app.socketIORouter = new SocketIORouter(app.securityManager);
+ app.socketIORouter.setSocketIO(io);
+ app.socketIORouter.addComponent("pad", app.padMessageHandler);
+ //socketIORouter.addComponent("timeslider", timesliderMessageHandler);
+
+ callback(null);
}
]);
diff --git a/node/utils/Settings.js b/node/utils/Settings.js
index e95702112..de6050991 100644
--- a/node/utils/Settings.js
+++ b/node/utils/Settings.js
@@ -1,5 +1,5 @@
/**
- * The Settings Modul reads the settings out of settings.json and provides
+ * The Settings Modul reads the settings out of settings.json and provides
* this information to the other modules
*/
@@ -23,109 +23,103 @@ var fs = require("fs");
var os = require("os");
var path = require('path');
+var defaults = {};
/**
* The IP ep-lite should listen to
*/
-exports.ip = "0.0.0.0";
-
+defaults.ip = "0.0.0.0";
+
/**
* The Port ep-lite should listen to
*/
-exports.port = 9001;
+defaults.port = 9001;
/*
* The Type of the database
*/
-exports.dbType = "dirty";
+defaults.dbType = "dirty";
/**
* This setting is passed with dbType to ueberDB to set up the database
*/
-exports.dbSettings = { "filename" : "../var/dirty.db" };
+defaults.dbSettings = { "filename" : "../var/dirty.db" };
/**
* The default Text of a new pad
*/
-exports.defaultPadText = "Welcome to Etherpad Lite!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nEtherpad Lite on Github: http:\/\/j.mp/ep-lite\n";
+defaults.defaultPadText = "Welcome to Etherpad Lite!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nEtherpad Lite on Github: http:\/\/j.mp/ep-lite\n";
/**
* A flag that requires any user to have a valid session (via the api) before accessing a pad
*/
-exports.requireSession = false;
+defaults.requireSession = false;
/**
* A flag that prevents users from creating new pads
*/
-exports.editOnly = false;
+defaults.editOnly = false;
/**
* A flag that shows if minification is enabled or not
*/
-exports.minify = true;
+defaults.minify = true;
/**
* The path of the abiword executable
*/
-exports.abiword = null;
+defaults.abiword = null;
/**
* The log level of log4js
*/
-exports.loglevel = "INFO";
+defaults.logLevel = "INFO";
/**
* Http basic auth, with "user:password" format
*/
-exports.httpAuth = null;
+defaults.httpAuth = null;
-//checks if abiword is avaiable
-exports.abiwordAvailable = function()
-{
- if(exports.abiword != null)
- {
- return os.type().indexOf("Windows") != -1 ? "withoutPDF" : "yes";
- }
- else
- {
- return "no";
- }
-}
+
+var Settings = function(settings) {
+
+ this.ip = settings.ip || defaults.ip;
+ this.port = settings.port || defaults.port;
+ this.dbType = settings.dbType || defaults.dbType;
+ this.dbSettings = settings.dbSettings || defaults.dbSettings;
+ this.defaultPadText = settings.defaultPadText || defaults.defaultPadText;
+ this.requireSessions = settings.requireSessions || defaults.requireSessions;
+ this.editOnly = settings.editOnly || defaults.editOnly;
+ this.minify = settings.minify || defaults.minify;
+ this.abiword = settings.abiword || defaults.abiword;
+ this.logLevel = settings.logLevel || defaults.logLevel;
+ this.httpAuth = settings.httpAuth || defaults.httpAuth;
+};
+
+//TODO this is shit
+Settings.prototype.abiwordAvailable = function abiwordAvailable() {
+ if(this.abiword != null) {
+ return os.type().indexOf("Windows") != -1 ? "withoutPDF" : "yes";
+ } else {
+ return "no";
+ }
+};
+
+exports.Settings = Settings;
//read the settings sync
-var settingsPath = path.normalize(__dirname + "/../../");
-var settingsStr = fs.readFileSync(settingsPath + "settings.json").toString();
-//remove all comments
-settingsStr = settingsStr.replace(/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/gm,"").replace(/#.*/g,"").replace(/\/\/.*/g,"");
+exports.parseSettings = function parseSettings(path) {
-//try to parse the settings
-var settings;
-try
-{
- settings = JSON.parse(settingsStr);
-}
-catch(e)
-{
- console.error("There is a syntax error in your settings.json file");
- console.error(e.message);
- process.exit(1);
-}
+ var settingsStr = fs.readFileSync(path).toString();
+ settingsStr = settingsStr.replace(/\*([^*]|[\r\n]|(\*+([^*\/]|[\r\n])))*\*+/gm,"").replace(/#.*/g,"").replace(/\/\/.*/g,"");
+ var pojo;
+ //try to parse the settings
+ try {
+ pojo = JSON.parse(settingsStr);
+ }
+ catch(e) {
+ console.log(e);
+ process.exit(1);
+ }
+ return new Settings(pojo);
+
+};
-//loop trough the settings
-for(var i in settings)
-{
- //test if the setting start with a low character
- if(i.charAt(0).search("[a-z]") !== 0)
- {
- console.warn("Settings should start with a low character: '" + i + "'");
- }
- //we know this setting, so we overwrite it
- if(exports[i] !== undefined)
- {
- exports[i] = settings[i];
- }
- //this setting is unkown, output a warning and throw it away
- else
- {
- console.warn("Unkown Setting: '" + i + "'");
- console.warn("This setting doesn't exist or it was removed");
- }
-}