resolve merge conflict

This commit is contained in:
John McLear 2013-12-09 21:55:04 +00:00
commit 58bbfd8a65
210 changed files with 12113 additions and 7505 deletions

View file

@ -74,6 +74,124 @@ exports.listSessionsOfAuthor = sessionManager.listSessionsOfAuthor;
/**PAD CONTENT FUNCTIONS*/
/************************/
/**
getAttributePool(padID) returns the attribute pool of a pad
Example returns:
{
"code":0,
"message":"ok",
"data": {
"pool":{
"numToAttrib":{
"0":["author","a.X4m8bBWJBZJnWGSh"],
"1":["author","a.TotfBPzov54ihMdH"],
"2":["author","a.StiblqrzgeNTbK05"],
"3":["bold","true"]
},
"attribToNum":{
"author,a.X4m8bBWJBZJnWGSh":0,
"author,a.TotfBPzov54ihMdH":1,
"author,a.StiblqrzgeNTbK05":2,
"bold,true":3
},
"nextNum":4
}
}
}
*/
exports.getAttributePool = function (padID, callback)
{
getPadSafe(padID, true, function(err, pad)
{
if (ERR(err, callback)) return;
callback(null, {pool: pad.pool});
});
}
/**
getRevisionChangeset (padID, [rev])
get the changeset at a given revision, or last revision if 'rev' is not defined.
Example returns:
{
"code" : 0,
"message" : "ok",
"data" : "Z:1>6b|5+6b$Welcome to Etherpad!\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\nGet involved with Etherpad at http://etherpad.org\n"
}
*/
exports.getRevisionChangeset = function(padID, rev, callback)
{
// check if rev is set
if (typeof rev === "function")
{
callback = rev;
rev = undefined;
}
// check if rev is a number
if (rev !== undefined && typeof rev !== "number")
{
// try to parse the number
if (!isNaN(parseInt(rev)))
{
rev = parseInt(rev);
}
else
{
callback(new customError("rev is not a number", "apierror"));
return;
}
}
// ensure this is not a negative number
if (rev !== undefined && rev < 0)
{
callback(new customError("rev is not a negative number", "apierror"));
return;
}
// ensure this is not a float value
if (rev !== undefined && !is_int(rev))
{
callback(new customError("rev is a float value", "apierror"));
return;
}
// get the pad
getPadSafe(padID, true, function(err, pad)
{
if(ERR(err, callback)) return;
//the client asked for a special revision
if(rev !== undefined)
{
//check if this is a valid revision
if(rev > pad.getHeadRevisionNumber())
{
callback(new customError("rev is higher than the head revision of the pad","apierror"));
return;
}
//get the changeset for this revision
pad.getRevisionChangeset(rev, function(err, changeset)
{
if(ERR(err, callback)) return;
callback(null, changeset);
})
}
//the client wants the latest changeset, lets return it to him
else
{
callback(null, {"changeset": pad.getRevisionChangeset(pad.getHeadRevisionNumber())});
}
});
}
/**
getText(padID, [rev]) returns the text of a pad
@ -326,8 +444,8 @@ exports.getChatHistory = function(padID, start, end, callback)
// fall back to getting the whole chat-history if a parameter is missing
if(!start || !end)
{
start = 0;
end = pad.chatHead;
start = 0;
end = pad.chatHead;
}
if(start >= chatHead && chatHead > 0)
@ -438,6 +556,46 @@ exports.deletePad = function(padID, callback)
});
}
/**
copyPad(sourceID, destinationID[, force=false]) copies a pad. If force is true,
the destination will be overwritten if it exists.
Example returns:
{code: 0, message:"ok", data: {padID: destinationID}}
{code: 1, message:"padID does not exist", data: null}
*/
exports.copyPad = function(sourceID, destinationID, force, callback)
{
getPadSafe(sourceID, true, function(err, pad)
{
if(ERR(err, callback)) return;
pad.copy(destinationID, force, callback);
});
}
/**
movePad(sourceID, destinationID[, force=false]) moves a pad. If force is true,
the destination will be overwritten if it exists.
Example returns:
{code: 0, message:"ok", data: {padID: destinationID}}
{code: 1, message:"padID does not exist", data: null}
*/
exports.movePad = function(sourceID, destinationID, force, callback)
{
getPadSafe(sourceID, true, function(err, pad)
{
if(ERR(err, callback)) return;
pad.copy(destinationID, force, function(err) {
if(ERR(err, callback)) return;
pad.remove(callback);
});
});
}
/**
getReadOnlyLink(padID) returns the read only link of a pad
@ -664,7 +822,7 @@ createDiffHTML(padID, startRev, endRev) returns an object of diffs from 2 points
Example returns:
{"code":0,"message":"ok","data":{"html":"<style>\n.authora_HKIv23mEbachFYfH {background-color: #a979d9}\n.authora_n4gEeMLsv1GivNeh {background-color: #a9b5d9}\n.removed {text-decoration: line-through; -ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=80)'; filter: alpha(opacity=80); opacity: 0.8; }\n</style>Welcome to Etherpad Lite!<br><br>This 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!<br><br>Get involved with Etherpad at <a href=\"http&#x3a;&#x2F;&#x2F;etherpad&#x2e;org\">http:&#x2F;&#x2F;etherpad.org</a><br><span class=\"authora_HKIv23mEbachFYfH\">aw</span><br><br>","authors":["a.HKIv23mEbachFYfH",""]}}
{"code":0,"message":"ok","data":{"html":"<style>\n.authora_HKIv23mEbachFYfH {background-color: #a979d9}\n.authora_n4gEeMLsv1GivNeh {background-color: #a9b5d9}\n.removed {text-decoration: line-through; -ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=80)'; filter: alpha(opacity=80); opacity: 0.8; }\n</style>Welcome to Etherpad!<br><br>This 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!<br><br>Get involved with Etherpad at <a href=\"http&#x3a;&#x2F;&#x2F;etherpad&#x2e;org\">http:&#x2F;&#x2F;etherpad.org</a><br><span class=\"authora_HKIv23mEbachFYfH\">aw</span><br><br>","authors":["a.HKIv23mEbachFYfH",""]}}
{"code":4,"message":"no or wrong API Key","data":null}
*/
exports.createDiffHTML = function(padID, startRev, endRev, callback){

View file

@ -22,6 +22,7 @@
var ERR = require("async-stacktrace");
var db = require("./DB").db;
var async = require("async");
var customError = require("../utils/customError");
var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
exports.getColorPalette = function(){
@ -272,4 +273,4 @@ exports.removePad = function (authorID, padID)
db.set("globalAuthor:" + authorID, author);
}
});
}
}

