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