summaryrefslogtreecommitdiffstats
path: root/trunk/etherpad/src/etherpad/pad/model.js
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/etherpad/src/etherpad/pad/model.js')
-rw-r--r--trunk/etherpad/src/etherpad/pad/model.js651
1 files changed, 0 insertions, 651 deletions
diff --git a/trunk/etherpad/src/etherpad/pad/model.js b/trunk/etherpad/src/etherpad/pad/model.js
deleted file mode 100644
index 9424f10..0000000
--- a/trunk/etherpad/src/etherpad/pad/model.js
+++ /dev/null
@@ -1,651 +0,0 @@
-/**
- * Copyright 2009 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS-IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import("fastJSON");
-import("sqlbase.sqlbase");
-import("sqlbase.sqlcommon");
-import("sqlbase.sqlobj");
-import("timer");
-import("sync");
-
-import("etherpad.collab.ace.easysync2.{Changeset,AttribPool}");
-import("etherpad.log");
-import("etherpad.pad.padevents");
-import("etherpad.pad.padutils");
-import("etherpad.pad.dbwriter");
-import("etherpad.pad.pad_migrations");
-import("etherpad.pad.pad_security");
-import("etherpad.collab.collab_server");
-import("cache_utils.syncedWithCache");
-jimport("net.appjet.common.util.LimitedSizeMapping");
-
-jimport("java.lang.System.out.println");
-
-jimport("java.util.concurrent.ConcurrentHashMap");
-jimport("net.appjet.oui.GlobalSynchronizer");
-jimport("net.appjet.oui.exceptionlog");
-
-function onStartup() {
- appjet.cache.pads = {};
- appjet.cache.pads.meta = new ConcurrentHashMap();
- appjet.cache.pads.temp = new ConcurrentHashMap();
- appjet.cache.pads.revs = new ConcurrentHashMap();
- appjet.cache.pads.revs10 = new ConcurrentHashMap();
- appjet.cache.pads.revs100 = new ConcurrentHashMap();
- appjet.cache.pads.revs1000 = new ConcurrentHashMap();
- appjet.cache.pads.chat = new ConcurrentHashMap();
- appjet.cache.pads.revmeta = new ConcurrentHashMap();
- appjet.cache.pads.authors = new ConcurrentHashMap();
- appjet.cache.pads.apool = new ConcurrentHashMap();
-}
-
-var _JSON_CACHE_SIZE = 10000;
-
-// to clear: appjet.cache.padmodel.modelcache.map.clear()
-function _getModelCache() {
- return syncedWithCache('padmodel.modelcache', function(cache) {
- if (! cache.map) {
- cache.map = new LimitedSizeMapping(_JSON_CACHE_SIZE);
- }
- return cache.map;
- });
-}
-
-function cleanText(txt) {
- return txt.replace(/\r\n/g,'\n').replace(/\r/g,'\n').replace(/\t/g, ' ').replace(/\xa0/g, ' ');
-}
-
-/**
- * Access a pad object, which is passed as an argument to
- * the given padFunc, which is executed inside an exclusive lock,
- * and return the result. If the pad doesn't exist, a wrapper
- * object is still created and passed to padFunc, and it can
- * be used to check whether the pad exists and create it.
- *
- * Note: padId is a GLOBAL id.
- */
-function accessPadGlobal(padId, padFunc, rwMode) {
- // this may make a nested call to accessPadGlobal, so do it first
- pad_security.checkAccessControl(padId, rwMode);
-
- // pad is never loaded into memory (made "active") unless it has been migrated.
- // Migrations do not use accessPad, but instead access the database directly.
- pad_migrations.ensureMigrated(padId);
-
- var mode = (rwMode || "rw").toLowerCase();
-
- if (! appjet.requestCache.padsAccessing) {
- appjet.requestCache.padsAccessing = {};
- }
- if (appjet.requestCache.padsAccessing[padId]) {
- // nested access to same pad
- var p = appjet.requestCache.padsAccessing[padId];
- var m = p._meta;
- if (m && mode != "r") {
- m.status.lastAccess = +new Date();
- m.status.dirty = true;
- }
- return padFunc(p);
- }
-
- return doWithPadLock(padId, function() {
- return sqlcommon.inTransaction(function() {
- var meta = _getPadMetaData(padId); // null if pad doesn't exist yet
-
- if (meta && ! meta.status) {
- meta.status = { validated: false };
- }
-
- if (meta && mode != "r") {
- meta.status.lastAccess = +new Date();
- }
-
- function getCurrentAText() {
- var tempObj = pad.tempObj();
- if (! tempObj.atext) {
- tempObj.atext = pad.getInternalRevisionAText(meta.head);
- }
- return tempObj.atext;
- }
- function addRevision(theChangeset, author, optDatestamp) {
- var atext = getCurrentAText();
- var newAText = Changeset.applyToAText(theChangeset, atext, pad.pool());
- Changeset.copyAText(newAText, atext); // updates pad.tempObj().atext!
-
- var newRev = ++meta.head;
-
- var revs = _getPadStringArray(padId, "revs");
- revs.setEntry(newRev, theChangeset);
-
- var revmeta = _getPadStringArray(padId, "revmeta");
- var thisRevMeta = {t: (optDatestamp || (+new Date())),
- a: getNumForAuthor(author)};
- if ((newRev % meta.keyRevInterval) == 0) {
- thisRevMeta.atext = atext;
- }
- revmeta.setJSONEntry(newRev, thisRevMeta);
-
- updateCoarseChangesets(true);
- }
- function getNumForAuthor(author, dontAddIfAbsent) {
- return pad.pool().putAttrib(['author',author||''], dontAddIfAbsent);
- }
- function getAuthorForNum(n) {
- // must return null if n is an attrib number that isn't an author
- var pair = pad.pool().getAttrib(n);
- if (pair && pair[0] == 'author') {
- return pair[1];
- }
- return null;
- }
-
- function updateCoarseChangesets(onlyIfPresent) {
- // this is fast to run if the coarse changesets
- // are up-to-date or almost up-to-date;
- // if there's no coarse changeset data,
- // it may take a while.
-
- if (! meta.coarseHeads) {
- if (onlyIfPresent) {
- return;
- }
- else {
- meta.coarseHeads = {10:-1, 100:-1, 1000:-1};
- }
- }
- var head = meta.head;
- // once we reach head==9, coarseHeads[10] moves
- // from -1 up to 0; at head==19 it moves up to 1
- var desiredCoarseHeads = {
- 10: Math.floor((head-9)/10),
- 100: Math.floor((head-99)/100),
- 1000: Math.floor((head-999)/1000)
- };
- var revs = _getPadStringArray(padId, "revs");
- var revs10 = _getPadStringArray(padId, "revs10");
- var revs100 = _getPadStringArray(padId, "revs100");
- var revs1000 = _getPadStringArray(padId, "revs1000");
- var fineArrays = [revs, revs10, revs100];
- var coarseArrays = [revs10, revs100, revs1000];
- var levels = [10, 100, 1000];
- var dirty = false;
- for(var z=0;z<3;z++) {
- var level = levels[z];
- var coarseArray = coarseArrays[z];
- var fineArray = fineArrays[z];
- while (meta.coarseHeads[level] < desiredCoarseHeads[level]) {
- dirty = true;
- // for example, if the current coarse head is -1,
- // compose 0-9 inclusive of the finer level and call it 0
- var x = meta.coarseHeads[level] + 1;
- var cs = fineArray.getEntry(10 * x);
- for(var i=1;i<=9;i++) {
- cs = Changeset.compose(cs, fineArray.getEntry(10*x + i),
- pad.pool());
- }
- coarseArray.setEntry(x, cs);
- meta.coarseHeads[level] = x;
- }
- }
- if (dirty) {
- meta.status.dirty = true;
- }
- }
-
- /////////////////// "Public" API starts here (functions used by collab_server or other modules)
- var pad = {
- // Operations that write to the data structure should
- // set meta.dirty = true. Any pad access that isn't
- // done in "read" mode also sets dirty = true.
- getId: function() { return padId; },
- exists: function() { return !!meta; },
- create: function(optText) {
- meta = {};
- meta.head = -1; // incremented below by addRevision
- pad.tempObj().atext = Changeset.makeAText("\n");
- meta.padId = padId,
- meta.keyRevInterval = 100;
- meta.numChatMessages = 0;
- var t = +new Date();
- meta.status = { validated: true };
- meta.status.lastAccess = t;
- meta.status.dirty = true;
- meta.supportsTimeSlider = true;
-
- var firstChangeset = Changeset.makeSplice("\n", 0, 0,
- cleanText(optText || ''));
- addRevision(firstChangeset, '');
-
- _insertPadMetaData(padId, meta);
-
- sqlobj.insert("PAD_SQLMETA", {
- id: padId, version: 2, creationTime: new Date(t), lastWriteTime: new Date(),
- headRev: meta.head }); // headRev is not authoritative, just for info
-
- padevents.onNewPad(pad);
- },
- destroy: function() { // you may want to collab_server.bootAllUsers first
- padevents.onDestroyPad(pad);
-
- _destroyPadStringArray(padId, "revs");
- _destroyPadStringArray(padId, "revs10");
- _destroyPadStringArray(padId, "revs100");
- _destroyPadStringArray(padId, "revs1000");
- _destroyPadStringArray(padId, "revmeta");
- _destroyPadStringArray(padId, "chat");
- _destroyPadStringArray(padId, "authors");
- _removePadMetaData(padId);
- _removePadAPool(padId);
- sqlobj.deleteRows("PAD_SQLMETA", { id: padId });
- meta = null;
- },
- writeToDB: function() {
- var meta2 = {};
- for(var k in meta) meta2[k] = meta[k];
- delete meta2.status;
- sqlbase.putJSON("PAD_META", padId, meta2);
-
- _getPadStringArray(padId, "revs").writeToDB();
- _getPadStringArray(padId, "revs10").writeToDB();
- _getPadStringArray(padId, "revs100").writeToDB();
- _getPadStringArray(padId, "revs1000").writeToDB();
- _getPadStringArray(padId, "revmeta").writeToDB();
- _getPadStringArray(padId, "chat").writeToDB();
- _getPadStringArray(padId, "authors").writeToDB();
- sqlbase.putJSON("PAD_APOOL", padId, pad.pool().toJsonable());
-
- var props = { headRev: meta.head, lastWriteTime: new Date() };
- _writePadSqlMeta(padId, props);
- },
- pool: function() {
- return _getPadAPool(padId);
- },
- getHeadRevisionNumber: function() { return meta.head; },
- getRevisionAuthor: function(r) {
- var n = _getPadStringArray(padId, "revmeta").getJSONEntry(r).a;
- return getAuthorForNum(Number(n));
- },
- getRevisionChangeset: function(r) {
- return _getPadStringArray(padId, "revs").getEntry(r);
- },
- tempObj: function() { return _getPadTemp(padId); },
- getKeyRevisionNumber: function(r) {
- return Math.floor(r / meta.keyRevInterval) * meta.keyRevInterval;
- },
- getInternalRevisionAText: function(r) {
- var cacheKey = "atext/C/"+r+"/"+padId;
- var modelCache = _getModelCache();
- var cachedValue = modelCache.get(cacheKey);
- if (cachedValue) {
- modelCache.touch(cacheKey);
- //java.lang.System.out.println("HIT! "+cacheKey);
- return Changeset.cloneAText(cachedValue);
- }
- //java.lang.System.out.println("MISS! "+cacheKey);
-
- var revs = _getPadStringArray(padId, "revs");
- var keyRev = pad.getKeyRevisionNumber(r);
- var revmeta = _getPadStringArray(padId, "revmeta");
- var atext = revmeta.getJSONEntry(keyRev).atext;
- var curRev = keyRev;
- var targetRev = r;
- var apool = pad.pool();
- while (curRev < targetRev) {
- curRev++;
- var cs = pad.getRevisionChangeset(curRev);
- atext = Changeset.applyToAText(cs, atext, apool);
- }
- modelCache.put(cacheKey, Changeset.cloneAText(atext));
- return atext;
- },
- getInternalRevisionText: function(r, optInfoObj) {
- var atext = pad.getInternalRevisionAText(r);
- var text = atext.text;
- if (optInfoObj) {
- if (text.slice(-1) != "\n") {
- optInfoObj.badLastChar = text.slice(-1);
- }
- }
- return text;
- },
- getRevisionText: function(r, optInfoObj) {
- var internalText = pad.getInternalRevisionText(r, optInfoObj);
- return internalText.slice(0, -1);
- },
- atext: function() { return Changeset.cloneAText(getCurrentAText()); },
- text: function() { return pad.atext().text; },
- getRevisionDate: function(r) {
- var revmeta = _getPadStringArray(padId, "revmeta");
- return new Date(revmeta.getJSONEntry(r).t);
- },
- // note: calls like appendRevision will NOT notify clients of the change!
- // you must go through collab_server.
- // Also, be sure to run cleanText() on any text to strip out carriage returns
- // and other stuff.
- appendRevision: function(theChangeset, author, optDatestamp) {
- addRevision(theChangeset, author || '', optDatestamp);
- },
- appendChatMessage: function(obj) {
- var index = meta.numChatMessages;
- meta.numChatMessages++;
- var chat = _getPadStringArray(padId, "chat");
- chat.setJSONEntry(index, obj);
- },
- getNumChatMessages: function() {
- return meta.numChatMessages;
- },
- getChatMessage: function(i) {
- var chat = _getPadStringArray(padId, "chat");
- return chat.getJSONEntry(i);
- },
- getPadOptionsObj: function() {
- var data = pad.getDataRoot();
- if (! data.padOptions) {
- data.padOptions = {};
- }
- if ((! data.padOptions.guestPolicy) ||
- (data.padOptions.guestPolicy == 'ask')) {
- data.padOptions.guestPolicy = 'deny';
- }
- return data.padOptions;
- },
- getGuestPolicy: function() {
- // allow/ask/deny
- return pad.getPadOptionsObj().guestPolicy;
- },
- setGuestPolicy: function(policy) {
- pad.getPadOptionsObj().guestPolicy = policy;
- },
- getDataRoot: function() {
- var dataRoot = meta.dataRoot;
- if (! dataRoot) {
- dataRoot = {};
- meta.dataRoot = dataRoot;
- }
- return dataRoot;
- },
- // returns an object, changes to which are not reflected
- // in the DB; use setAuthorData for mutation
- getAuthorData: function(author) {
- var authors = _getPadStringArray(padId, "authors");
- var n = getNumForAuthor(author, true);
- if (n < 0) {
- return null;
- }
- else {
- return authors.getJSONEntry(n);
- }
- },
- setAuthorData: function(author, data) {
- var authors = _getPadStringArray(padId, "authors");
- var n = getNumForAuthor(author);
- authors.setJSONEntry(n, data);
- },
- adoptChangesetAttribs: function(cs, oldPool) {
- return Changeset.moveOpsToNewPool(cs, oldPool, pad.pool());
- },
- eachATextAuthor: function(atext, func) {
- var seenNums = {};
- Changeset.eachAttribNumber(atext.attribs, function(n) {
- if (! seenNums[n]) {
- seenNums[n] = true;
- var author = getAuthorForNum(n);
- if (author) {
- func(author, n);
- }
- }
- });
- },
- getCoarseChangeset: function(start, numChangesets) {
- updateCoarseChangesets();
-
- if (!(numChangesets == 10 || numChangesets == 100 ||
- numChangesets == 1000)) {
- return null;
- }
- var level = numChangesets;
- var x = Math.floor(start / level);
- if (!(x >= 0 && x*level == start)) {
- return null;
- }
-
- var cs = _getPadStringArray(padId, "revs"+level).getEntry(x);
-
- if (! cs) {
- return null;
- }
-
- return cs;
- },
- getSupportsTimeSlider: function() {
- if (! ('supportsTimeSlider' in meta)) {
- if (padutils.isProPadId(padId)) {
- return true;
- }
- else {
- return false;
- }
- }
- else {
- return !! meta.supportsTimeSlider;
- }
- },
- setSupportsTimeSlider: function(v) {
- meta.supportsTimeSlider = v;
- },
- get _meta() { return meta; }
- };
-
- try {
- padutils.setCurrentPad(padId);
- appjet.requestCache.padsAccessing[padId] = pad;
- return padFunc(pad);
- }
- finally {
- padutils.clearCurrentPad();
- delete appjet.requestCache.padsAccessing[padId];
- if (meta) {
- if (mode != "r") {
- meta.status.dirty = true;
- }
- if (meta.status.dirty) {
- dbwriter.notifyPadDirty(padId);
- }
- }
- }
- });
- });
-}
-
-/**
- * Call an arbitrary function with no arguments inside an exclusive
- * lock on a padId, and return the result.
- */
-function doWithPadLock(padId, func) {
- var lockName = "document/"+padId;
- return sync.doWithStringLock(lockName, func);
-}
-
-function isPadLockHeld(padId) {
- var lockName = "document/"+padId;
- return GlobalSynchronizer.isHeld(lockName);
-}
-
-/**
- * Get pad meta-data object, which is stored in SQL as JSON
- * but cached in appjet.cache. Returns null if pad doesn't
- * exist at all (does NOT create it). Requires pad lock.
- */
-function _getPadMetaData(padId) {
- var padMeta = appjet.cache.pads.meta.get(padId);
- if (! padMeta) {
- // not in cache
- padMeta = sqlbase.getJSON("PAD_META", padId);
- if (! padMeta) {
- // not in SQL
- padMeta = null;
- }
- else {
- appjet.cache.pads.meta.put(padId, padMeta);
- }
- }
- return padMeta;
-}
-
-/**
- * Sets a pad's meta-data object, such as when creating
- * a pad for the first time. Requires pad lock.
- */
-function _insertPadMetaData(padId, obj) {
- appjet.cache.pads.meta.put(padId, obj);
-}
-
-/**
- * Removes a pad's meta data, writing through to the database.
- * Used for the rare case of deleting a pad.
- */
-function _removePadMetaData(padId) {
- appjet.cache.pads.meta.remove(padId);
- sqlbase.deleteJSON("PAD_META", padId);
-}
-
-function _getPadAPool(padId) {
- var padAPool = appjet.cache.pads.apool.get(padId);
- if (! padAPool) {
- // not in cache
- padAPool = new AttribPool();
- padAPoolJson = sqlbase.getJSON("PAD_APOOL", padId);
- if (padAPoolJson) {
- // in SQL
- padAPool.fromJsonable(padAPoolJson);
- }
- appjet.cache.pads.apool.put(padId, padAPool);
- }
- return padAPool;
-}
-
-/**
- * Removes a pad's apool data, writing through to the database.
- * Used for the rare case of deleting a pad.
- */
-function _removePadAPool(padId) {
- appjet.cache.pads.apool.remove(padId);
- sqlbase.deleteJSON("PAD_APOOL", padId);
-}
-
-/**
- * Get an object for a pad that's not persisted in storage,
- * e.g. for tracking open connections. Creates object
- * if necessary. Requires pad lock.
- */
-function _getPadTemp(padId) {
- var padTemp = appjet.cache.pads.temp.get(padId);
- if (! padTemp) {
- padTemp = {};
- appjet.cache.pads.temp.put(padId, padTemp);
- }
- return padTemp;
-}
-
-/**
- * Returns an object with methods for manipulating a string array, where name
- * is something like "revs" or "chat". The object must be acquired and used
- * all within a pad lock.
- */
-function _getPadStringArray(padId, name) {
- var padFoo = appjet.cache.pads[name].get(padId);
- if (! padFoo) {
- padFoo = {};
- // writes go into writeCache, which is authoritative for reads;
- // reads cause pages to be read into readCache
- padFoo.readCache = {};
- padFoo.writeCache = {};
- appjet.cache.pads[name].put(padId, padFoo);
- }
- var tableName = "PAD_"+name.toUpperCase();
- var self = {
- getEntry: function(idx) {
- var n = Number(idx);
- if (padFoo.writeCache[n]) return padFoo.writeCache[n];
- if (padFoo.readCache[n]) return padFoo.readCache[n];
- sqlbase.getPageStringArrayElements(tableName, padId, n, padFoo.readCache);
- return padFoo.readCache[n]; // null if not present in SQL
- },
- setEntry: function(idx, value) {
- var n = Number(idx);
- var v = String(value);
- padFoo.writeCache[n] = v;
- },
- getJSONEntry: function(idx) {
- var result = self.getEntry(idx);
- if (! result) return result;
- return fastJSON.parse(String(result));
- },
- setJSONEntry: function(idx, valueObj) {
- self.setEntry(idx, fastJSON.stringify(valueObj));
- },
- writeToDB: function() {
- sqlbase.putDictStringArrayElements(tableName, padId, padFoo.writeCache);
- // copy key-vals of writeCache into readCache
- var readCache = padFoo.readCache;
- var writeCache = padFoo.writeCache;
- for(var p in writeCache) {
- readCache[p] = writeCache[p];
- }
- padFoo.writeCache = {};
- }
- };
- return self;
-}
-
-/**
- * Destroy a string array; writes through to the database. Must be
- * called within a pad lock.
- */
-function _destroyPadStringArray(padId, name) {
- appjet.cache.pads[name].remove(padId);
- var tableName = "PAD_"+name.toUpperCase();
- sqlbase.clearStringArray(tableName, padId);
-}
-
-/**
- * SELECT the row of PAD_SQLMETA for the given pad. Requires pad lock.
- */
-function _getPadSqlMeta(padId) {
- return sqlobj.selectSingle("PAD_SQLMETA", { id: padId });
-}
-
-function _writePadSqlMeta(padId, updates) {
- sqlobj.update("PAD_SQLMETA", { id: padId }, updates);
-}
-
-
-// called from dbwriter
-function removeFromMemory(pad) {
- // safe to call if all data is written to SQL, otherwise will lose data;
- var padId = pad.getId();
- appjet.cache.pads.meta.remove(padId);
- appjet.cache.pads.revs.remove(padId);
- appjet.cache.pads.revs10.remove(padId);
- appjet.cache.pads.revs100.remove(padId);
- appjet.cache.pads.revs1000.remove(padId);
- appjet.cache.pads.chat.remove(padId);
- appjet.cache.pads.revmeta.remove(padId);
- appjet.cache.pads.apool.remove(padId);
- collab_server.removeFromMemory(pad);
-}
-
-