View file

@ -215,25 +215,32 @@ exports.createGroupIfNotExistsFor = function(groupMapper, callback)
{
if(ERR(err, callback)) return;
// there is a group for this mapper
if(groupID) {
exports.doesGroupExist(groupID, function(err, exists) {
if(ERR(err, callback)) return;
if(exists) return callback(null, {groupID: groupID});
// hah, the returned group doesn't exist, let's create one
createGroupForMapper(callback)
})
}
//there is no group for this mapper, let's create a group
if(groupID == null)
{
else {
createGroupForMapper(callback)
}
function createGroupForMapper(cb) {
exports.createGroup(function(err, responseObj)
{
if(ERR(err, callback)) return;
if(ERR(err, cb)) return;
//create the mapper entry for this group
db.set("mapper2group:"+groupMapper, responseObj.groupID);
callback(null, responseObj);
cb(null, responseObj);
});
}
//there is a group for this mapper, let's return it
else
{
if(ERR(err, callback)) return;
callback(null, {groupID: groupID});
}
});
}

View file

@ -13,6 +13,8 @@ var settings = require('../utils/Settings');
var authorManager = require("./AuthorManager");
var padManager = require("./PadManager");
var padMessageHandler = require("../handler/PadMessageHandler");
var groupManager = require("./GroupManager");
var customError = require("../utils/customError");
var readOnlyManager = require("./ReadOnlyManager");
var crypto = require("crypto");
var randomString = require("../utils/randomstring");
@ -404,6 +406,152 @@ Pad.prototype.init = function init(text, callback) {
});
};
Pad.prototype.copy = function copy(destinationID, force, callback) {
var sourceID = this.id;
var _this = this;
// make force optional
if (typeof force == "function") {
callback = force;
force = false;
}
else if (force == undefined || force.toLowerCase() != "true") {
force = false;
}
else force = true;
//kick everyone from this pad
// TODO: this presents a message on the client saying that the pad was 'deleted'. Fix this?
padMessageHandler.kickSessionsFromPad(sourceID);
// flush the source pad:
_this.saveToDatabase();
async.series([
// if it's a group pad, let's make sure the group exists.
function(callback)
{
if (destinationID.indexOf("$") != -1)
{
groupManager.doesGroupExist(destinationID.split("$")[0], function (err, exists)
{
if(ERR(err, callback)) return;
//group does not exist
if(exists == false)
{
callback(new customError("groupID does not exist for destinationID","apierror"));
return;
}
//everything is fine, continue
else
{
callback();
}
});
}
else
callback();
},
// if the pad exists, we should abort, unless forced.
function(callback)
{
console.log("destinationID", destinationID, force);
padManager.doesPadExists(destinationID, function (err, exists)
{
if(ERR(err, callback)) return;
if(exists == true)
{
if (!force)
{
console.log("erroring out without force");
callback(new customError("destinationID already exists","apierror"));
console.log("erroring out without force - after");
return;
}
else // exists and forcing
{
padManager.getPad(destinationID, function(err, pad) {
if (ERR(err, callback)) return;
pad.remove(callback);
});
}
}
else
{
callback();
}
});
},
// copy the 'pad' entry
function(callback)
{
db.get("pad:"+sourceID, function(err, pad) {
db.set("pad:"+destinationID, pad);
});
callback();
},
//copy all relations
function(callback)
{
async.parallel([
//copy all chat messages
function(callback)
{
var chatHead = _this.chatHead;
for(var i=0;i<=chatHead;i++)
{
db.get("pad:"+sourceID+":chat:"+i, function (err, chat) {
if (ERR(err, callback)) return;
db.set("pad:"+destinationID+":chat:"+i, chat);
});
}
callback();
},
//copy all revisions
function(callback)
{
var revHead = _this.head;
//console.log(revHead);
for(var i=0;i<=revHead;i++)
{
db.get("pad:"+sourceID+":revs:"+i, function (err, rev) {
//console.log("HERE");
if (ERR(err, callback)) return;
db.set("pad:"+destinationID+":revs:"+i, rev);
});
}
callback();
},
//add the new pad to all authors who contributed to the old one
function(callback)
{
var authorIDs = _this.getAllAuthors();
authorIDs.forEach(function (authorID)
{
console.log("authors");
authorManager.addPad(authorID, destinationID);
});
callback();
},
// parallel
], callback);
},
// series
], function(err)
{
if(ERR(err, callback)) return;
callback(null, {padID: destinationID});
});
};
Pad.prototype.remove = function remove(callback) {
var padID = this.id;
var _this = this;
@ -487,7 +635,7 @@ Pad.prototype.remove = function remove(callback) {
authorIDs.forEach(function (authorID)
{
authorManager.removePad(authorID, padID);
authorManager.removePad(authorID, padID);
});
callback();

View file

@ -24,7 +24,9 @@ var Pad = require("../db/Pad").Pad;
var db = require("./DB").db;
/**
* An Object containing all known Pads. Provides "get" and "set" functions,
* A cache of all loaded 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
* these functions themselves.
@ -37,39 +39,55 @@ var globalPads = {
set: function (name, value)
{
this[':'+name] = value;
padList.addPad(name);
},
remove: function (name) { delete this[':'+name]; }
remove: function (name) {
delete this[':'+name];
}
};
/**
* A cache of the list of all pads.
*
* Updated without db access as new pads are created/old ones removed.
*/
var padList = {
list: [],
sorted : false,
init: function()
initiated: false,
init: function(cb)
{
db.findKeys("pad:*", "*:*:*", function(err, dbData)
{
if(ERR(err)) return;
if(ERR(err, cb)) return;
if(dbData != null){
padList.initiated = true
dbData.forEach(function(val){
padList.addPad(val.replace(/pad:/,""),false);
});
cb && cb()
}
});
return this;
},
load: function(cb) {
if(this.initiated) cb && cb()
else this.init(cb)
},
/**
* Returns all pads in alphabetical order as array.
*/
getPads: function(){
if(!this.sorted){
this.list=this.list.sort();
this.sorted=true;
}
return this.list;
getPads: function(cb){
this.load(function() {
if(!padList.sorted){
padList.list = padList.list.sort();
padList.sorted = true;
}
cb && cb(padList.list);
})
},
addPad: function(name)
{
if(!this.initiated) return;
if(this.list.indexOf(name) == -1){
this.list.push(name);
this.sorted=false;
@ -77,7 +95,8 @@ var padList = {
},
removePad: function(name)
{
var index=this.list.indexOf(name);
if(!this.initiated) return;
var index = this.list.indexOf(name);
if(index>-1){
this.list.splice(index,1);
this.sorted=false;
@ -85,7 +104,6 @@ var padList = {
}
};
//initialises the allknowing data structure
padList.init();
/**
* An array of padId transformations. These represent changes in pad name policy over
@ -146,25 +164,23 @@ exports.getPad = function(id, text, callback)
else
{
pad = new Pad(id);
//initalize the pad
pad.init(text, function(err)
{
if(ERR(err, callback)) return;
globalPads.set(id, pad);
padList.addPad(id);
callback(null, pad);
});
}
}
exports.listAllPads = function(callback)
exports.listAllPads = function(cb)
{
if(callback != null){
callback(null,{padIDs: padList.getPads()});
}else{
return {padIDs: padList.getPads()};
}
padList.getPads(function(list) {
cb && cb(null, {padIDs: list});
});
}
//checks if a pad exists
@ -230,9 +246,8 @@ exports.removePad = function(padId){
padList.removePad(padId);
}
//removes a pad from the array
//removes a pad from the cache
exports.unloadPad = function(padId)
{
if(globalPads.get(padId))
globalPads.remove(padId);
globalPads.remove(padId);
}

View file

@ -77,28 +77,22 @@ exports.getPadId = function(readOnlyId, callback)
* returns a the padId and readonlyPadId in an object for any id
* @param {String} padIdOrReadonlyPadId read only id or real pad id
*/
exports.getIds = function(padIdOrReadonlyPadId, callback) {
var handleRealPadId = function () {
exports.getReadOnlyId(padIdOrReadonlyPadId, function (err, value) {
exports.getIds = function(id, callback) {
if (id.indexOf("r.") == 0)
exports.getPadId(id, function (err, value) {
if(ERR(err, callback)) return;
callback(null, {
readOnlyPadId: id,
padId: value, // Might be null, if this is an unknown read-only id
readonly: true
});
});
else
exports.getReadOnlyId(id, function (err, value) {
callback(null, {
readOnlyPadId: value,
padId: padIdOrReadonlyPadId,
padId: id,
readonly: false
});
});
}
if (padIdOrReadonlyPadId.indexOf("r.") != 0)
return handleRealPadId();
exports.getPadId(padIdOrReadonlyPadId, function (err, value) {
if(ERR(err, callback)) return;
if (value == null)
return handleRealPadId();
callback(null, {
readOnlyPadId: padIdOrReadonlyPadId,
padId: value,
readonly: true
});
});
}

View file

@ -27,6 +27,8 @@ var padManager = require("./PadManager");
var sessionManager = require("./SessionManager");
var settings = require("../utils/Settings");
var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
var log4js = require('log4js');
var authLogger = log4js.getLogger("auth");
/**
* This function controlls the access to a pad, it checks if the user can access a pad.
@ -39,6 +41,11 @@ var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
exports.checkAccess = function (padID, sessionCookie, token, password, callback)
{
var statusObject;
if(!padID) {
callback(null, {accessStatus: "deny"});
return;
}
// a valid session is required (api-only mode)
if(settings.requireSession)
@ -117,31 +124,43 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback)
//get information about all sessions contained in this cookie
function(callback)
{
if (!sessionCookie) {
if (!sessionCookie)
{
callback();
return;
}
var sessionIDs = sessionCookie.split(',');
async.forEach(sessionIDs, function(sessionID, callback) {
sessionManager.getSessionInfo(sessionID, function(err, sessionInfo) {
async.forEach(sessionIDs, function(sessionID, callback)
{
sessionManager.getSessionInfo(sessionID, function(err, sessionInfo)
{
//skip session if it doesn't exist
if(err && err.message == "sessionID does not exist") return;
if(err && err.message == "sessionID does not exist")
{
authLogger.debug("Auth failed: unknown session");
callback();
return;
}
if(ERR(err, callback)) return;
var now = Math.floor(new Date().getTime()/1000);
//is it for this group?
if(sessionInfo.groupID != groupID) {
callback();
return;
if(sessionInfo.groupID != groupID)
{
authLogger.debug("Auth failed: wrong group");
callback();
return;
}
//is validUntil still ok?
if(sessionInfo.validUntil <= now){
callback();
return;
if(sessionInfo.validUntil <= now)
{
authLogger.debug("Auth failed: validUntil");
callback();
return;
}
// There is a valid session
@ -234,7 +253,11 @@ exports.checkAccess = function (padID, sessionCookie, 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(settings.editOnly)
{
authLogger.debug("Auth failed: valid session & pad does not exist");
statusObject.accessStatus = "deny";
}
}
// there is no valid session avaiable AND pad exists
else if(!validSession && padExists)
@ -266,6 +289,7 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback)
//- its not public
else if(!isPublic)
{
authLogger.debug("Auth failed: invalid session & pad is not public");
//--> deny access
statusObject = {accessStatus: "deny"};
}
@ -277,6 +301,7 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback)
// there is no valid session avaiable AND pad doesn't exists
else
{
authLogger.debug("Auth failed: invalid session & pad does not exist");
//--> deny access
statusObject = {accessStatus: "deny"};
}

View file

@ -1,5 +1,5 @@
/**
* The Session Manager provides functions to manage session in the database
* The Session Manager provides functions to manage session in the database, it only provides session management for sessions created by the API
*/
/*

View file

@ -0,0 +1,82 @@
/*
* Stores session data in the database
* Source; https://github.com/edy-b/SciFlowWriter/blob/develop/available_plugins/ep_sciflowwriter/db/DirtyStore.js
* This is not used for authors that are created via the API at current
*/
var Store = require('ep_etherpad-lite/node_modules/connect/lib/middleware/session/store'),
utils = require('ep_etherpad-lite/node_modules/connect/lib/utils'),
Session = require('ep_etherpad-lite/node_modules/connect/lib/middleware/session/session'),
db = require('ep_etherpad-lite/node/db/DB').db,
log4js = require('ep_etherpad-lite/node_modules/log4js'),
messageLogger = log4js.getLogger("SessionStore");
var SessionStore = module.exports = function SessionStore() {};
SessionStore.prototype.__proto__ = Store.prototype;
SessionStore.prototype.get = function(sid, fn){
messageLogger.debug('GET ' + sid);
var self = this;
db.get("sessionstorage:" + sid, function (err, sess)
{
if (sess) {
sess.cookie.expires = 'string' == typeof sess.cookie.expires ? new Date(sess.cookie.expires) : sess.cookie.expires;
if (!sess.cookie.expires || new Date() < sess.cookie.expires) {
fn(null, sess);
} else {
self.destroy(sid, fn);
}
} else {
fn();
}
});
};
SessionStore.prototype.set = function(sid, sess, fn){
messageLogger.debug('SET ' + sid);
db.set("sessionstorage:" + sid, sess);
process.nextTick(function(){
if(fn) fn();
});
};
SessionStore.prototype.destroy = function(sid, fn){
messageLogger.debug('DESTROY ' + sid);
db.remove("sessionstorage:" + sid);
process.nextTick(function(){
if(fn) fn();
});
};
SessionStore.prototype.all = function(fn){
messageLogger.debug('ALL');
var sessions = [];
db.forEach(function(key, value){
if (key.substr(0,15) === "sessionstorage:") {
sessions.push(value);
}
});
fn(null, sessions);
};
SessionStore.prototype.clear = function(fn){
messageLogger.debug('CLEAR');
db.forEach(function(key, value){
if (key.substr(0,15) === "sessionstorage:") {
db.db.remove("session:" + key);
}
});
if(fn) fn();
};
SessionStore.prototype.length = function(fn){
messageLogger.debug('LENGTH');
var i = 0;
db.forEach(function(key, value){
if (key.substr(0,15) === "sessionstorage:") {
i++;
}
});
fn(null, i);
};