/** * The pad object, defined with joose */ 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"); /** * Copied from the Etherpad source code. It converts Windows line breaks to Unix line breaks and convert Tabs to spaces * @param txt */ exports.cleanText = function (txt) { return txt.replace(/\r\n/g,'\n').replace(/\r/g,'\n').replace(/\t/g, ' ').replace(/\xa0/g, ' '); } Class('Pad', { // these are the properties has : { atext : { is : 'rw', // readwrite init : function() { return Changeset.makeAText("\n"); } // first value }, // atext pool : { is: 'rw', init : function() { return AttributePoolFactory.createAttributePool(); }, getterName : 'apool' // legacy }, // pool head : { is : 'rw', init : -1, getterName : 'getHeadRevisionNumber' }, // head chatHead : { is: 'rw', init: -1 }, // chatHead id : { is : 'r' } }, methods : { BUILD : function (id) { return { 'id' : id, } }, appendRevision : function(aChangeset, author) { if(!author) author = ''; var newAText = Changeset.applyToAText(aChangeset, this.atext, this.pool); Changeset.copyAText(newAText, this.atext); var newRev = ++this.head; var newRevData = {}; newRevData.changeset = aChangeset; newRevData.meta = {}; newRevData.meta.author = author; newRevData.meta.timestamp = new Date().getTime(); //ex. getNumForAuthor if(author != '') this.pool.putAttrib(['author', author || '']); if(newRev % 100 == 0) { newRevData.meta.atext = this.atext; } db.set("pad:"+this.id+":revs:"+newRev, newRevData); db.set("pad:"+this.id, {atext: this.atext, pool: this.pool.toJsonable(), head: this.head, chatHead: this.chatHead}); }, //appendRevision getRevisionChangeset : function(revNum, callback) { db.getSub("pad:"+this.id+":revs:"+revNum, ["changeset"], callback); }, // getRevisionChangeset getRevisionAuthor : function(revNum, callback) { db.getSub("pad:"+this.id+":revs:"+revNum, ["meta", "author"], callback); }, // getRevisionAuthor getRevisionDate : function(revNum, callback) { db.getSub("pad:"+this.id+":revs:"+revNum, ["meta", "timestamp"], callback); }, // getRevisionAuthor getAllAuthors : function() { var authors = []; for(key in this.pool.numToAttrib) { if(this.pool.numToAttrib[key][0] == "author" && this.pool.numToAttrib[key][1] != "") { authors.push(this.pool.numToAttrib[key][1]); } } return authors; }, getInternalRevisionAText : function(targetRev, callback) { var _this = this; var keyRev = this.getKeyRevisionNumber(targetRev); var atext; var changesets = []; //find out which changesets are needed var neededChangesets = []; var curRev = keyRev; while (curRev < targetRev) { curRev++; neededChangesets.push(curRev); } async.series([ //get all needed data out of the database function(callback) { async.parallel([ //get the atext of the key revision function (callback) { db.getSub("pad:"+_this.id+":revs:"+keyRev, ["meta", "atext"], function(err, _atext) { atext = Changeset.cloneAText(_atext); callback(err); }); }, //get all needed changesets function (callback) { async.forEach(neededChangesets, function(item, callback) { _this.getRevisionChangeset(item, function(err, changeset) { changesets[item] = changeset; callback(err); }); }, callback); } ], callback); }, //apply all changesets to the key changeset function(callback) { var apool = _this.apool(); var curRev = keyRev; while (curRev < targetRev) { curRev++; var cs = changesets[curRev]; atext = Changeset.applyToAText(cs, atext, apool); } callback(null); } ], function(err) { callback(err, atext); }); }, getKeyRevisionNumber : function(revNum) { return Math.floor(revNum / 100) * 100; }, text : function() { return this.atext.text; }, setText : function(newText) { //clean the new text newText = exports.cleanText(newText); var oldText = this.text(); //create the changeset var changeset = Changeset.makeSplice(oldText, 0, oldText.length-1, newText); //append the changeset this.appendRevision(changeset); }, appendChatMessage: function(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}); //save the new chat head db.setSub("pad:"+this.id, ["chatHead"], this.chatHead); }, getChatMessage: function(entryNum, callback) { var _this = this; var entry; async.series([ //get the chat entry function(callback) { db.get("pad:"+_this.id+":chat:"+entryNum, function(err, _entry) { entry = _entry; callback(err); }); }, //add the authorName function(callback) { //this chat message doesn't exist, return null if(entry == null) { callback(); return; } //get the authorName authorManager.getAuthorName(entry.userId, function(err, authorName) { entry.userName = authorName; callback(err); }); } ], function(err) { callback(err, entry); }); }, getLastChatMessages: function(count, callback) { //return an empty array if there are no chat messages if(this.chatHead == -1) { callback(null, []); return; } var _this = this; //works only if we decrement the amount, for some reason count--; //set the startpoint var start = this.chatHead-count; if(start < 0) start = 0; //set the endpoint var end = this.chatHead; //collect the numbers of chat entries and in which order we need them var neededEntries = []; var order = 0; for(var i=start;i<=end; i++) { neededEntries.push({entryNum:i, order: order}); order++; } //get all entries out of the database var entries = []; async.forEach(neededEntries, function(entryObject, callback) { _this.getChatMessage(entryObject.entryNum, function(err, entry) { entries[entryObject.order] = entry; callback(err); }); }, function(err) { //sort out broken chat entries //it looks like in happend in the past that the chat head was //incremented, but the chat message wasn't added var cleanedEntries = []; for(var i=0;i