From 78eb0c741084429efade72507235a8d0ddcb9012 Mon Sep 17 00:00:00 2001 From: s1341 Date: Mon, 9 Dec 2013 15:58:17 +0200 Subject: [PATCH] Working changesetloader (no caching) --- src/static/js/broadcast.js | 4 +- src/static/js/revisioncache.js | 124 ++++++++++++++++++++++++++++----- src/static/js/timeslider.js | 91 +++++++++++++++--------- 3 files changed, 165 insertions(+), 54 deletions(-) diff --git a/src/static/js/broadcast.js b/src/static/js/broadcast.js index fe9038721..a2c43fe91 100644 --- a/src/static/js/broadcast.js +++ b/src/static/js/broadcast.js @@ -525,8 +525,8 @@ function loadBroadcastJS(tsclient, fireWhenAllScriptsAreLoaded, BroadcastSlider) } } }; - tsclient.on("CHANGESET_REQ", changesetLoader.handle_CHANGESET_REQ); - tsclient.on("COLLABROOM", changesetLoader.handle_COLLABROOM); + //tsclient.on("CHANGESET_REQ", changesetLoader.handle_CHANGESET_REQ); + //tsclient.on("COLLABROOM", changesetLoader.handle_COLLABROOM); // to start upon window load, just push a function onto this array //window['onloadFuncts'].push(setUpSocket); diff --git a/src/static/js/revisioncache.js b/src/static/js/revisioncache.js index 7c4594580..04bee31c9 100644 --- a/src/static/js/revisioncache.js +++ b/src/static/js/revisioncache.js @@ -199,8 +199,9 @@ $.Class("Thread", } _this._run.apply(_this); }; - this._interval_id = setInterval(wrapper, this._interval); this._is_running = true; + this._is_stopping = false; + this._interval_id = setInterval(wrapper, this._interval); }, // stop the run loop stop: function () { @@ -217,10 +218,21 @@ $.Class("ChangesetRequest", {//statics }, {//instance - init: function (start, end) { + init: function (start, granularity, callback) { this.start = start; - this.end = end; + this.granularity = granularity; + this.request_id = (this.start << 16) + granularity; + this.fulfill_callback = callback; + }, + getRequestID: function () { + return this.request_id; + }, + fulfill: function (data) { + console.log("[changesetrequest] Fulfilling request %d", this.getRequestID()); + if (this.fulfill_callback) + this.fulfill_callback(data); } + } ); @@ -228,41 +240,117 @@ Thread("ChangesetLoader", {//statics }, {//instance - init: function () { + /** + * Create a new ChangesetLoader. + * @constructor + * @param {TimesliderClient} client - a TimesliderClient object to be used + * for communication with the server. + */ + init: function (client) { this._super(100); - this.queue_large = []; - this.queue_medium = []; - this.queue_small = []; + this.client = client; + this.queues = { + small: [], + medium: [], + large: [], + } + this.pending = {}; + var _this = this; + this.client.on("CHANGESET_REQ", function () { + _this.on_response.apply(_this, arguments); + }); }, - enqueue: function (start, count) { + /** + * Enqueue a request for changesets. The changesets will be retrieved + * asynchronously. + * @param {number} start - The revision from which to start. + * @param {number} granularity - The granularity of the changesets. If this + * is 1, the response will include changesets which + * can be applied to go from revision r to r+1. + * If 10 is specified, the resulting changesets will + * be 'condensed', so that each changeset will go from + * r to r+10. + * If any other number is specified, that granularity will + * apply. + * + * TODO: there is currently no 'END' revision implemented + * in the server. The 'END' calculated at the server is: + * start + (100 * granularity) + * We should probably fix this so that you can specify + * exact ranges. Right now, the minimum number of + * changesets/revisions you can retrieve is 100, which + * feels broken. + * @param {function} callback - A callback which will be triggered when the request has + * been fulfilled. + */ + enqueue: function (start, granularity, callback) { //TODO: check cache to see if we really need to fetch this // maybe even to splices if we just need a smaller range // in the middle var queue = null; - if (count >= 100) - queue = this.queue_large; - else if (count >= 10) - queue = this.queue_medium; + if (granularity == 1) + queue = this.queues.small; + else if (granularity == 10) + queue = this.queues.medium; else - queue = this.queue_small; + queue = this.queues.large; - queue.push(new ChangesetRequest(start, start+count)); + queue.push(new ChangesetRequest(start, granularity, callback)); }, _run: function () { console.log("[changesetloader] tick"); //TODO: pop an item from the queue and perform a request. + for (q in this.queues) { + var queue = this.queues[q]; + if (queue.length > 0) { + // TODO: pop and handle + var request = queue.pop(); + //TODO: test AGAIN to make sure that it hasn't been retrieved and cached by + //a previous request. This should handle the case when two requests for the + //same changesets are enqueued (which would be fine, as at enqueue time, we + //only check the cache of AVAILABLE changesets, not the pending requests), + //the first one is fulfilled, and then we pop the second one, and don't + //need to perform a server request. Note: it might be worth changing enqueue + //to check the pending requests queue to avoid this situation entirely. + var _this = this; + this.client.sendMessage("CHANGESET_REQ", { + start: request.start, + granularity: request.granularity, + requestID: request.getRequestID(), + }, function () { + _this.pending[request.getRequestID()] = request; + }); + }; + }; + //TODO: this stop is just for debugging!!!! + //FIXME: remove when done testing + this.stop(); + }, + on_response: function (data) { + console.log("on_response: ", data) + if (!data.requestID in this.pending) { + console.log("[changesetloader] WTF? changeset not pending: ", data.requestID); + return; + } + + // pop it from the pending list: + var request = this.pending[data.requestID]; + delete this.pending[data.requestID]; + //fulfill the request + request.fulfill(data); }, } ); -function loadBroadcastRevisionsJS(clientVars) +function loadBroadcastRevisionsJS(clientVars, client) { console.log("here") - revisionCache = new RevisionCache(clientVars.collab_client_vars.rev || 0); - revisionInfo.latest = clientVars.collab_client_vars.rev || -1; +// revisionCache = new RevisionCache(clientVars.collab_client_vars.rev || 0); +// revisionInfo.latest = clientVars.collab_client_vars.rev || -1; -// cl = new ChangesetLoader(); + cl = new ChangesetLoader(client); + return cl; } diff --git a/src/static/js/timeslider.js b/src/static/js/timeslider.js index d7673f0b9..968ac376c 100644 --- a/src/static/js/timeslider.js +++ b/src/static/js/timeslider.js @@ -45,30 +45,41 @@ $.Class("SocketClient", // setup the socket callbacks: _this = this; - this.socket.on("connect", function(callback) { - _this.onConnect(callback); + this.socket.on("connect", function() { + _this.onConnect.apply(_this, arguments); }); - this.socket.on("disconnect", function(callback) { - _this.onDisconnect(callback); + this.socket.on("disconnect", function() { + _this.onDisconnect.apply(_this, arguments); }); - this.socket.on("message", function(message, callback) { - _this.onMessage(message, callback); + this.socket.on("message", function(message) { + _this.onMessage.apply(_this, arguments); }); }, - onConnect: function(callback) { + onConnect: function() { console.log("[socket_client] > onConnect"); }, - onDisconnect: function(callback) { + onDisconnect: function() { console.log("[socket_client] > onDisconnect"); }, - onMessage: function(message, callback) { + /** + * Triggered when a new message arrives from the server. + * @param {object} message - The message. + */ + onMessage: function(message) { console.log("[socket_client] > onMessage: ", message); }, + /** + * Sends a message to the server. + * @param {object} message - The message to send + * @param {function} callback - A callback function which will be called after + * the message has been sent to the socket. + */ sendMessage: function(message, callback) { + console.log("[socket_client] > sendMessage: ", message); this.socket.json.send(message); if (callback) callback(); @@ -97,6 +108,15 @@ SocketClient("AuthenticatedSocketClient", this._super(baseurl); }, + /** + * Sends a pad message to the server, including all the neccessary + * session info and tokens. + * @param {string} type - The message type to send. See the server code for + * valid message types. + * @param {object} data - The data payload to be sent to the server. + * @param {function} callback - A callback function which will be called after + * the message has been sent to the socket. + */ sendMessage: function (type, data, callback) { this.sessionID = decodeURIComponent(readCookie("sessionID")); this.password = readCookie("password"); @@ -111,49 +131,50 @@ SocketClient("AuthenticatedSocketClient", this._super(msg, callback); }, - onMessage: function (message, callback) { + onMessage: function (message) { console.log("[authorized_client] > onMessage:", message); if (message.accessStatus) { //access denied? //TODO raise some kind of error? console.log("ACCESS ERROR!"); } - this.dispatchMessage(message.type, message.data, callback); + this.dispatchMessage(message.type, message.data); }, - dispatchMessage: function(type, data, callback) { + /** + * Dispatch incoming messages to handlers in subclasses or registered + * as event handlers. + * @param {string} type - The type of the message. See the server code + * for possible values. + * @param {object} data - The message payload. + */ + dispatchMessage: function(type, data) { console.log("[authorized_client] > dispatchMessage('%s', %s)", type, data); // first call local handlers if ("handle_" + type in this) - this["handle_" + type](data, callback); + this["handle_" + type](data); // then call registered handlers if (type in this.handlers) for(var h in this.handlers[type]) { //TODO: maybe chain the handlers into some kind of chain-of-responsibility? var handler = this.handlers[type][h]; - handler.handler.call(this, data, handler.context, callback); + handler.handler.call(this, data, handler.context); } }, - on: function(type, callback, context) { + /** + * Register an event handler for a given message type. + * @param {string} type - The message type. + * @param {function} handler - The handler function. + * @param {object} context - Optionally, some context to be passed to the handler. + */ + on: function(type, handler, context) { if (!(type in this.handlers)) this.handlers[type] = []; - this.handlers[type].push({handler: callback, context: context}); + this.handlers[type].push({handler: handler, context: context}); return this; }, - registerHandler: this.on, - unregisterHandler: function(type, context) { - if(type in this.handlers) - { - for(var h in this.handlers[type]) - if (this.handlers[type][h].context == context) - { - delete this.handlers[type][h]; - break; - } - } - }, } ); @@ -165,25 +186,25 @@ AuthenticatedSocketClient("TimesliderClient", this._super(baseurl, padID); }, - onConnect: function (callback) { + onConnect: function () { this.sendMessage("CLIENT_READY", {}, function() { console.log("CLIENT_READY sent");}); }, // ------------------------------------------ // Handling events - handle_CLIENT_VARS: function(data, callback) { + handle_CLIENT_VARS: function(data) { console.log("[timeslider_client] handle_CLIENT_VARS: ", data); this.clientVars = data; this.current_revision = this.head_revision = this.clientVars.collab_client_vars.rev; this.savedRevisions = this.clientVars.savedRevisions; }, - handle_COLLABROOM: function(data, callback) { + handle_COLLABROOM: function(data) { console.log("[timeslider_client] handle_COLLABROOM: ", data); }, - handle_CHANGESET_REQ: function(data, callback) { - console.log("[timeslider_client] handle_COLLABROOM: ", data); + handle_CHANGESET_REQ: function(data) { + console.log("[timeslider_client] handle_CHANGESET_REQ: ", data); }, } ); @@ -217,12 +238,14 @@ function init(baseURL) { //find out in which subfolder we are var resource = baseURL.substring(1) + 'socket.io'; + var cl; console.log(url, baseURL, resource, padId); var timesliderclient = new TimesliderClient(url, padId) .on("CLIENT_VARS", function(data, context, callback) { //load all script that doesn't work without the clientVars BroadcastSlider = require('./broadcast_slider').loadBroadcastSliderJS(this,fireWhenAllScriptsAreLoaded); - require('./broadcast_revisions').loadBroadcastRevisionsJS(this.clientVars); + cl = require('./revisioncache').loadBroadcastRevisionsJS(this.clientVars, this); + //require('./broadcast_revisions').loadBroadcastRevisionsJS(this.clientVars); changesetLoader = require('./broadcast').loadBroadcastJS(this, fireWhenAllScriptsAreLoaded, BroadcastSlider); //initialize export ui