mirror of
https://github.com/EGroupware/egroupware.git
synced 2025-08-18 04:20:05 +02:00
W.I.P of collaborative odf editor:
- Add wodocollabtexteditor library
This commit is contained in:
346
api/js/webodf/collab/backend/jsglobal/OperationRouter.js
Normal file
346
api/js/webodf/collab/backend/jsglobal/OperationRouter.js
Normal file
@@ -0,0 +1,346 @@
|
||||
/**
|
||||
* Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
|
||||
*
|
||||
* @licstart
|
||||
* This file is part of WebODF.
|
||||
*
|
||||
* WebODF is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License (GNU AGPL)
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* WebODF is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with WebODF. If not, see <http://www.gnu.org/licenses/>.
|
||||
* @licend
|
||||
*
|
||||
* @source: http://www.webodf.org/
|
||||
* @source: https://github.com/kogmbh/WebODF/
|
||||
*/
|
||||
|
||||
/*global define, runtime, core, ops*/
|
||||
|
||||
define("webodf/editor/backend/jsglobal/OperationRouter", [], function () {
|
||||
"use strict";
|
||||
|
||||
var /**@const @type {!string}*/
|
||||
EVENT_BEFORESAVETOFILE = "beforeSaveToFile",
|
||||
/**@const @type {!string}*/
|
||||
EVENT_SAVEDTOFILE = "savedToFile",
|
||||
/**@const @type {!string}*/
|
||||
EVENT_HASLOCALUNSYNCEDOPERATIONSCHANGED = "hasLocalUnsyncedOperationsChanged",
|
||||
/**@const @type {!string}*/
|
||||
EVENT_HASSESSIONHOSTCONNECTIONCHANGED = "hasSessionHostConnectionChanged";
|
||||
|
||||
runtime.loadClass("ops.OperationTransformer");
|
||||
runtime.loadClass("core.EventNotifier");
|
||||
runtime.loadClass("core.Task");
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @implements ops.OperationRouter
|
||||
*/
|
||||
return function JsGlobalOperationRouter(sessionId, memberId, server, errorCallback) {
|
||||
|
||||
var events = new core.EventNotifier([
|
||||
ops.OperationRouter.signalProcessingBatchStart,
|
||||
ops.OperationRouter.signalProcessingBatchEnd,
|
||||
EVENT_HASLOCALUNSYNCEDOPERATIONSCHANGED,
|
||||
EVENT_HASSESSIONHOSTCONNECTIONCHANGED,
|
||||
EVENT_BEFORESAVETOFILE,
|
||||
EVENT_SAVEDTOFILE
|
||||
]),
|
||||
/**@type{!ops.OperationFactory}*/
|
||||
operationFactory,
|
||||
playbackFunction,
|
||||
operationTransformer = new ops.OperationTransformer(),
|
||||
/**@type{number}*/
|
||||
groupIdentifier = 0,
|
||||
uploadingOpSpecCount,
|
||||
syncTask,
|
||||
unsyncedClientOpspecQueue = [],
|
||||
hasLocalUnsyncedOps = false,
|
||||
hasSessionHostConnection = true,
|
||||
hasUnresolvableConflict = false,
|
||||
syncInProgress = false,
|
||||
syncAttemptCompleteCallbacks = [],
|
||||
successfulSyncCallbacks = [],
|
||||
destroyed = false;
|
||||
|
||||
/**
|
||||
* @return {undefined}
|
||||
*/
|
||||
function updateUnsyncedOps() {
|
||||
var hasLocalUnsyncedOpsNow = (unsyncedClientOpspecQueue.length > 0);
|
||||
|
||||
// no change?
|
||||
if (hasLocalUnsyncedOps === hasLocalUnsyncedOpsNow) {
|
||||
return;
|
||||
}
|
||||
|
||||
hasLocalUnsyncedOps = hasLocalUnsyncedOpsNow;
|
||||
events.emit(EVENT_HASLOCALUNSYNCEDOPERATIONSCHANGED, hasLocalUnsyncedOps);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!boolean}
|
||||
*/
|
||||
this.hasLocalUnsyncedOps = function() {
|
||||
return hasLocalUnsyncedOps;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {!boolean} hasConnection
|
||||
* @return {undefined}
|
||||
*/
|
||||
function updateHasSessionHostConnectionState(hasConnection) {
|
||||
// no change?
|
||||
if (hasSessionHostConnection === hasConnection) {
|
||||
return;
|
||||
}
|
||||
|
||||
hasSessionHostConnection = hasConnection;
|
||||
events.emit(EVENT_HASSESSIONHOSTCONNECTIONCHANGED, hasSessionHostConnection);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!boolean}
|
||||
*/
|
||||
this.hasSessionHostConnection = function () {
|
||||
return hasSessionHostConnection;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the factory to use to create operation instances from operation specs.
|
||||
*
|
||||
* @param {!ops.OperationFactory} f
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.setOperationFactory = function (f) {
|
||||
operationFactory = f;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the method which should be called to apply operations.
|
||||
*
|
||||
* @param {!function(!ops.Operation):boolean} playback_func
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.setPlaybackFunction = function (playback_func) {
|
||||
playbackFunction = playback_func;
|
||||
};
|
||||
|
||||
/**
|
||||
* Brings the locally created operations into the game.
|
||||
*
|
||||
* @param {!Array.<!{optype:!string}>} operationSpecs
|
||||
* @return {undefined}
|
||||
*/
|
||||
function executeOpSpecs(operationSpecs) {
|
||||
// This is an extremely simplistic and VERY temporary implementation of operation grouping.
|
||||
// In order to improve undo behaviour, the undo manager requires knowledge about what groups
|
||||
// of operations were queued together, so these can be stored in a single undo state.
|
||||
// The current implementation is only designed for a localeditor instance & the TrivialUndoManager.
|
||||
// TODO redesign this concept to work with collaborative editing
|
||||
groupIdentifier += 1;
|
||||
events.emit(ops.OperationRouter.signalProcessingBatchStart, {});
|
||||
operationSpecs.forEach(function (opspec) {
|
||||
var /**@type{?ops.Operation}*/
|
||||
timedOp;
|
||||
|
||||
timedOp = operationFactory.create(opspec);
|
||||
timedOp.group = "g" + groupIdentifier;
|
||||
|
||||
// TODO: handle return flag in error case
|
||||
playbackFunction(timedOp);
|
||||
});
|
||||
events.emit(ops.OperationRouter.signalProcessingBatchEnd, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes local operations and prepares to send to these to the collab server
|
||||
*
|
||||
* @param {!Array.<!ops.Operation>} operations
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.push = function (operations) {
|
||||
if (hasUnresolvableConflict) {
|
||||
throw new Error("unresolvableConflictingOps");
|
||||
}
|
||||
|
||||
var specs = [],
|
||||
timestamp = Date.now();
|
||||
|
||||
operations.forEach(function(op) {
|
||||
var spec = op.spec();
|
||||
spec.timestamp = timestamp;
|
||||
specs.push(spec);
|
||||
});
|
||||
|
||||
unsyncedClientOpspecQueue = unsyncedClientOpspecQueue.concat(specs);
|
||||
executeOpSpecs(specs);
|
||||
updateUnsyncedOps();
|
||||
syncTask.trigger();
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {function()} cb
|
||||
*/
|
||||
this.close = function (cb) {
|
||||
function internalDestroy() {
|
||||
destroyed = true;
|
||||
syncTask.destroy(cb);
|
||||
}
|
||||
syncAttemptCompleteCallbacks.push(internalDestroy);
|
||||
syncTask.triggerImmediate();
|
||||
if (!syncInProgress) {
|
||||
internalDestroy();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {!string} eventId
|
||||
* @param {!Function} cb
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.subscribe = function (eventId, cb) {
|
||||
events.subscribe(eventId, cb);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {!string} eventId
|
||||
* @param {!Function} cb
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.unsubscribe = function (eventId, cb) {
|
||||
events.unsubscribe(eventId, cb);
|
||||
};
|
||||
|
||||
/**
|
||||
* Complete the current synchronize request
|
||||
* @param {!boolean=} connectionState If specified, update the session host connection state.
|
||||
* @return {undefined}
|
||||
*/
|
||||
function completeSyncRequest(connectionState) {
|
||||
syncInProgress = false;
|
||||
updateUnsyncedOps();
|
||||
if (connectionState !== undefined) {
|
||||
updateHasSessionHostConnectionState(connectionState);
|
||||
}
|
||||
|
||||
syncAttemptCompleteCallbacks.forEach(function(cb) {
|
||||
cb();
|
||||
});
|
||||
syncAttemptCompleteCallbacks.length = 0;
|
||||
|
||||
if (!destroyed) {
|
||||
syncTask.cancel();
|
||||
syncTask.trigger();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a synchronization error and retry
|
||||
* @param {!Error} e
|
||||
* @return {undefined}
|
||||
*/
|
||||
function logErrorAndRetry(e) {
|
||||
var isConnected;
|
||||
switch (e.code) {
|
||||
case "SEQ_OUTOFDATE":
|
||||
// This error is an expected as part of normal operation. Don't bother logging
|
||||
isConnected = true;
|
||||
break;
|
||||
case "SERVICE_UNAVAILABLE":
|
||||
// This error is an expected as part of normal operation. Don't bother logging.
|
||||
// However, the error does indicate a connectivity problem with the session, so
|
||||
// ensure the connection status is updated
|
||||
isConnected = false;
|
||||
break;
|
||||
default:
|
||||
isConnected = false;
|
||||
runtime.log(e);
|
||||
break;
|
||||
}
|
||||
completeSyncRequest(isConnected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove synchronized client ops off the client queue
|
||||
* @return {undefined}
|
||||
*/
|
||||
function onPushSuccess() {
|
||||
// Only remove operations that have now been uploaded to the remote service
|
||||
unsyncedClientOpspecQueue.splice(0, uploadingOpSpecCount);
|
||||
completeSyncRequest(true);
|
||||
successfulSyncCallbacks.forEach(function(cb) {
|
||||
cb();
|
||||
});
|
||||
successfulSyncCallbacks.length = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process remote changes
|
||||
* @param {!{specs: !Array.<!Object>, sequenceId: !Object}} remoteChanges
|
||||
* @return {undefined}
|
||||
*/
|
||||
function onRemoteChangesSuccess(remoteChanges) {
|
||||
var transformResult = operationTransformer.transform(unsyncedClientOpspecQueue, remoteChanges.specs);
|
||||
if (!transformResult) {
|
||||
hasUnresolvableConflict = true;
|
||||
errorCallback("unresolvableConflictingOps");
|
||||
completeSyncRequest();
|
||||
return;
|
||||
}
|
||||
|
||||
if (transformResult.opSpecsB.length > 0) {
|
||||
// Apply remote changes that have been transformed against local updates
|
||||
executeOpSpecs(transformResult.opSpecsB);
|
||||
}
|
||||
|
||||
// The local client changes are now transformed against the current remote updates, and should not be transformed again.
|
||||
// Update the local sync queue with the translated results in case the push attempt fails due to a conflict
|
||||
unsyncedClientOpspecQueue = transformResult.opSpecsA;
|
||||
|
||||
// Send local changes that have been transformed against the remote updates
|
||||
uploadingOpSpecCount = unsyncedClientOpspecQueue.length;
|
||||
if (unsyncedClientOpspecQueue.length > 0) {
|
||||
server.getJsGlobalServer().push(sessionId, memberId, remoteChanges.sequenceId, unsyncedClientOpspecQueue, onPushSuccess, logErrorAndRetry);
|
||||
} else {
|
||||
onPushSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DO NOT CALL DIRECTLY, use syncTask.trigger instead.
|
||||
*
|
||||
* Synchronize the local state with the remote server. This is an async call and will return immediately, while
|
||||
* the processing occurs in the background.
|
||||
*
|
||||
* @return {undefined}
|
||||
*/
|
||||
function synchronizeWithServer() {
|
||||
if (hasUnresolvableConflict === false && syncInProgress === false) {
|
||||
syncInProgress = true;
|
||||
server.getJsGlobalServer().getRemoteChanges(sessionId, memberId, onRemoteChangesSuccess, logErrorAndRetry);
|
||||
}
|
||||
}
|
||||
|
||||
this.requestReplay = function(cb) {
|
||||
if (hasUnresolvableConflict) {
|
||||
throw new Error("unresolvableConflictingOps");
|
||||
}
|
||||
successfulSyncCallbacks.push(cb);
|
||||
syncTask.triggerImmediate();
|
||||
};
|
||||
|
||||
function init() {
|
||||
syncTask = core.Task.createTimeoutTask(synchronizeWithServer, 100);
|
||||
}
|
||||
init();
|
||||
};
|
||||
});
|
103
api/js/webodf/collab/backend/jsglobal/Server.js
Normal file
103
api/js/webodf/collab/backend/jsglobal/Server.js
Normal file
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
|
||||
*
|
||||
* @licstart
|
||||
* This file is part of WebODF.
|
||||
*
|
||||
* WebODF is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License (GNU AGPL)
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* WebODF is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with WebODF. If not, see <http://www.gnu.org/licenses/>.
|
||||
* @licend
|
||||
*
|
||||
* @source: http://www.webodf.org/
|
||||
* @source: https://github.com/kogmbh/WebODF/
|
||||
*/
|
||||
|
||||
/*global define, window, runtime, core*/
|
||||
|
||||
define("webodf/editor/backend/jsglobal/Server", [], function () {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @implements ops.Server
|
||||
*/
|
||||
return function JsGlobalServer() {
|
||||
var jsGlobalInstance;
|
||||
|
||||
this.getGenesisUrl = function () {
|
||||
return "welcome.odt";
|
||||
};
|
||||
|
||||
this.getJsGlobalServer = function() {
|
||||
return jsGlobalInstance;
|
||||
};
|
||||
|
||||
/*jslint unparam: true*/
|
||||
/**
|
||||
* @param {!number} timeout in milliseconds
|
||||
* @param {!function(!string)} callback
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.connect = function (timeout, callback) {
|
||||
var interval = window.setInterval(function() {
|
||||
if (window.jsGlobalInstance) {
|
||||
jsGlobalInstance = window.jsGlobalInstance;
|
||||
window.clearInterval(interval);
|
||||
callback("ready");
|
||||
}
|
||||
// TODO properly timeout
|
||||
}, 100);
|
||||
};
|
||||
/*jslint unparam: false*/
|
||||
|
||||
/**
|
||||
* @return {!string}
|
||||
*/
|
||||
this.networkStatus = function () {
|
||||
return "ready";
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {!string} login
|
||||
* @param {!string} password
|
||||
* @param {function(!Object)} successCb
|
||||
* @param {function(!string)} failCb
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.login = function (login, password, successCb, failCb) {
|
||||
jsGlobalInstance.login(login, password, successCb, failCb);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {!string} userId
|
||||
* @param {!string} sessionId
|
||||
* @param {!function(!string)} successCb
|
||||
* @param {!function()} failCb
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.joinSession = function (userId, sessionId, successCb, failCb) {
|
||||
jsGlobalInstance.joinSession(userId, sessionId, successCb, failCb);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {!string} sessionId
|
||||
* @param {!string} memberId
|
||||
* @param {!function()} successCb
|
||||
* @param {!function()} failCb
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.leaveSession = function (sessionId, memberId, successCb, failCb) {
|
||||
jsGlobalInstance.leaveSession(sessionId, memberId, successCb, failCb);
|
||||
};
|
||||
};
|
||||
});
|
49
api/js/webodf/collab/backend/jsglobal/ServerFactory.js
Normal file
49
api/js/webodf/collab/backend/jsglobal/ServerFactory.js
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
|
||||
*
|
||||
* @licstart
|
||||
* This file is part of WebODF.
|
||||
*
|
||||
* WebODF is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License (GNU AGPL)
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* WebODF is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with WebODF. If not, see <http://www.gnu.org/licenses/>.
|
||||
* @licend
|
||||
*
|
||||
* @source: http://www.webodf.org/
|
||||
* @source: https://github.com/kogmbh/WebODF/
|
||||
*/
|
||||
|
||||
/*global define, document, require, runtime, ops */
|
||||
|
||||
define("webodf/editor/backend/jsglobal/ServerFactory", [
|
||||
"webodf/editor/backend/jsglobal/Server",
|
||||
"webodf/editor/backend/jsglobal/SessionBackend",
|
||||
"webodf/editor/backend/jsglobal/SessionList"],
|
||||
function (JsGlobalServer, JsGlobalSessionBackend, JsGlobalSessionList) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @implements ServerFactory
|
||||
*/
|
||||
return function JsGlobalServerFactory() {
|
||||
this.createServer = function (args) {
|
||||
return new JsGlobalServer(args);
|
||||
};
|
||||
this.createSessionBackend = function (sid, mid, server) {
|
||||
return new JsGlobalSessionBackend(sid, mid, server);
|
||||
};
|
||||
this.createSessionList = function (server) {
|
||||
return new JsGlobalSessionList(server);
|
||||
};
|
||||
};
|
||||
});
|
66
api/js/webodf/collab/backend/jsglobal/SessionBackend.js
Normal file
66
api/js/webodf/collab/backend/jsglobal/SessionBackend.js
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright (C) 2014 KO GmbH <copyright@kogmbh.com>
|
||||
*
|
||||
* @licstart
|
||||
* This file is part of WebODF.
|
||||
*
|
||||
* WebODF is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License (GNU AGPL)
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* WebODF is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with WebODF. If not, see <http://www.gnu.org/licenses/>.
|
||||
* @licend
|
||||
*
|
||||
* @source: http://www.webodf.org/
|
||||
* @source: https://github.com/kogmbh/WebODF/
|
||||
*/
|
||||
|
||||
/*global define, ops*/
|
||||
|
||||
|
||||
define("webodf/editor/backend/jsglobal/SessionBackend", [
|
||||
"webodf/editor/backend/jsglobal/OperationRouter"],
|
||||
function (JsGlobalOperationRouter) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @implements SessionBackend
|
||||
*/
|
||||
function JsGlobalSessionBackend(sessionId, memberId, server) {
|
||||
|
||||
/**
|
||||
* @return {!string}
|
||||
*/
|
||||
this.getMemberId = function () {
|
||||
return memberId;
|
||||
};
|
||||
|
||||
/*jslint unparam: true*/
|
||||
/**
|
||||
* @param {!odf.OdfContainer} odfContainer
|
||||
* @param {!function(!Object)} errorCallback
|
||||
* @return {!ops.OperationRouter}
|
||||
*/
|
||||
this.createOperationRouter = function (odfContainer, errorCallback) {
|
||||
return new JsGlobalOperationRouter(sessionId, memberId, server, errorCallback);
|
||||
};
|
||||
/*jslint unparam: false*/
|
||||
|
||||
/**
|
||||
* @return {!string}
|
||||
*/
|
||||
this.getGenesisUrl = function () {
|
||||
return server.getGenesisUrl(sessionId);
|
||||
};
|
||||
}
|
||||
|
||||
return JsGlobalSessionBackend;
|
||||
});
|
126
api/js/webodf/collab/backend/jsglobal/SessionList.js
Normal file
126
api/js/webodf/collab/backend/jsglobal/SessionList.js
Normal file
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
|
||||
*
|
||||
* @licstart
|
||||
* This file is part of WebODF.
|
||||
*
|
||||
* WebODF is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License (GNU AGPL)
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* WebODF is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with WebODF. If not, see <http://www.gnu.org/licenses/>.
|
||||
* @licend
|
||||
*
|
||||
* @source: http://www.webodf.org/
|
||||
* @source: https://github.com/kogmbh/WebODF/
|
||||
*/
|
||||
|
||||
/*global define, ops, runtime, core */
|
||||
|
||||
define("webodf/editor/backend/jsglobal/SessionList", [], function () {
|
||||
"use strict";
|
||||
|
||||
var /**@const @type {!string}*/
|
||||
EVENT_SESSION_CREATED = "jsglobal/sessionCreated",
|
||||
/**@const @type {!string}*/
|
||||
EVENT_SESSION_MODIFIED = "jsglobal/sessionModified",
|
||||
/**@const @type {!string}*/
|
||||
EVENT_SESSION_REMOVED = "jsglobal/sessionRemoved",
|
||||
/**@const @type {!number}*/
|
||||
POLL_FREQUENCY_MS = 500;
|
||||
|
||||
runtime.loadClass("core.EventNotifier");
|
||||
|
||||
return function JsGlobalSessionList(server) {
|
||||
var events = new core.EventNotifier([
|
||||
EVENT_SESSION_CREATED,
|
||||
EVENT_SESSION_MODIFIED,
|
||||
EVENT_SESSION_REMOVED
|
||||
]),
|
||||
pullUpdateTask,
|
||||
existingSessionHashes = {},
|
||||
sessions;
|
||||
|
||||
/**
|
||||
* @return {undefined}
|
||||
*/
|
||||
function fetchAndProcessSessionList() {
|
||||
var sessionPresentInUpdate = {};
|
||||
sessions = server.getJsGlobalServer().getSessions();
|
||||
|
||||
sessions.forEach(function(newSessionObj) {
|
||||
var existingSession = existingSessionHashes[newSessionObj.id],
|
||||
newSessionHash = JSON.stringify(newSessionObj); // World's most inefficient hash? Perhaps!
|
||||
|
||||
if (!existingSession) {
|
||||
events.core_EventNotifier$emit(EVENT_SESSION_CREATED, newSessionObj);
|
||||
} else if (existingSession.hash !== newSessionHash) {
|
||||
events.core_EventNotifier$emit(EVENT_SESSION_MODIFIED, newSessionObj);
|
||||
}
|
||||
existingSessionHashes[newSessionObj.id] = newSessionHash;
|
||||
sessionPresentInUpdate[newSessionObj.id] = true;
|
||||
});
|
||||
|
||||
Object.keys(existingSessionHashes).forEach(function(sessionId) {
|
||||
if (!sessionPresentInUpdate.hasOwnProperty(sessionId)) {
|
||||
events.core_EventNotifier$emit(EVENT_SESSION_REMOVED, sessionId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current session list, and optionally subscribe to future updates
|
||||
*
|
||||
* @param {!{onCreated: function(!Object):undefined, onUpdated: function(!Object):undefined, onRemoved: function(!string):undefined}=} subscriber
|
||||
* @return {!Array.<!Object>} Return an array of session objects
|
||||
*/
|
||||
this.getSessions = function (subscriber) {
|
||||
fetchAndProcessSessionList();
|
||||
if (subscriber) {
|
||||
events.core_EventNotifier$subscribe(EVENT_SESSION_CREATED, subscriber.onCreated);
|
||||
events.core_EventNotifier$subscribe(EVENT_SESSION_MODIFIED, subscriber.onUpdated);
|
||||
events.core_EventNotifier$subscribe(EVENT_SESSION_REMOVED, subscriber.onRemoved);
|
||||
}
|
||||
return sessions;
|
||||
};
|
||||
|
||||
/**
|
||||
* Unsubscribe to updates
|
||||
*
|
||||
* @param {!{onCreated: function(!Object):undefined, onUpdated: function(!Object):undefined, onRemoved: function(!string):undefined}} subscriber
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.unsubscribe = function (subscriber) {
|
||||
events.unsubscribe(EVENT_SESSION_CREATED, subscriber.onCreated);
|
||||
events.unsubscribe(EVENT_SESSION_MODIFIED, subscriber.onUpdated);
|
||||
events.unsubscribe(EVENT_SESSION_REMOVED, subscriber.onRemoved);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {!boolean}{ enabled
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.setUpdatesEnabled = function (enabled) {
|
||||
if (enabled) {
|
||||
pullUpdateTask.triggerImmediate();
|
||||
} else {
|
||||
pullUpdateTask.cancel();
|
||||
}
|
||||
};
|
||||
|
||||
function init() {
|
||||
pullUpdateTask = core.Task.createTimeoutTask(function() {
|
||||
fetchAndProcessSessionList();
|
||||
pullUpdateTask.trigger();
|
||||
}, POLL_FREQUENCY_MS);
|
||||
}
|
||||
init();
|
||||
};
|
||||
});
|
Reference in New Issue
Block a user