<commit_before>/**
 * 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("comet");
import("ejs");
import("collab.ace.easysync2.{AttribPool,Changeset}");
//import("etherpad.log");
import("pad.activepads");
import("pad.model");
//import("etherpad.pad.padusers");
import("pad.pad_events");
//import("etherpad.pad.pad_security");
//import("etherpad.pro.pro_padmeta");
import("fastJSON");
import("fileutils.readFile");
import("jsutils.{eachProperty,keys}");
import("cache_utils.syncedWithCache");
import("collab.collabroom_server.*");

import("editor.workspace");

jimport("java.util.concurrent.ConcurrentHashMap");
jimport("java.lang.System");

var PADPAGE_ROOMTYPE = "padpage";
var EXTENDED = "collab_server_extended_handlers";

function onStartup() {
  appjet.cache[EXTENDED] = {};
}

function willShutdown() {
  getAllRoomsOfType(PADPAGE_ROOMTYPE).forEach(function(roomName) {
    getRoomConnections(roomName).forEach(function(connection) {
      bootConnection(connection.connectionId, "shutdown");
    });
  });
}

function _padIdToRoom(padId) {
  return "padpage/"+padId;
}

function _roomToPadId(roomName) {
  return roomName.substring(roomName.indexOf("/")+1);
}

function removeFromMemory(pad) {
  // notification so we can free stuff
  if (getNumConnections(pad) == 0) {
    var tempObj = pad.tempObj();
    tempObj.revisionSockets = {};
  }
}

function _getPadConnections(pad) {
  return getRoomConnections(_padIdToRoom(pad.getId()));
}

function guestKnock(globalPadId, guestId, displayName) {
  var askedSomeone = false;

  // requires that we somehow have permission on this pad
  model.accessPadGlobal(globalPadId, function(pad) {
    var connections = _getPadConnections(pad);
    connections.forEach(function(connection) {
      // only send to pro users
      if (! padusers.isGuest(connection.data.userInfo.userId)) {
        askedSomeone = true;
        var msg = { type: "SERVER_MESSAGE",
                    payload: { type: 'GUEST_PROMPT',
                               userId: guestId,
                               displayName: displayName } };
        sendMessage(connection.connectionId, msg);
      }
    });
  });

  if (! askedSomeone) {
    pad_security.answerKnock(guestId, globalPadId, "denied");
  }
}

function _verifyUserId(userId) {
  return true; // XXX or maybe not?
  //var result;
  //if (padusers.isGuest(userId)) {
  //  // allow cookie-verified guest even if user has signed in
  //  result = (userId == padusers.getGuestUserId());
  //}
  //else {
  //  result = (userId == padusers.getUserId());
  //}
  //return result;
}

function _checkChangesetAndPool(cs, pool) {
  Changeset.checkRep(cs);
  Changeset.eachAttribNumber(cs, function(n) {
    if (! pool.getAttrib(n)) {
      throw new Error("Attribute pool is missing attribute "+n+" for changeset "+cs);
    }
  });
}

function _doWarn(str) {
  System.err.println(appjet.executionId+": "+str);
}

function _doInfo(str) {
  log.info(appjet.executionId+": "+str);
}

function _getPadRevisionSockets(pad) {
  var revisionSockets = pad.tempObj().revisionSockets;
  if (! revisionSockets) {
    revisionSockets = {}; // rev# -> socket id
    pad.tempObj().revisionSockets = revisionSockets;
  }
  return revisionSockets;
}

function applyUserChanges(pad, baseRev, changeset, optSocketId, optAuthor) {
  // changeset must be already adapted to the server's apool

  var apool = pad.pool();
  var r = baseRev;
  while (r < pad.getHeadRevisionNumber()) {
    r++;
    var c = pad.getRevisionChangeset(r);
    changeset = Changeset.follow(c, changeset, false, apool);
  }

  var prevText = pad.text();
  if (Changeset.oldLen(changeset) != prevText.length) {
    _doWarn("Can't apply USER_CHANGES "+changeset+" to document of length "+
            prevText.length);
    return;
  }

  var thisAuthor = '';
  if (optSocketId) {
    var connectionId = getSocketConnectionId(optSocketId);
    if (connectionId) {
      var connection = getConnection(connectionId);
      if (connection) {
        thisAuthor = connection.data.userInfo.userId;
      }
    }
  }
  if (optAuthor) {
    thisAuthor = optAuthor;
  }
  
  if (pad._meta.pdlinked && ( ! workspace.isChangesetAllowed(pad.getId(), changeset, thisAuthor))) {
    if (optSocketId) {
      var connectionId = getSocketConnectionId(optSocketId);
      if (connectionId) {
        sendMessage(connectionId, {
          type: "REJECT_COMMIT",
          newRev: pad.getHeadRevisionNumber()
        });
      }
    }
    return;
  }

  pad.appendRevision(changeset, thisAuthor);
  var newRev = pad.getHeadRevisionNumber();
  if (optSocketId) {
    _getPadRevisionSockets(pad)[newRev] = optSocketId;
  }

  var correctionChangeset = _correctMarkersInPad(pad.atext(), pad.pool());
  if (correctionChangeset) {
    pad.appendRevision(correctionChangeset);
  }

  ///// make document end in blank line if it doesn't:
  if (pad.text().lastIndexOf("\n\n") != pad.text().length-2) {
    var nlChangeset = Changeset.makeSplice(
      pad.text(), pad.text().length-1, 0, "\n");
    pad.appendRevision(nlChangeset, thisAuthor); // XXX should be null-authored
  }

  updatePadClients(pad);

  activepads.touch(pad.getId());
  pad_events.onEditPad(pad, thisAuthor);
}

function _getSelectionCache(pad) {
  return syncedWithCache('collab_server.selectioncache', function(cache) {
    if (! cache.map) {<commit_msg>Fix possible source of revisions with no author<commit_after>/**
 * 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("comet");
import("ejs");
import("collab.ace.easysync2.{AttribPool,Changeset}");
//import("etherpad.log");
import("pad.activepads");
import("pad.model");
//import("etherpad.pad.padusers");
import("pad.pad_events");
//import("etherpad.pad.pad_security");
//import("etherpad.pro.pro_padmeta");
import("fastJSON");
import("fileutils.readFile");
import("jsutils.{eachProperty,keys}");
import("cache_utils.syncedWithCache");
import("collab.collabroom_server.*");

import("editor.workspace");

jimport("java.util.concurrent.ConcurrentHashMap");
jimport("java.lang.System");

var PADPAGE_ROOMTYPE = "padpage";
var EXTENDED = "collab_server_extended_handlers";

function onStartup() {
  appjet.cache[EXTENDED] = {};
}

function willShutdown() {
  getAllRoomsOfType(PADPAGE_ROOMTYPE).forEach(function(roomName) {
    getRoomConnections(roomName).forEach(function(connection) {
      bootConnection(connection.connectionId, "shutdown");
    });
  });
}

function _padIdToRoom(padId) {
  return "padpage/"+padId;
}

function _roomToPadId(roomName) {
  return roomName.substring(roomName.indexOf("/")+1);
}

function removeFromMemory(pad) {
  // notification so we can free stuff
  if (getNumConnections(pad) == 0) {
    var tempObj = pad.tempObj();
    tempObj.revisionSockets = {};
  }
}

function _getPadConnections(pad) {
  return getRoomConnections(_padIdToRoom(pad.getId()));
}

function guestKnock(globalPadId, guestId, displayName) {
  var askedSomeone = false;

  // requires that we somehow have permission on this pad
  model.accessPadGlobal(globalPadId, function(pad) {
    var connections = _getPadConnections(pad);
    connections.forEach(function(connection) {
      // only send to pro users
      if (! padusers.isGuest(connection.data.userInfo.userId)) {
        askedSomeone = true;
        var msg = { type: "SERVER_MESSAGE",
                    payload: { type: 'GUEST_PROMPT',
                               userId: guestId,
                               displayName: displayName } };
        sendMessage(connection.connectionId, msg);
      }
    });
  });

  if (! askedSomeone) {
    pad_security.answerKnock(guestId, globalPadId, "denied");
  }
}

function _verifyUserId(userId) {
  return true; // XXX or maybe not?
  //var result;
  //if (padusers.isGuest(userId)) {
  //  // allow cookie-verified guest even if user has signed in
  //  result = (userId == padusers.getGuestUserId());
  //}
  //else {
  //  result = (userId == padusers.getUserId());
  //}
  //return result;
}

function _checkChangesetAndPool(cs, pool) {
  Changeset.checkRep(cs);
  Changeset.eachAttribNumber(cs, function(n) {
    if (! pool.getAttrib(n)) {
      throw new Error("Attribute pool is missing attribute "+n+" for changeset "+cs);
    }
  });
}

function _doWarn(str) {
  System.err.println(appjet.executionId+": "+str);
}

function _doInfo(str) {
  log.info(appjet.executionId+": "+str);
}

function _getPadRevisionSockets(pad) {
  var revisionSockets = pad.tempObj().revisionSockets;
  if (! revisionSockets) {
    revisionSockets = {}; // rev# -> socket id
    pad.tempObj().revisionSockets = revisionSockets;
  }
  return revisionSockets;
}

function applyUserChanges(pad, baseRev, changeset, optSocketId, optAuthor) {
  // changeset must be already adapted to the server's apool

  var apool = pad.pool();
  var r = baseRev;
  while (r < pad.getHeadRevisionNumber()) {
    r++;
    var c = pad.getRevisionChangeset(r);
    changeset = Changeset.follow(c, changeset, false, apool);
  }

  var prevText = pad.text();
  if (Changeset.oldLen(changeset) != prevText.length) {
    _doWarn("Can't apply USER_CHANGES "+changeset+" to document of length "+
            prevText.length);
    return;
  }

  var thisAuthor = '';
  if (optSocketId) {
    var connectionId = getSocketConnectionId(optSocketId);
    if (connectionId) {
      var connection = getConnection(connectionId);
      if (connection) {
        thisAuthor = connection.data.userInfo.userId;
      }
    }
  }
  if (optAuthor) {
    thisAuthor = optAuthor;
  }
  
  if (pad._meta.pdlinked && ( ! workspace.isChangesetAllowed(pad.getId(), changeset, thisAuthor))) {
    if (optSocketId) {
      var connectionId = getSocketConnectionId(optSocketId);
      if (connectionId) {
        sendMessage(connectionId, {
          type: "REJECT_COMMIT",
          newRev: pad.getHeadRevisionNumber()
        });
      }
    }
    return;
  }

  pad.appendRevision(changeset, thisAuthor);
  var newRev = pad.getHeadRevisionNumber();
  if (optSocketId) {
    _getPadRevisionSockets(pad)[newRev] = optSocketId;
  }

  var correctionChangeset = _correctMarkersInPad(pad.atext(), pad.pool());
  if (correctionChangeset) {
    pad.appendRevision(correctionChangeset, thisAuthor); // XXX should be null-authored
  }

  ///// make document end in blank line if it doesn't:
  if (pad.text().lastIndexOf("\n\n") != pad.text().length-2) {
    var nlChangeset = Changeset.makeSplice(
      pad.text(), pad.text().length-1, 0, "\n");
    pad.appendRevision(nlChangeset, thisAuthor); // XXX should be null-authored
  }

  updatePadClients(pad);

  activepads.touch(pad.getId());
  pad_events.onEditPad(pad, thisAuthor);
}

function _getSelectionCache(pad) {
  return syncedWithCache('collab_server.selectioncache', function(cache) {
    if (! cache.map) {
{'fraction_non_alphanumeric': 0.13503249219343405, 'fraction_numerical': 0.0023630686133850956, 'mean_word_length': 3.6893549663632768, 'pattern_counts': {'":': 4, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2823157', 'n_tokens_mistral': 3795, 'n_tokens_neox': 3744, 'n_words': 816}