diff --git a/node/db/Pad.js b/node/db/Pad.js index 7807d464..632eebe8 100644 --- a/node/db/Pad.js +++ b/node/db/Pad.js @@ -2,8 +2,6 @@ * The pad object, defined with joose */ -require('joose'); - var ERR = require("async-stacktrace"); var Changeset = require("../utils/Changeset"); var AttributePoolFactory = require("../utils/AttributePoolFactory"); @@ -22,490 +20,451 @@ var crypto = require("crypto"); */ 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 - - publicStatus : { - is: 'rw', - init: false, - getterName : 'getPublicStatus' - }, //publicStatus - - passwordHash : { - is: 'rw', - init: null - }, // passwordHash - - id : { is : 'r' } - }, +var Pad = function Pad(id) { - methods : { - - BUILD : function (id) - { - return { - 'id' : id, - } - }, - - appendRevision : function(aChangeset, author) - { - if(!author) - author = ''; + this.atext = Changeset.makeAText("\n"); + this.pool = AttributePoolFactory.createAttributePool(); + this.head = -1; + this.chatHead = -1; + this.publicStatus = false; + this.passwordHash = null; + this.id = id; - 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, - publicStatus: this.publicStatus, - passwordHash: this.passwordHash}); - }, //appendRevision - - getRevisionChangeset : function(revNum, callback) +}; + +exports.Pad = Pad; + +Pad.prototype.apool = function apool() { + return this.pool; +}; + +Pad.prototype.getHeadRevisionNumber = function getHeadRevisionNumber() { + return this.head; +}; + +Pad.prototype.getPublicStatus = function getPublicStatus() { + return this.publicStatus; +}; + +Pad.prototype.appendRevision = function appendRevision(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, + publicStatus: this.publicStatus, + passwordHash: this.passwordHash}); +}; + +Pad.prototype.getRevisionChangeset = function getRevisionChangeset(revNum, callback) { + 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); +}; + +Pad.prototype.getRevisionDate = function getRevisionDate(revNum, callback) { + db.getSub("pad:"+this.id+":revs:"+revNum, ["meta", "timestamp"], callback); +}; + +Pad.prototype.getAllAuthors = function getAllAuthors() { + var authors = []; + + for(key in this.pool.numToAttrib) + { + if(this.pool.numToAttrib[key][0] == "author" && this.pool.numToAttrib[key][1] != "") { - 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() + authors.push(this.pool.numToAttrib[key][1]); + } + } + + return authors; +}; + +Pad.prototype.getInternalRevisionAText = function getInternalRevisionAText(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) { - var authors = []; - - for(key in this.pool.numToAttrib) - { - if(this.pool.numToAttrib[key][0] == "author" && this.pool.numToAttrib[key][1] != "") + async.parallel([ + //get the atext of the key revision + function (callback) { - authors.push(this.pool.numToAttrib[key][1]); + db.getSub("pad:"+_this.id+":revs:"+keyRev, ["meta", "atext"], function(err, _atext) + { + if(ERR(err, callback)) return; + atext = Changeset.cloneAText(_atext); + callback(); + }); + }, + //get all needed changesets + function (callback) + { + async.forEach(neededChangesets, function(item, callback) + { + _this.getRevisionChangeset(item, function(err, changeset) + { + if(ERR(err, callback)) return; + changesets[item] = changeset; + callback(); + }); + }, callback); } - } - - return authors; + ], callback); }, - - getInternalRevisionAText : function(targetRev, callback) + //apply all changesets to the key changeset + function(callback) { - var _this = this; - - var keyRev = this.getKeyRevisionNumber(targetRev); - var atext; - var changesets = []; - - //find out which changesets are needed - var neededChangesets = []; + var apool = _this.apool(); var curRev = keyRev; - while (curRev < targetRev) + + while (curRev < targetRev) { curRev++; - neededChangesets.push(curRev); + var cs = changesets[curRev]; + atext = Changeset.applyToAText(cs, atext, apool); } - - 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) - { - if(ERR(err, callback)) return; - atext = Changeset.cloneAText(_atext); - callback(); - }); - }, - //get all needed changesets - function (callback) - { - async.forEach(neededChangesets, function(item, callback) - { - _this.getRevisionChangeset(item, function(err, changeset) - { - if(ERR(err, callback)) return; - changesets[item] = changeset; - callback(); - }); - }, 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) - { - if(ERR(err, callback)) return; - callback(null, 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) - { + + callback(null); + } + ], function(err) + { + if(ERR(err, callback)) return; + callback(null, atext); + }); +}; + +Pad.prototype.getKeyRevisionNumber = function getKeyRevisionNumber(revNum) { + return Math.floor(revNum / 100) * 100; +}; + +Pad.prototype.text = function text() { + return this.atext.text; +}; + +Pad.prototype.setText = function setText(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); +}; + +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}); //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) - { - if(ERR(err, callback)) return; - entry = _entry; - callback(); - }); - }, - //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) - { - if(ERR(err, callback)) return; - entry.userName = authorName; - callback(); - }); - } - ], function(err) +}; + +Pad.prototype.getChatMessage = function getChatMessage(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) { if(ERR(err, callback)) return; - callback(null, entry); + entry = _entry; + callback(); }); }, - - getLastChatMessages: function(count, callback) + //add the authorName + function(callback) { - //return an empty array if there are no chat messages - if(this.chatHead == -1) + //this chat message doesn't exist, return null + if(entry == null) { - callback(null, []); + callback(); 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) - { - if(ERR(err, callback)) return; - entries[entryObject.order] = entry; - callback(); - }); - }, function(err) - { - if(ERR(err, callback)) return; - - //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 delete the entry of this pad in the group - function(callback) - { - //is it a group pad? - if(padID.indexOf("$")!=-1) - { - var groupID = padID.substring(0,padID.indexOf("$")); - - db.get("group:" + groupID, function (err, group) - { - if(ERR(err, callback)) return; - - //remove the pad entry - delete group.pads[padID]; - - //set the new value - db.set("group:" + groupID, group); - - callback(); - }); - } - //its no group pad, nothing to do here - else - { - callback(); - } - }, - //remove the readonly entries - function(callback) - { - readOnlyManager.getReadOnlyId(padID, function(err, readonlyID) - { - if(ERR(err, callback)) return; - - db.remove("pad2readonly:" + padID); - db.remove("readonly2pad:" + readonlyID); - - callback(); - }); - }, - //delete all chat messages - function(callback) - { - var chatHead = _this.chatHead; - - for(var i=0;i<=chatHead;i++) - { - db.remove("pad:"+padID+":chat:"+i); - } - - callback(); - }, - //delete all revisions - function(callback) - { - var revHead = _this.head; - - for(var i=0;i<=revHead;i++) - { - db.remove("pad:"+padID+":revs:"+i); - } - - callback(); - } - ], callback); - }, - //delete the pad entry and delete pad from padManager - function(callback) - { - db.remove("pad:"+padID); - padManager.unloadPad(padID); - callback(); - } - ], function(err) + //get the authorName + authorManager.getAuthorName(entry.userId, function(err, authorName) { if(ERR(err, callback)) return; + entry.userName = authorName; callback(); - }) - }, - //set in db - setPublicStatus: function(publicStatus) - { - this.publicStatus = publicStatus; - db.setSub("pad:"+this.id, ["publicStatus"], this.publicStatus); - }, - setPassword: function(password) - { - this.passwordHash = password == null ? null : hash(password, generateSalt()); - db.setSub("pad:"+this.id, ["passwordHash"], this.passwordHash); - }, - isCorrectPassword: function(password) - { - return compare(this.passwordHash, password) - }, - isPasswordProtected: function() - { - return this.passwordHash != null; + }); } - }, // methods -}); + ], function(err) + { + if(ERR(err, callback)) return; + callback(null, entry); + }); +}; + +Pad.prototype.getLastChatMessages = function getLastChatMessages(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) + { + if(ERR(err, callback)) return; + entries[entryObject.order] = entry; + callback(); + }); + }, function(err) + { + if(ERR(err, callback)) return; + + //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 delete the entry of this pad in the group + function(callback) + { + //is it a group pad? + if(padID.indexOf("$")!=-1) + { + var groupID = padID.substring(0,padID.indexOf("$")); + + db.get("group:" + groupID, function (err, group) + { + if(ERR(err, callback)) return; + + //remove the pad entry + delete group.pads[padID]; + + //set the new value + db.set("group:" + groupID, group); + + callback(); + }); + } + //its no group pad, nothing to do here + else + { + callback(); + } + }, + //remove the readonly entries + function(callback) + { + readOnlyManager.getReadOnlyId(padID, function(err, readonlyID) + { + if(ERR(err, callback)) return; + + db.remove("pad2readonly:" + padID); + db.remove("readonly2pad:" + readonlyID); + + callback(); + }); + }, + //delete all chat messages + function(callback) + { + var chatHead = _this.chatHead; + + for(var i=0;i<=chatHead;i++) + { + db.remove("pad:"+padID+":chat:"+i); + } + + callback(); + }, + //delete all revisions + function(callback) + { + var revHead = _this.head; + + for(var i=0;i<=revHead;i++) + { + db.remove("pad:"+padID+":revs:"+i); + } + + callback(); + } + ], callback); + }, + //delete the pad entry and delete pad from padManager + function(callback) + { + db.remove("pad:"+padID); + padManager.unloadPad(padID); + callback(); + } + ], function(err) + { + if(ERR(err, callback)) return; + callback(); + }); +}; + //set in db +Pad.prototype.setPublicStatus = function setPublicStatus(publicStatus) { + this.publicStatus = publicStatus; + 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); +}; + +Pad.prototype.isCorrectPassword = function isCorrectPassword(password) { + return compare(this.passwordHash, password); +}; + +Pad.prototype.isPasswordProtected = function isPasswordProtected() { + return this.passwordHash != null; +}; /* Crypto helper methods */ @@ -531,5 +490,5 @@ function generateSalt() function compare(hashStr, password) { - return hash(password, hashStr.split("$")[1]) === hashStr; + return hash(password, hashStr.split("$")[1]) === hashStr; } diff --git a/node/db/PadManager.js b/node/db/PadManager.js index 1aadcd1f..231aa901 100644 --- a/node/db/PadManager.js +++ b/node/db/PadManager.js @@ -20,7 +20,7 @@ var ERR = require("async-stacktrace"); var customError = require("../utils/customError"); -require("../db/Pad"); +var Pad = require("../db/Pad").Pad; var db = require("./DB").db; /** diff --git a/package.json b/package.json index e24a776c..ec693697 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,6 @@ "socket.io" : "0.8.7", "ueberDB" : "0.1.3", "async" : "0.1.15", - "joose" : "3.50.0", "express" : "2.5.0", "clean-css" : "0.2.4", "uglify-js" : "1.1.1",