      1 /**
      2  * 2011 Peter 'Pita' Martischka
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS-IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16
     17 /*
     18 The Pad Module trys to simulate the pad object from EtherPad. You can find the original code in /etherpad/src/etherpad/pad/model.js
     19 see https://github.com/ether/pad/blob/master/etherpad/src/etherpad/pad/model.js
     20 */
     21
     22 var Changeset = require("./Changeset");
     23 var AttributePoolFactory = require("./AttributePoolFactory");
     24
     25 /**
     26  * The initial Text of a Pad
     27  */
     28 exports.startText = "Hello World\nGoodbye Etherpad";
     29
     30 /**
     31  * A Array with all known Pads
     32  */
     33 globalPads = [];
     34
     35 /**
     36  * Return a Function Wrapper to work with the Pad
     37  * @param id A String with the id of the pad
     38  * @param createIfNotExist A Boolean which says the function if it should create the Pad if it not exist
     39  */
     40 exports.getPad = function(id, createIfNotExist)
     41 {
     42   if(!globalPads[id] && createIfNotExist == true)
     43   {
     44     createPad(id);
     45   }
     46
     47   if(!globalPads[id])
     48     return null;
     49
     50   globalPads[id].timestamp = new Date().getTime();
     51
     52   var functionWrapper = {};
     53
     54   functionWrapper.id = id;
     55   functionWrapper.appendRevision = function (theChangeset, author) {return appendRevision(id, theChangeset, author)};
     56   functionWrapper.text = function () {return text(id)};
     57   functionWrapper.atext = function () {return atext(id)};
     58   functionWrapper.pool = function () {return pool(id)};
     59   functionWrapper.getHeadRevisionNumber = function () {return getHeadRevisionNumber(id)};
     60   functionWrapper.getRevisionChangeset = function (revNum) {return getRevisionChangeset(id, revNum)};
     61   functionWrapper.getRevisionAuthor = function (revNum) {return getRevisionAuthor(id, revNum)};
     62   functionWrapper.getAllAuthors = function () {return getAllAuthors(id)};
     63
     64   return functionWrapper;
     65 }
     66
     67 /**
     68  * Ensures that the Pad exists
     69  * @param id The Pad id
     70  */
     71 exports.ensurePadExists = function(id)
     72 {
     73   if(!globalPads[id])
     74   {
     75     createPad(id);
     76   }
     77 }
     78
     79 /**
     80  * Creates an empty pad
     81  * @param id The Pad id
     82  */
     83 function createPad(id)
     84 {
     85   var pad = {};
     86   globalPads[id] = pad;
     87
     88   pad.id = id;
     89   pad.rev = [];
     90   pad.head = -1;
     91   pad.atext = Changeset.makeAText("\n");
     92   pad.apool = AttributePoolFactory.createAttributePool();
     93   pad.authors = [];
     94
     95   var firstChangeset = Changeset.makeSplice("\n", 0, 0,
     96                         exports.cleanText(exports.startText));
     97   appendRevision(id, firstChangeset, '');
     98 }
     99
    100 /**
    101  * Append a changeset to a pad
    102  * @param id The Pad id
    103  * @param theChangeset the changeset which should apply to the text
    104  * @param The author of the revision, can be null
    105  */
    106 function appendRevision(id, theChangeset, author)
    107 {
    108   throwExceptionIfPadDontExist(id);
    109
    110   if(!author)
    111     author = '';
    112
    113   var atext = globalPads[id].atext;
    114   var apool = globalPads[id].apool;
    115   var newAText = Changeset.applyToAText(theChangeset, atext, apool);
    116   Changeset.copyAText(newAText, atext);
    117
    118   var newRev = ++globalPads[id].head;
    119   globalPads[id].rev[newRev] = {};
    120   globalPads[id].rev[newRev].changeset = theChangeset;
    121   globalPads[id].rev[newRev].meta = {};
    122   globalPads[id].rev[newRev].meta.author = author;
    123   globalPads[id].rev[newRev].meta.timestamp = new Date().getTime();
    124
    125   //ex. getNumForAuthor
    126   apool.putAttrib(['author',author||'']);
    127
    128   if(newRev%100==0)
    129   {
    130     globalPads[id].rev[newRev].meta.atext=atext;
    131   }
    132 }
    133
    134 /**
    135  * Returns all Authors of a Pad
    136  * @param id The Pad id
    137  */
    138 function getAllAuthors(id)
    139 {
    140   var authors = [];
    141
    142   for(key in globalPads[id].apool.numToAttrib)
    143   {
    144     if(globalPads[id].apool.numToAttrib[key][0] == "author" && globalPads[id].apool.numToAttrib[key][1] != "")
    145     {
    146       authors.push(globalPads[id].apool.numToAttrib[key][1]);
    147     }
    148   }
    149
    150   return authors;
    151 }
    152
    153 /**
    154  * Returns the plain text of a pad
    155  * @param id The Pad id
    156  */
    157
    158 function text(id)
    159 {
    160   throwExceptionIfPadDontExist(id);
    161
    162   return globalPads[id].atext.text;
    163 }
    164
    165 /**
    166  * Returns the Attributed Text of a pad
    167  * @param id The Pad id
    168  */
    169 function atext(id)
    170 {
    171   throwExceptionIfPadDontExist(id);
    172
    173   return globalPads[id].atext;
    174 }
    175
    176 /**
    177  * Returns the Attribute Pool whichs the Pad is using
    178  * @param id The Pad id
    179  */
    180 function pool(id)
    181 {
    182   throwExceptionIfPadDontExist(id);
    183
    184   return globalPads[id].apool;
    185 }
    186
    187 /**
    188  * Returns the latest Revision Number of the Pad
    189  * @param id The Pad id
    190  */
    191 function getHeadRevisionNumber(id)
    192 {
    193   throwExceptionIfPadDontExist(id);
    194
    195   return globalPads[id].head;
    196 }
    197
    198 /**
    199  * Returns the changeset of a specific revision
    200  * @param id The Pad id
    201  * @param revNum The Revision Number
    202  */
    203 function getRevisionChangeset(id, revNum)
    204 {
    205   throwExceptionIfPadDontExist(id);
    206   throwExceptionIfRevDontExist(id, revNum);
    207
    208   return globalPads[id].rev[revNum].changeset;
    209 }
    210
    211 /**
    212  * Returns the author of a specific revision
    213  * @param id The Pad id
    214  * @param revNum The Revision Number
    215  */
    216 function getRevisionAuthor(id, revNum)
    217 {
    218   throwExceptionIfPadDontExist(id);
    219   throwExceptionIfRevDontExist(id, revNum);
    220
    221   return globalPads[id].rev[revNum].meta.author;
    222 }
    223
    224 /**
    225  * Check if the ID is a valid Pad ID and trows an Exeption if not
    226  * @param id The Pad id
    227  */
    228 function throwExceptionIfPadDontExist(id)
    229 {
    230   if(id == null)
    231   {
    232     throw "Padname is null!";
    233   }
    234   if(!globalPads[id])
    235   {
    236     throw "Pad don't exist!'";
    237   }
    238 }
    239
    240 /**
    241  * Check if the Revision of a Pad is valid and throws an Exeption if not
    242  * @param id The Pad id
    243  */
    244 function throwExceptionIfRevDontExist(id, revNum)
    245 {
    246   if(revNum == null)
    247     throw "revNum is null";
    248
    249   if((typeof revNum) != "number")
    250     throw revNum + " is no Number";
    251
    252   if(revNum < 0 || revNum > globalPads[id].head)
    253     throw "The Revision " + revNum + " don't exist'";
    254 }
    255
    256 /**
    257  * Copied from the Etherpad source code, don't know what its good for
    258  * @param txt
    259  */
    260 exports.cleanText = function (txt) {
    261   return txt.replace(/\r\n/g,'\n').replace(/\r/g,'\n').replace(/\t/g, '        ').replace(/\xa0/g, ' ');
    262 }
    263
    264
    265