DocumentServer/NodeJsProjects/CoAuthoring/sources/DocsCoServer.js
2015-04-28 17:59:00 +03:00

1488 lines
57 KiB
JavaScript

/*
* (c) Copyright Ascensio System SIA 2010-2015
*
* This program is a free software product. You can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License (AGPL)
* version 3 as published by the Free Software Foundation. In accordance with
* Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
* that Ascensio System SIA expressly excludes the warranty of non-infringement
* of any third-party rights.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
* details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
*
* You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia,
* EU, LV-1021.
*
* The interactive user interfaces in modified source and object code versions
* of the Program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU AGPL version 3.
*
* Pursuant to Section 7(b) of the License you must retain the original Product
* logo when distributing the program. Pursuant to Section 7(e) we decline to
* grant you any rights under trademark law for use of our trademarks.
*
* All the Product's GUI elements, including illustrations and icon sets, as
* well as technical writing content are licensed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International. See the License
* terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
*
*/
var sockjs = require("sockjs"),
_ = require("underscore"),
https = require("https"),
http = require("http"),
url = require("url"),
logger = require("./../../Common/sources/logger"),
config = require("./config.json"),
sqlBase = require("./baseConnector");
var defaultHttpPort = 80,
defaultHttpsPort = 443;
var messages = {},
connections = [],
objServiceInfo = {},
objServicePucker = {},
arrCacheDocumentsChanges = [],
nCacheSize = 100;
var asc_coAuthV = "3.0.8";
function DocumentChanges(docId) {
this.docId = docId;
this.arrChanges = [];
return this;
}
DocumentChanges.prototype.getLength = function () {
return this.arrChanges.length;
};
DocumentChanges.prototype.push = function (change) {
this.arrChanges.push(change);
};
DocumentChanges.prototype.splice = function (start, deleteCount) {
this.arrChanges.splice(start, deleteCount);
};
DocumentChanges.prototype.concat = function (item) {
this.arrChanges = this.arrChanges.concat(item);
};
var c_oAscServerStatus = {
NotFound: 0,
Editing: 1,
MustSave: 2,
Corrupted: 3,
Closed: 4
};
var c_oAscChangeBase = {
No: 0,
Delete: 1,
All: 2
};
var c_oAscServerCommandErrors = {
NoError: 0,
DocumentIdError: 1,
ParseError: 2,
CommandError: 3
};
var c_oAscSaveTimeOutDelay = 5000;
var c_oAscLockTimeOutDelay = 500;
var c_oAscRecalcIndexTypes = {
RecalcIndexAdd: 1,
RecalcIndexRemove: 2
};
var FileStatus = {
None: 0,
Ok: 1,
WaitQueue: 2,
NeedParams: 3,
Convert: 4,
Err: 5,
ErrToReload: 6,
SaveVersion: 7,
UpdateVersion: 8
};
var c_oAscLockTypes = {
kLockTypeNone: 1,
kLockTypeMine: 2,
kLockTypeOther: 3,
kLockTypeOther2: 4,
kLockTypeOther3: 5
};
var c_oAscLockTypeElem = {
Range: 1,
Object: 2,
Sheet: 3
};
var c_oAscLockTypeElemSubType = {
DeleteColumns: 1,
InsertColumns: 2,
DeleteRows: 3,
InsertRows: 4,
ChangeProperties: 5
};
var c_oAscLockTypeElemPresentation = {
Object: 1,
Slide: 2,
Presentation: 3
};
function CRecalcIndexElement(recalcType, position, bIsSaveIndex) {
if (! (this instanceof CRecalcIndexElement)) {
return new CRecalcIndexElement(recalcType, position, bIsSaveIndex);
}
this._recalcType = recalcType;
this._position = position;
this._count = 1;
this.m_bIsSaveIndex = !!bIsSaveIndex;
return this;
}
CRecalcIndexElement.prototype = {
constructor: CRecalcIndexElement,
getLockOther: function (position, type) {
var inc = (c_oAscRecalcIndexTypes.RecalcIndexAdd === this._recalcType) ? +1 : -1;
if (position === this._position && c_oAscRecalcIndexTypes.RecalcIndexRemove === this._recalcType && true === this.m_bIsSaveIndex) {
return null;
} else {
if (position === this._position && c_oAscRecalcIndexTypes.RecalcIndexRemove === this._recalcType && c_oAscLockTypes.kLockTypeMine === type && false === this.m_bIsSaveIndex) {
return null;
} else {
if (position < this._position) {
return position;
} else {
return (position + inc);
}
}
}
},
getLockSaveOther: function (position, type) {
if (this.m_bIsSaveIndex) {
return position;
}
var inc = (c_oAscRecalcIndexTypes.RecalcIndexAdd === this._recalcType) ? +1 : -1;
if (position === this._position && c_oAscRecalcIndexTypes.RecalcIndexRemove === this._recalcType && true === this.m_bIsSaveIndex) {
return null;
} else {
if (position === this._position && c_oAscRecalcIndexTypes.RecalcIndexRemove === this._recalcType && c_oAscLockTypes.kLockTypeMine === type && false === this.m_bIsSaveIndex) {
return null;
} else {
if (position < this._position) {
return position;
} else {
return (position + inc);
}
}
}
},
getLockMe: function (position) {
var inc = (c_oAscRecalcIndexTypes.RecalcIndexAdd === this._recalcType) ? -1 : +1;
if (position < this._position) {
return position;
} else {
return (position + inc);
}
},
getLockMe2: function (position) {
var inc = (c_oAscRecalcIndexTypes.RecalcIndexAdd === this._recalcType) ? -1 : +1;
if (true !== this.m_bIsSaveIndex || position < this._position) {
return position;
} else {
return (position + inc);
}
}
};
function CRecalcIndex() {
if (! (this instanceof CRecalcIndex)) {
return new CRecalcIndex();
}
this._arrElements = [];
return this;
}
CRecalcIndex.prototype = {
constructor: CRecalcIndex,
add: function (recalcType, position, count, bIsSaveIndex) {
for (var i = 0; i < count; ++i) {
this._arrElements.push(new CRecalcIndexElement(recalcType, position, bIsSaveIndex));
}
},
clear: function () {
this._arrElements.length = 0;
},
getLockOther: function (position, type) {
var newPosition = position;
var count = this._arrElements.length;
for (var i = 0; i < count; ++i) {
newPosition = this._arrElements[i].getLockOther(newPosition, type);
if (null === newPosition) {
break;
}
}
return newPosition;
},
getLockSaveOther: function (position, type) {
var newPosition = position;
var count = this._arrElements.length;
for (var i = 0; i < count; ++i) {
newPosition = this._arrElements[i].getLockSaveOther(newPosition, type);
if (null === newPosition) {
break;
}
}
return newPosition;
},
getLockMe: function (position) {
var newPosition = position;
var count = this._arrElements.length;
for (var i = count - 1; i >= 0; --i) {
newPosition = this._arrElements[i].getLockMe(newPosition);
if (null === newPosition) {
break;
}
}
return newPosition;
},
getLockMe2: function (position) {
var newPosition = position;
var count = this._arrElements.length;
for (var i = count - 1; i >= 0; --i) {
newPosition = this._arrElements[i].getLockMe2(newPosition);
if (null === newPosition) {
break;
}
}
return newPosition;
}
};
function sendData(conn, data) {
conn.write(JSON.stringify(data));
}
function getOriginalParticipantsId(docId) {
var result = [],
tmpObject = {},
elConnection;
for (var i = 0, length = connections.length; i < length; ++i) {
elConnection = connections[i].connection;
if (elConnection.docId === docId && false === elConnection.isViewer) {
tmpObject[elConnection.user.idOriginal] = 1;
}
}
for (var name in tmpObject) {
if (tmpObject.hasOwnProperty(name)) {
result.push(name);
}
}
return result;
}
function sendServerRequest(server, postData, onReplyCallback) {
if (!server.host || !server.path) {
return;
}
var options = {
host: server.host,
path: server.path,
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Length": postData.length
},
rejectUnauthorized: false
};
if (server.port) {
options.port = server.port;
}
var requestFunction = server.https ? https.request : http.request;
logger.info("postData: %s", postData);
var req = requestFunction(options, function (res) {
res.setEncoding("utf8");
res.on("data", function (replyData) {
logger.info("replyData: %s", replyData);
if (onReplyCallback) {
onReplyCallback(replyData);
}
});
res.on("end", function () {
logger.info("end");
});
});
req.on("error", function (e) {
logger.warn("problem with request on server: %s", e.message);
});
req.write(postData);
req.end();
}
function parseUrl(callbackUrl) {
var result = null;
try {
var parseObject = url.parse(decodeURIComponent(callbackUrl));
var isHttps = "https:" === parseObject.protocol;
var port = parseObject.port;
if (!port) {
port = isHttps ? defaultHttpsPort : defaultHttpPort;
}
result = {
"https": isHttps,
"host": parseObject.hostname,
"port": port,
"path": parseObject.path,
"href": parseObject.href
};
} catch(e) {
result = null;
}
return result;
}
function deleteCallback(id) {
sqlBase.deleteCallback(id);
delete objServiceInfo[id];
}
function sendStatusDocument(docId, bChangeBase) {
var callback = objServiceInfo[docId];
if (null == callback) {
return;
}
var status = c_oAscServerStatus.Editing;
var participants = getOriginalParticipantsId(docId);
var oPucker = objServicePucker[docId];
if (0 === participants.length && !(oPucker && oPucker.inDataBase && 0 !== oPucker.index)) {
status = c_oAscServerStatus.Closed;
}
if (c_oAscChangeBase.No !== bChangeBase) {
if (c_oAscServerStatus.Editing === status && c_oAscChangeBase.All === bChangeBase) {
sqlBase.insertInTable(sqlBase.tableId.callbacks, docId, callback.href);
} else {
if (c_oAscServerStatus.Closed === status) {
deleteCallback(docId);
}
}
}
var sendData = JSON.stringify({
"key": docId,
"status": status,
"url": "",
"users": participants
});
sendServerRequest(callback, sendData, function (replyData) {
onReplySendStatusDocument(docId, replyData);
});
}
function onReplySendStatusDocument(docId, replyData) {
if (!replyData) {
return;
}
var i, oData, users;
try {
oData = JSON.parse(replyData);
} catch(e) {
logger.error("error reply SendStatusDocument: %s docId = %s", e, docId);
oData = null;
}
if (!oData) {
return;
}
users = Array.isArray(oData) ? oData : oData.users;
if (Array.isArray(users)) {
for (i = 0; i < users.length; ++i) {
dropUserFromDocument(docId, users[i], "");
}
}
}
function dropUserFromDocument(docId, userId, description) {
var elConnection;
for (var i = 0, length = connections.length; i < length; ++i) {
elConnection = connections[i].connection;
if (elConnection.docId === docId && userId === elConnection.user.idOriginal) {
sendData(elConnection, {
type: "drop",
description: description
});
}
}
}
function removeDocumentChanges(docId) {
for (var i = 0, length = arrCacheDocumentsChanges.length; i < length; ++i) {
if (docId === arrCacheDocumentsChanges[i].docId) {
arrCacheDocumentsChanges.splice(i, 1);
return;
}
}
}
function bindEvents(docId, callback) {
var bChangeBase = c_oAscChangeBase.Delete;
if (!objServiceInfo[docId]) {
var oCallbackUrl = parseUrl(callback);
if (null === oCallbackUrl) {
return c_oAscServerCommandErrors.ParseError;
}
objServiceInfo[docId] = oCallbackUrl;
bChangeBase = c_oAscChangeBase.All;
}
sendStatusDocument(docId, bChangeBase);
}
function removeChanges(id, isCorrupted, isConvertService) {
logger.info("removeChanges: %s", id);
delete messages[id];
deleteCallback(id);
removeDocumentChanges(id);
if (!isCorrupted) {
deletePucker(id);
sqlBase.deleteChanges(id, null);
} else {
sqlBase.updateStatusFile(id);
logger.error("saved corrupted id = %s convert = %s", id, isConvertService);
}
}
function deletePucker(docId) {
sqlBase.deletePucker(docId);
delete objServicePucker[docId];
}
function _createPucker(url, documentFormatSave, indexUser, inDataBase) {
var serverUrl = parseUrl(url);
if (null === serverUrl) {
logger.error("Error server url = %s", url);
return null;
}
return {
url: url,
server: serverUrl,
documentFormatSave: documentFormatSave,
inDataBase: inDataBase,
index: 0,
indexUser: indexUser
};
}
function createPucker(docId, url, documentFormatSave, indexUser, inDataBase) {
var pucker = null;
if (!objServicePucker.hasOwnProperty(docId)) {
pucker = _createPucker(url, documentFormatSave, indexUser, inDataBase);
if (null === pucker) {
return null;
}
objServicePucker[docId] = pucker;
}
return objServicePucker[docId];
}
function updatePucker(docId, indexUser) {
var pucker = objServicePucker[docId];
var nOldIndex;
if (pucker) {
nOldIndex = pucker.indexUser;
pucker.indexUser = indexUser;
if (!pucker.inDataBase) {
sqlBase.insertInTable(sqlBase.tableId.pucker, docId, pucker.url, pucker.documentFormatSave, pucker.indexUser);
pucker.inDataBase = true;
} else {
if (nOldIndex !== pucker.indexUser) {
sqlBase.updateIndexUser(docId, pucker.indexUser);
}
}
}
}
exports.version = asc_coAuthV;
exports.install = function (server, callbackFunction) {
var sockjs_opts = {
sockjs_url: "./../../Common/sources/sockjs-0.3.min.js"
},
sockjs_echo = sockjs.createServer(sockjs_opts),
locks = {},
lockDocuments = {},
arrSaveLock = {},
saveTimers = {},
urlParse = new RegExp("^/doc/([0-9-.a-zA-Z_=]*)/c.+", "i");
sockjs_echo.on("connection", function (conn) {
if (null == conn) {
logger.error("null == conn");
return;
}
conn.on("data", function (message) {
try {
var data = JSON.parse(message);
switch (data.type) {
case "auth":
auth(conn, data);
break;
case "message":
onMessage(conn, data);
break;
case "getLock":
getLock(conn, data, false);
break;
case "saveChanges":
saveChanges(conn, data);
break;
case "isSaveLock":
isSaveLock(conn, data);
break;
case "unSaveLock":
unSaveLock(conn, -1);
break;
case "getMessages":
getMessages(conn, data);
break;
case "unLockDocument":
checkEndAuthLock(data.isSave, conn.docId, conn.user.id, null, conn);
break;
}
} catch(e) {
logger.error("error receiving response: %s docId = %s type = %s", e, conn ? conn.docId : "null", (data && data.type) ? data.type : "null");
}
});
conn.on("error", function () {
logger.error("On error");
});
conn.on("close", function () {
var connection = this,
userLocks, participants, reconnected, oPucker, bHasEditors, bHasChanges;
var docId = conn.docId;
if (null == docId) {
return;
}
logger.info("Connection closed or timed out");
connections = _.reject(connections, function (el) {
return el.connection.id === connection.id;
});
reconnected = _.any(connections, function (el) {
return el.connection.sessionId === connection.sessionId;
});
var state = (false == reconnected) ? false : undefined;
participants = getParticipants(docId);
sendParticipantsState(participants, state, connection);
if (!reconnected) {
if (undefined != arrSaveLock[docId] && connection.user.id == arrSaveLock[docId].user) {
if (null != arrSaveLock[docId].saveLockTimeOutId) {
clearTimeout(arrSaveLock[docId].saveLockTimeOutId);
}
arrSaveLock[docId] = undefined;
}
oPucker = objServicePucker[docId];
bHasEditors = hasEditors(docId);
bHasChanges = oPucker && oPucker.inDataBase && 0 !== oPucker.index;
if (false === connection.isViewer) {
if (!bHasEditors) {
if (null != arrSaveLock[docId] && null != arrSaveLock[docId].saveLockTimeOutId) {
clearTimeout(arrSaveLock[docId].saveLockTimeOutId);
}
arrSaveLock[docId] = undefined;
if (bHasChanges) {
_createSaveTimer(docId);
} else {
deletePucker(docId);
sendStatusDocument(docId, c_oAscChangeBase.All);
}
} else {
sendStatusDocument(docId, c_oAscChangeBase.No);
}
userLocks = getUserLocks(docId, connection.sessionId);
if (0 < userLocks.length) {
_.each(participants, function (participant) {
if (!participant.connection.isViewer) {
sendData(participant.connection, {
type: "releaseLock",
locks: _.map(userLocks, function (e) {
return {
block: e.block,
user: e.user,
time: Date.now(),
changes: null
};
})
});
}
});
}
checkEndAuthLock(false, docId, connection.user.id, participants);
} else {
if (!bHasEditors && !bHasChanges) {
deletePucker(docId);
}
}
}
});
});
function getDocumentChangesCache(docId) {
var oPucker = objServicePucker[docId];
if (oPucker && oPucker.inDataBase) {
var i, length;
for (i = 0, length = arrCacheDocumentsChanges.length; i < length; ++i) {
if (docId === arrCacheDocumentsChanges[i].docId) {
return arrCacheDocumentsChanges[i];
}
}
}
return null;
}
function getDocumentChanges(docId, callback) {
var oPucker = objServicePucker[docId];
if (oPucker && oPucker.inDataBase) {
var i, length;
for (i = 0, length = arrCacheDocumentsChanges.length; i < length; ++i) {
if (docId === arrCacheDocumentsChanges[i].docId) {
callback(arrCacheDocumentsChanges[i].arrChanges, oPucker.index);
return;
}
}
var callbackGetChanges = function (error, arrayElements) {
if (!oPucker || error) {
return;
}
var j, element;
var objChangesDocument = new DocumentChanges(docId);
for (j = 0; j < arrayElements.length; ++j) {
element = arrayElements[j];
objChangesDocument.push({
docid: docId,
change: element["dc_data"],
time: Date.parse(element["dc_date"] + " GMT"),
user: element["dc_user_id"],
useridoriginal: element["dc_user_id_original"]
});
}
oPucker.index = objChangesDocument.getLength();
arrCacheDocumentsChanges.push(objChangesDocument);
callback(objChangesDocument.arrChanges, oPucker.index);
};
sqlBase.getChanges(docId, callbackGetChanges);
return;
}
callback(undefined, 0);
}
function getUserLocks(docId, sessionId) {
var userLocks = [],
i;
var docLock = locks[docId];
if (docLock) {
if ("array" === typeOf(docLock)) {
for (i = 0; i < docLock.length; ++i) {
if (docLock[i].sessionId === sessionId) {
userLocks.push(docLock[i]);
docLock.splice(i, 1);
--i;
}
}
} else {
for (i in docLock) {
if (docLock[i].sessionId === sessionId) {
userLocks.push(docLock[i]);
delete docLock[i];
}
}
}
}
return userLocks;
}
function checkEndAuthLock(isSave, docId, userId, participants, currentConnection) {
var result = false;
if (lockDocuments.hasOwnProperty(docId) && userId === lockDocuments[docId].id) {
delete lockDocuments[docId];
if (!participants) {
participants = getParticipants(docId);
}
var participantsMap = _.map(participants, function (conn) {
var tmpUser = conn.connection.user;
return {
id: tmpUser.id,
username: tmpUser.name,
indexUser: tmpUser.indexUser,
view: conn.connection.isViewer
};
});
getDocumentChanges(docId, function (objChangesDocument, changesIndex) {
var connection;
for (var i = 0, l = participants.length; i < l; ++i) {
connection = participants[i].connection;
if (userId !== connection.user.id && !connection.isViewer) {
sendAuthInfo(objChangesDocument, changesIndex, connection, participantsMap);
}
}
});
result = true;
} else {
if (isSave) {
var userLocks = getUserLocks(docId, currentConnection.sessionId);
if (0 < userLocks.length) {
if (!participants) {
participants = getParticipants(docId);
}
for (var i = 0, l = participants.length; i < l; ++i) {
var connection = participants[i].connection;
if (userId !== connection.user.id && !connection.isViewer) {
sendData(connection, {
type: "releaseLock",
locks: _.map(userLocks, function (e) {
return {
block: e.block,
user: e.user,
time: Date.now(),
changes: null
};
})
});
}
}
}
unSaveLock(currentConnection, -1);
}
}
return result;
}
function sendParticipantsState(participants, stateConnect, oConnection) {
var tmpUser = oConnection.user;
_.each(participants, function (participant) {
if (participant.connection.user.id !== tmpUser.id) {
sendData(participant.connection, {
type: "connectState",
state: stateConnect,
id: tmpUser.id,
username: tmpUser.name,
indexUser: tmpUser.indexUser,
view: oConnection.isViewer
});
}
});
}
function sendFileError(conn, errorId) {
logger.error("error description: %s", errorId);
sendData(conn, {
type: "error",
description: errorId
});
}
function getParticipants(docId, excludeUserId, excludeViewer) {
return _.filter(connections, function (el) {
return el.connection.docId === docId && el.connection.user.id !== excludeUserId && el.connection.isViewer !== excludeViewer;
});
}
function hasEditors(docId) {
var result = false,
elConnection;
for (var i = 0, length = connections.length; i < length; ++i) {
elConnection = connections[i].connection;
if (elConnection.docId === docId && false === elConnection.isViewer) {
result = true;
break;
}
}
return result;
}
function sendChangesToServer(docId) {
var sendData = JSON.stringify({
"id": docId,
"c": "sfc",
"url": "/CommandService.ashx?c=saved&conv=1&key=" + docId + "&status=",
"outputformat" : objServicePucker[docId].documentFormatSave,
"data": c_oAscSaveTimeOutDelay
});
sendServerRequest(objServicePucker[docId].server, sendData);
}
function _recalcLockArray(userId, _locks, oRecalcIndexColumns, oRecalcIndexRows) {
if (null == _locks) {
return;
}
var count = _locks.length;
var element = null,
oRangeOrObjectId = null;
var i;
var sheetId = -1;
for (i = 0; i < count; ++i) {
if (userId === _locks[i].user) {
continue;
}
element = _locks[i].block;
if (c_oAscLockTypeElem.Range !== element["type"] || c_oAscLockTypeElemSubType.InsertColumns === element["subType"] || c_oAscLockTypeElemSubType.InsertRows === element["subType"]) {
continue;
}
sheetId = element["sheetId"];
oRangeOrObjectId = element["rangeOrObjectId"];
if (oRecalcIndexColumns && oRecalcIndexColumns.hasOwnProperty(sheetId)) {
oRangeOrObjectId["c1"] = oRecalcIndexColumns[sheetId].getLockMe2(oRangeOrObjectId["c1"]);
oRangeOrObjectId["c2"] = oRecalcIndexColumns[sheetId].getLockMe2(oRangeOrObjectId["c2"]);
}
if (oRecalcIndexRows && oRecalcIndexRows.hasOwnProperty(sheetId)) {
oRangeOrObjectId["r1"] = oRecalcIndexRows[sheetId].getLockMe2(oRangeOrObjectId["r1"]);
oRangeOrObjectId["r2"] = oRecalcIndexRows[sheetId].getLockMe2(oRangeOrObjectId["r2"]);
}
}
}
function _addRecalcIndex(oRecalcIndex) {
if (null == oRecalcIndex) {
return null;
}
var nIndex = 0;
var nRecalcType = c_oAscRecalcIndexTypes.RecalcIndexAdd;
var oRecalcIndexElement = null;
var oRecalcIndexResult = {};
for (var sheetId in oRecalcIndex) {
if (oRecalcIndex.hasOwnProperty(sheetId)) {
if (!oRecalcIndexResult.hasOwnProperty(sheetId)) {
oRecalcIndexResult[sheetId] = new CRecalcIndex();
}
for (; nIndex < oRecalcIndex[sheetId]._arrElements.length; ++nIndex) {
oRecalcIndexElement = oRecalcIndex[sheetId]._arrElements[nIndex];
if (true === oRecalcIndexElement.m_bIsSaveIndex) {
continue;
}
nRecalcType = (c_oAscRecalcIndexTypes.RecalcIndexAdd === oRecalcIndexElement._recalcType) ? c_oAscRecalcIndexTypes.RecalcIndexRemove : c_oAscRecalcIndexTypes.RecalcIndexAdd;
oRecalcIndexResult[sheetId].add(nRecalcType, oRecalcIndexElement._position, oRecalcIndexElement._count, true);
}
}
}
return oRecalcIndexResult;
}
function compareExcelBlock(newBlock, oldBlock) {
if (null !== newBlock.subType && null !== oldBlock.subType) {
return true;
}
if ((c_oAscLockTypeElemSubType.ChangeProperties === oldBlock.subType && c_oAscLockTypeElem.Sheet !== newBlock.type) || (c_oAscLockTypeElemSubType.ChangeProperties === newBlock.subType && c_oAscLockTypeElem.Sheet !== oldBlock.type)) {
return false;
}
var resultLock = false;
if (newBlock.type === c_oAscLockTypeElem.Range) {
if (oldBlock.type === c_oAscLockTypeElem.Range) {
if (c_oAscLockTypeElemSubType.InsertRows === oldBlock.subType || c_oAscLockTypeElemSubType.InsertColumns === oldBlock.subType) {
resultLock = false;
} else {
if (isInterSection(newBlock.rangeOrObjectId, oldBlock.rangeOrObjectId)) {
resultLock = true;
}
}
} else {
if (oldBlock.type === c_oAscLockTypeElem.Sheet) {
resultLock = true;
}
}
} else {
if (newBlock.type === c_oAscLockTypeElem.Sheet) {
resultLock = true;
} else {
if (newBlock.type === c_oAscLockTypeElem.Object) {
if (oldBlock.type === c_oAscLockTypeElem.Sheet) {
resultLock = true;
} else {
if (oldBlock.type === c_oAscLockTypeElem.Object && oldBlock.rangeOrObjectId === newBlock.rangeOrObjectId) {
resultLock = true;
}
}
}
}
}
return resultLock;
}
function isInterSection(range1, range2) {
if (range2.c1 > range1.c2 || range2.c2 < range1.c1 || range2.r1 > range1.r2 || range2.r2 < range1.r1) {
return false;
}
return true;
}
function typeOf(obj) {
if (obj === undefined) {
return "undefined";
}
if (obj === null) {
return "null";
}
return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
}
function comparePresentationBlock(newBlock, oldBlock) {
var resultLock = false;
switch (newBlock.type) {
case c_oAscLockTypeElemPresentation.Presentation:
if (c_oAscLockTypeElemPresentation.Presentation === oldBlock.type) {
resultLock = newBlock.val === oldBlock.val;
}
break;
case c_oAscLockTypeElemPresentation.Slide:
if (c_oAscLockTypeElemPresentation.Slide === oldBlock.type) {
resultLock = newBlock.val === oldBlock.val;
} else {
if (c_oAscLockTypeElemPresentation.Object === oldBlock.type) {
resultLock = newBlock.val === oldBlock.slideId;
}
}
break;
case c_oAscLockTypeElemPresentation.Object:
if (c_oAscLockTypeElemPresentation.Slide === oldBlock.type) {
resultLock = newBlock.slideId === oldBlock.val;
} else {
if (c_oAscLockTypeElemPresentation.Object === oldBlock.type) {
resultLock = newBlock.objId === oldBlock.objId;
}
}
break;
}
return resultLock;
}
function auth(conn, data) {
if (data.version !== asc_coAuthV) {
sendFileError(conn, "Old Version Sdk");
return;
}
if (data.token && data.user) {
var docId;
var user = data.user;
var parsed = urlParse.exec(conn.url);
if (parsed.length > 1) {
docId = conn.docId = parsed[1];
} else {}
if (false === data.isViewer && saveTimers[docId]) {
clearTimeout(saveTimers[docId]);
}
var bIsRestore = null != data.sessionId;
var pucker = createPucker(docId, data.server, data.documentFormatSave, 0, false);
if (!pucker) {
sendFileError(conn, "pucker error");
return;
}
var curIndexUser = bIsRestore ? user.indexUser : pucker.indexUser;
var curUserId = user.id + curIndexUser;
conn.sessionState = 1;
conn.user = {
id: curUserId,
idOriginal: user.id,
name: user.name,
indexUser: curIndexUser
};
conn.isViewer = data.isViewer;
if (!conn.isViewer) {
updatePucker(docId, Math.max(pucker.indexUser, curIndexUser + 1));
}
if (bIsRestore) {
logger.info("restored old session id = %s", data.sessionId);
sqlBase.checkStatusFile(docId, function (error, result) {
if (null !== error || 0 === result.length) {
sendFileError(conn, "DataBase error");
return;
}
var status = result[0]["tr_status"];
if (FileStatus.Ok === status) {} else {
if (FileStatus.SaveVersion === status) {
sqlBase.updateStatusFile(docId);
} else {
if (FileStatus.UpdateVersion === status) {
sendFileError(conn, "Update Version error");
return;
} else {
sendFileError(conn, "Other error");
return;
}
}
}
getDocumentChanges(docId, function (objChangesDocument, changesIndex) {
var bIsSuccessRestore = true;
if (objChangesDocument && 0 < objChangesDocument.length) {
var change = objChangesDocument[objChangesDocument.length - 1];
if (change["change"]) {
if (change["user"] !== curUserId) {
bIsSuccessRestore = 0 === (((data["lastOtherSaveTime"] - change["time"]) / 1000) >> 0);
}
}
}
if (bIsSuccessRestore) {
conn.sessionId = data.sessionId;
var arrayBlocks = data["block"];
if (arrayBlocks && (0 === arrayBlocks.length || getLock(conn, data, true))) {
connections = _.reject(connections, function (el) {
return el.connection.sessionId === data.sessionId;
});
endAuth(conn, true);
} else {
sendFileError(conn, "Restore error. Locks not checked.");
}
} else {
sendFileError(conn, "Restore error. Document modified.");
}
});
});
} else {
conn.sessionId = conn.id;
endAuth(conn, false, data.documentCallbackUrl);
}
}
}
function endAuth(conn, bIsRestore, documentCallbackUrl) {
var docId = conn.docId;
connections.push({
connection: conn
});
var participants = getParticipants(docId);
var tmpConnection, tmpUser, firstParticipantNoView, participantsMap = [],
countNoView = 0;
for (var i = 0; i < participants.length; ++i) {
tmpConnection = participants[i].connection;
tmpUser = tmpConnection.user;
participantsMap.push({
id: tmpUser.id,
username: tmpUser.name,
indexUser: tmpUser.indexUser,
view: tmpConnection.isViewer
});
if (!tmpConnection.isViewer) {
++countNoView;
if (!firstParticipantNoView) {
firstParticipantNoView = participantsMap[participantsMap.length - 1];
}
}
}
if (!conn.isViewer) {
if (documentCallbackUrl) {
bindEvents(docId, documentCallbackUrl);
} else {
sendStatusDocument(docId, c_oAscChangeBase.No);
}
}
if (!bIsRestore && 2 === countNoView && !conn.isViewer) {
lockDocuments[docId] = firstParticipantNoView;
}
if (lockDocuments[docId] && !conn.isViewer) {
var sendObject = {
type: "waitAuth",
lockDocument: lockDocuments[docId]
};
sendData(conn, sendObject);
} else {
if (bIsRestore) {
sendAuthInfo(undefined, undefined, conn, participantsMap);
} else {
getDocumentChanges(docId, function (objChangesDocument, changesIndex) {
sendAuthInfo(objChangesDocument, changesIndex, conn, participantsMap);
});
}
}
sendParticipantsState(participants, true, conn);
}
function sendAuthInfo(objChangesDocument, changesIndex, conn, participantsMap) {
var docId = conn.docId;
var sendObject = {
type: "auth",
result: 1,
sessionId: conn.sessionId,
participants: participantsMap,
messages: messages[docId],
locks: locks[docId],
changes: objChangesDocument,
changesIndex: changesIndex,
indexUser: conn.user.indexUser
};
sendData(conn, sendObject);
}
function onMessage(conn, data) {
var participants = getParticipants(conn.docId),
msg = {
docid: conn.docId,
message: data.message,
time: Date.now(),
user: conn.user.id,
username: conn.user.name
};
if (!messages.hasOwnProperty(conn.docId)) {
messages[conn.docId] = [msg];
} else {
messages[conn.docId].push(msg);
}
logger.info("insert message: %s", JSON.stringify(msg));
_.each(participants, function (participant) {
sendData(participant.connection, {
type: "message",
messages: [msg]
});
});
}
function getLock(conn, data, bIsRestore) {
logger.info("getLock docid: %s", conn.docId);
var fLock = null;
switch (data["editorType"]) {
case 0:
fLock = getLockWord;
break;
case 1:
fLock = getLockExcel;
break;
case 2:
fLock = getLockPresentation;
break;
}
return fLock ? fLock(conn, data, bIsRestore) : false;
}
function getLockWord(conn, data, bIsRestore) {
var docId = conn.docId,
participants = getParticipants(docId, undefined, true),
arrayBlocks = data.block;
if (!locks.hasOwnProperty(docId)) {
locks[docId] = {};
}
var i, documentLocks = locks[docId];
if (_checkLock(documentLocks, arrayBlocks)) {
for (i = 0; i < arrayBlocks.length; ++i) {
documentLocks[arrayBlocks[i]] = {
time: Date.now(),
user: conn.user.id,
block: arrayBlocks[i],
sessionId: conn.sessionId
};
}
} else {
if (bIsRestore) {
return false;
}
}
sendGetLock(participants, documentLocks);
return true;
}
function getLockExcel(conn, data, bIsRestore) {
var docId = conn.docId,
userId = conn.user.id,
participants = getParticipants(docId, undefined, true),
arrayBlocks = data.block;
if (!locks.hasOwnProperty(docId)) {
locks[docId] = [];
}
var i, documentLocks = locks[docId];
if (_checkLockExcel(documentLocks, arrayBlocks, userId)) {
for (i = 0; i < arrayBlocks.length; ++i) {
documentLocks.push({
time: Date.now(),
user: userId,
block: arrayBlocks[i],
sessionId: conn.sessionId
});
}
} else {
if (bIsRestore) {
return false;
}
}
sendGetLock(participants, documentLocks);
return true;
}
function getLockPresentation(conn, data, bIsRestore) {
var docId = conn.docId,
userId = conn.user.id,
participants = getParticipants(docId, undefined, true),
arrayBlocks = data.block;
if (!locks.hasOwnProperty(docId)) {
locks[docId] = [];
}
var i, documentLocks = locks[docId];
if (_checkLockPresentation(documentLocks, arrayBlocks, userId)) {
for (i = 0; i < arrayBlocks.length; ++i) {
documentLocks.push({
time: Date.now(),
user: userId,
block: arrayBlocks[i],
sessionId: conn.sessionId
});
}
} else {
if (bIsRestore) {
return false;
}
}
sendGetLock(participants, documentLocks);
return true;
}
function sendGetLock(participants, documentLocks) {
_.each(participants, function (participant) {
sendData(participant.connection, {
type: "getLock",
locks: documentLocks
});
});
}
function saveChanges(conn, data) {
var docId = conn.docId,
userId = conn.user.id;
logger.info("saveChanges docid: %s", docId);
var pucker = objServicePucker[docId];
if (!pucker) {
logger.error("saveChanges find pucker error docid: %s", docId);
return;
}
var participants = getParticipants(docId, userId, true);
var objChangesDocument = getDocumentChangesCache(docId);
var deleteIndex = -1;
if (data.startSaveChanges && null != data.deleteIndex) {
deleteIndex = data.deleteIndex;
if (-1 !== deleteIndex) {
var deleteCount = pucker.index - deleteIndex;
if (0 < deleteCount) {
if (objChangesDocument) {
objChangesDocument.splice(deleteIndex, deleteCount);
}
pucker.index -= deleteCount;
sqlBase.deleteChanges(docId, deleteIndex);
} else {
if (0 > deleteCount) {
logger.error("saveChanges docid: %s ; deleteIndex: %s ; startIndex: %s ; deleteCount: %s", docId, deleteIndex, pucker.index, deleteCount);
}
}
}
}
var startIndex = pucker.index;
var newChanges = JSON.parse(data.changes);
var arrNewDocumentChanges = [];
logger.info("saveChanges docid: %s ; deleteIndex: %s ; startIndex: %s ; length: %s", docId, deleteIndex, startIndex, newChanges.length);
if (0 < newChanges.length) {
var oElement = null;
for (var i = 0; i < newChanges.length; ++i) {
oElement = newChanges[i];
arrNewDocumentChanges.push({
docid: docId,
change: JSON.stringify(oElement),
time: Date.now(),
user: userId,
useridoriginal: conn.user.idOriginal
});
}
if (objChangesDocument) {
objChangesDocument.concat(arrNewDocumentChanges);
}
pucker.index += arrNewDocumentChanges.length;
sqlBase.insertChanges(arrNewDocumentChanges, docId, startIndex, conn.user);
}
var changesIndex = (-1 === deleteIndex && data.startSaveChanges) ? startIndex : -1;
if (data.endSaveChanges) {
if (data.isExcel && false !== data.isCoAuthoring && data.excelAdditionalInfo) {
var tmpAdditionalInfo = JSON.parse(data.excelAdditionalInfo);
var oRecalcIndexColumns = _addRecalcIndex(tmpAdditionalInfo["indexCols"]);
var oRecalcIndexRows = _addRecalcIndex(tmpAdditionalInfo["indexRows"]);
if (null !== oRecalcIndexColumns || null !== oRecalcIndexRows) {
_recalcLockArray(userId, locks[docId], oRecalcIndexColumns, oRecalcIndexRows);
}
}
var userLocks = getUserLocks(docId, conn.sessionId);
if (!checkEndAuthLock(false, docId, userId)) {
var arrLocks = _.map(userLocks, function (e) {
return {
block: e.block,
user: e.user,
time: Date.now(),
changes: null
};
});
_.each(participants, function (participant) {
sendData(participant.connection, {
type: "saveChanges",
changes: arrNewDocumentChanges,
changesIndex: pucker.index,
locks: arrLocks,
excelAdditionalInfo: data.excelAdditionalInfo
});
});
}
unSaveLock(conn, changesIndex);
} else {
_.each(participants, function (participant) {
sendData(participant.connection, {
type: "saveChanges",
changes: arrNewDocumentChanges,
changesIndex: pucker.index,
locks: []
});
});
sendData(conn, {
type: "savePartChanges",
changesIndex: changesIndex
});
}
}
function isSaveLock(conn) {
var _docId = conn.docId;
var _userId = conn.user.id;
var _time = Date.now();
var isSaveLock = (undefined === arrSaveLock[_docId]) ? false : arrSaveLock[_docId].savelock;
if (false === isSaveLock) {
arrSaveLock[conn.docId] = {
docid: _docId,
savelock: true,
time: Date.now(),
user: conn.user.id
};
var _tmpSaveLock = arrSaveLock[_docId];
arrSaveLock[conn.docId].saveLockTimeOutId = setTimeout(function () {
if (_tmpSaveLock && _userId == _tmpSaveLock.user && _time == _tmpSaveLock.time) {
arrSaveLock[_docId] = undefined;
}
},
60000);
}
sendData(conn, {
type: "saveLock",
saveLock: isSaveLock
});
}
function unSaveLock(conn, index) {
if (undefined != arrSaveLock[conn.docId] && conn.user.id != arrSaveLock[conn.docId].user) {
return;
}
if (arrSaveLock[conn.docId] && null != arrSaveLock[conn.docId].saveLockTimeOutId) {
clearTimeout(arrSaveLock[conn.docId].saveLockTimeOutId);
}
arrSaveLock[conn.docId] = undefined;
sendData(conn, {
type: "unSaveLock",
index: index
});
}
function getMessages(conn) {
sendData(conn, {
type: "message",
messages: messages[conn.docId]
});
}
function _checkLock(documentLocks, arrayBlocks) {
var isLock = false;
var i, lengthArray = (arrayBlocks) ? arrayBlocks.length : 0;
for (i = 0; i < lengthArray; ++i) {
logger.info("getLock id: %s", arrayBlocks[i]);
if (documentLocks.hasOwnProperty(arrayBlocks[i]) && documentLocks[arrayBlocks[i]] !== null) {
isLock = true;
break;
}
}
if (0 === lengthArray) {
isLock = true;
}
return !isLock;
}
function _checkLockExcel(documentLocks, arrayBlocks, userId) {
var documentLock;
var isLock = false;
var isExistInArray = false;
var i, blockRange;
var lengthArray = (arrayBlocks) ? arrayBlocks.length : 0;
for (i = 0; i < lengthArray && false === isLock; ++i) {
blockRange = arrayBlocks[i];
for (var keyLockInArray in documentLocks) {
if (true === isLock) {
break;
}
if (!documentLocks.hasOwnProperty(keyLockInArray)) {
continue;
}
documentLock = documentLocks[keyLockInArray];
if (documentLock.user === userId && blockRange.sheetId === documentLock.block.sheetId && blockRange.type === c_oAscLockTypeElem.Object && documentLock.block.type === c_oAscLockTypeElem.Object && documentLock.block.rangeOrObjectId === blockRange.rangeOrObjectId) {
isExistInArray = true;
break;
}
if (c_oAscLockTypeElem.Sheet === blockRange.type && c_oAscLockTypeElem.Sheet === documentLock.block.type) {
if (documentLock.user === userId) {
if (blockRange.sheetId === documentLock.block.sheetId) {
isExistInArray = true;
break;
} else {
continue;
}
} else {
isLock = true;
break;
}
}
if (documentLock.user === userId || !(documentLock.block) || blockRange.sheetId !== documentLock.block.sheetId) {
continue;
}
isLock = compareExcelBlock(blockRange, documentLock.block);
}
}
if (0 === lengthArray) {
isLock = true;
}
return !isLock && !isExistInArray;
}
function _checkLockPresentation(documentLocks, arrayBlocks, userId) {
var isLock = false;
var i, documentLock, blockRange;
var lengthArray = (arrayBlocks) ? arrayBlocks.length : 0;
for (i = 0; i < lengthArray && false === isLock; ++i) {
blockRange = arrayBlocks[i];
for (var keyLockInArray in documentLocks) {
if (true === isLock) {
break;
}
if (!documentLocks.hasOwnProperty(keyLockInArray)) {
continue;
}
documentLock = documentLocks[keyLockInArray];
if (documentLock.user === userId || !(documentLock.block)) {
continue;
}
isLock = comparePresentationBlock(blockRange, documentLock.block);
}
}
if (0 === lengthArray) {
isLock = true;
}
return !isLock;
}
function _createSaveTimer(docId) {
var oTimeoutFunction = function () {
if (sqlBase.isLockCriticalSection(docId)) {
saveTimers[docId] = setTimeout(oTimeoutFunction, c_oAscLockTimeOutDelay);
} else {
delete saveTimers[docId];
sendChangesToServer(docId);
}
};
saveTimers[docId] = setTimeout(oTimeoutFunction, c_oAscSaveTimeOutDelay);
}
sockjs_echo.installHandlers(server, {
prefix: "/doc/[0-9-.a-zA-Z_=]*/c",
log: function (severity, message) {
logger.info(message);
}
});
var callbackLoadPuckerMySql = function (error, arrayElements) {
if (null != arrayElements) {
var i, element;
for (i = 0; i < arrayElements.length; ++i) {
element = arrayElements[i];
createPucker(element["dp_key"], element["dp_callback"], element["dp_documentFormatSave"], element["dp_indexUser"], true);
}
}
sqlBase.loadTable(sqlBase.tableId.callbacks, callbackLoadCallbacksMySql);
};
var callbackLoadCallbacksMySql = function (error, arrayElements) {
if (null != arrayElements) {
var i, element, callbackUrl;
for (i = 0; i < arrayElements.length; ++i) {
element = arrayElements[i];
callbackUrl = parseUrl(element["dc_callback"]);
if (null === callbackUrl) {
logger.error("error parse callback = %s", element["dc_callback"]);
}
objServiceInfo[element["dc_key"]] = callbackUrl;
}
var docId;
for (docId in objServiceInfo) {
if (objServicePucker[docId]) {
_createSaveTimer(docId);
} else {
deleteCallback(docId);
}
}
}
callbackFunction();
};
sqlBase.loadTable(sqlBase.tableId.pucker, callbackLoadPuckerMySql);
};
exports.commandFromServer = function (query) {
var docId = query.key;
if (null == docId) {
return c_oAscServerCommandErrors.DocumentIdError;
}
logger.info("commandFromServer: docId = %s c = %s", docId, query.c);
var result = c_oAscServerCommandErrors.NoError;
switch (query.c) {
case "info":
bindEvents(docId, query.callback);
break;
case "drop":
if (query.userid) {
dropUserFromDocument(docId, query.userid, query.description);
} else {
if (query.users) {
onReplySendStatusDocument(docId, query.users);
}
}
break;
case "saved":
removeChanges(docId, "1" !== query.status, "1" === query.conv);
break;
default:
result = c_oAscServerCommandErrors.CommandError;
break;
}
return result;
};