From d37d3914c9cab8696c0f93de61dcd630542efc73 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 19 Jun 2015 21:30:02 -0700 Subject: [PATCH 1/5] GUAC-1172: Implement JSONreader for convenience. --- .../src/main/webapp/modules/JSONReader.js | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 guacamole-common-js/src/main/webapp/modules/JSONReader.js diff --git a/guacamole-common-js/src/main/webapp/modules/JSONReader.js b/guacamole-common-js/src/main/webapp/modules/JSONReader.js new file mode 100644 index 000000000..14e2e16da --- /dev/null +++ b/guacamole-common-js/src/main/webapp/modules/JSONReader.js @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +var Guacamole = Guacamole || {}; + +/** + * A reader which automatically handles the given input stream, assembling all + * received blobs into a JavaScript object by appending them to each other, in + * order, and decoding the result as JSON. Note that this object will overwrite + * any installed event handlers on the given Guacamole.InputStream. + * + * @constructor + * @param {Guacamole.InputStream} stream + * The stream that JSON will be read from. + */ +Guacamole.JSONReader = function(stream) { + + /** + * Reference to this Guacamole.JSONReader. + * + * @private + * @type Guacamole.JSONReader + */ + var guacReader = this; + + /** + * Wrapped Guacamole.StringReader. + * + * @private + * @type Guacamole.StringReader + */ + var stringReader = new Guacamole.StringReader(stream); + + /** + * All JSON read thus far. + * + * @private + * @type String + */ + var json = ''; + + /** + * Returns the current length of this Guacamole.JSONReader, in characters. + * + * @return {Number} + * The current length of this Guacamole.JSONReader. + */ + this.getLength = function() { + return json.length; + }; + + /** + * Returns the contents of this Guacamole.JSONReader as a JavaScript + * object. + * + * @return {Object} + * The contents of this Guacamole.JSONReader, as parsed from the JSON + * contents of the input stream. + */ + this.getJSON = function() { + return JSON.parse(json); + }; + + // Append all received text + stringReader.ontext = function ontext(text) { + + // Append received text + json += text; + + // Call handler, if present + if (guacReader.onprogress) + guacReader.onprogress(text.length); + + }; + + // Simply call onend when end received + stringReader.onend = function() { + if (guacReader.onend) + guacReader.onend(); + }; + + /** + * Fired once for every blob of data received. + * + * @event + * @param {Number} length + * The number of characters received. + */ + this.onprogress = null; + + /** + * Fired once this stream is finished and no further data will be written. + * + * @event + */ + this.onend = null; + +}; From d332b028e3d3a022e4f1cc74204594bc07222ba3 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 19 Jun 2015 21:38:56 -0700 Subject: [PATCH 2/5] GUAC-1172: Add Guacamole.Object and handlers for object-related instructions. --- .../src/main/webapp/modules/Client.js | 132 +++++++++++ .../src/main/webapp/modules/Object.js | 205 ++++++++++++++++++ 2 files changed, 337 insertions(+) create mode 100644 guacamole-common-js/src/main/webapp/modules/Object.js diff --git a/guacamole-common-js/src/main/webapp/modules/Client.js b/guacamole-common-js/src/main/webapp/modules/Client.js index 1b319bdef..63c6ead10 100644 --- a/guacamole-common-js/src/main/webapp/modules/Client.js +++ b/guacamole-common-js/src/main/webapp/modules/Client.js @@ -86,6 +86,14 @@ Guacamole.Client = function(tunnel) { // No initial streams var streams = []; + /** + * All current objects. The index of each object is dictated by the + * Guacamole server. + * + * @type Guacamole.Object[] + */ + var objects = []; + // Pool of available stream indices var stream_indices = new Guacamole.IntegerPool(); @@ -293,6 +301,67 @@ Guacamole.Client = function(tunnel) { }; + /** + * Creates a new output stream associated with the given object and having + * the given mimetype and name. The legality of a mimetype and name is + * dictated by the object itself. + * + * @param {Number} index + * The index of the object for which the output stream is being + * created. + * + * @param {String} mimetype + * The mimetype of the data which will be sent to the output stream. + * + * @param {String} name + * The defined name of an output stream within the given object. + * + * @returns {Guacamole.OutputStream} + * An output stream which will write blobs to the named output stream + * of the given object. + */ + this.createObjectOutputStream = function(index, mimetype, name) { + + // Allocate index + var streamIndex = stream_indices.next(); + + // Create new stream + tunnel.sendMessage("put", index, streamIndex, mimetype, name); + var stream = output_streams[streamIndex] = new Guacamole.OutputStream(guac_client, streamIndex); + + // Override sendEnd() of stream to automatically free index + var oldEnd = stream.sendEnd; + stream.sendEnd = function() { + oldEnd(); + stream_indices.free(streamIndex); + delete output_streams[streamIndex]; + }; + + // Return new, overridden stream + return stream; + + }; + + /** + * Requests read access to the input stream having the given name. If + * successful, a new input stream will be created. + * + * @param {Number} index + * The index of the object from which the input stream is being + * requested. + * + * @param {String} name + * The name of the input stream to request. + */ + this.requestObjectInputStream = function(index, name) { + + // Do not send requests if not connected + if (!isConnected()) + return; + + tunnel.sendMessage("get", index, name); + }; + /** * Acknowledge receipt of a blob on the stream with the given index. * @@ -388,6 +457,20 @@ Guacamole.Client = function(tunnel) { */ this.onfile = null; + /** + * Fired when a filesystem object is created. The object provided to this + * event handler will contain its own event handlers and functions for + * requesting and handling data. + * + * @event + * @param {Guacamole.Object} object + * The created filesystem object. + * + * @param {String} name + * The name of the filesystem. + */ + this.onfilesystem = null; + /** * Fired when a pipe stream is created. The stream provided to this event * handler will contain its own event handlers for received data; @@ -562,6 +645,28 @@ Guacamole.Client = function(tunnel) { }, + "body": function(parameters) { + + // Get object + var objectIndex = parseInt(parameters[0]); + var object = objects[objectIndex]; + + var streamIndex = parseInt(parameters[1]); + var mimetype = parameters[2]; + var name = parameters[3]; + + // Create stream if handler defined + if (object && object.onbody) { + var stream = streams[streamIndex] = new Guacamole.InputStream(guac_client, streamIndex); + object.onbody(stream, mimetype, name); + } + + // Otherwise, unsupported + else + guac_client.sendAck(streamIndex, "Receipt of body unsupported", 0x0100); + + }, + "cfill": function(parameters) { var channelMask = parseInt(parameters[0]); @@ -758,6 +863,21 @@ Guacamole.Client = function(tunnel) { }, + "filesystem": function(parameters) { + + var objectIndex = parseInt(parameters[0]); + var name = parameters[1]; + + // Create object, if supported + if (guac_client.onfilesystem) { + var object = objects[objectIndex] = new Guacamole.Object(guac_client, objectIndex); + guac_client.onfilesystem(object, name); + } + + // If unsupported, simply ignore the availability of the filesystem + + }, + "identity": function(parameters) { var layer = getLayer(parseInt(parameters[0])); @@ -998,6 +1118,18 @@ Guacamole.Client = function(tunnel) { }, + "undefine": function(parameters) { + + // Get object + var objectIndex = parseInt(parameters[0]); + var object = objects[objectIndex]; + + // Signal end of object definition + if (object && object.onundefine) + object.onundefine(); + + }, + "video": function(parameters) { var stream_index = parseInt(parameters[0]); diff --git a/guacamole-common-js/src/main/webapp/modules/Object.js b/guacamole-common-js/src/main/webapp/modules/Object.js new file mode 100644 index 000000000..07a95beea --- /dev/null +++ b/guacamole-common-js/src/main/webapp/modules/Object.js @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2013 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +var Guacamole = Guacamole || {}; + +/** + * An object used by the Guacamole client to house arbitrarily-many named + * input and output streams. + * + * @constructor + * @param {Guacamole.Client} client + * The client owning this object. + * + * @param {Number} index + * The index of this object. + */ +Guacamole.Object = function(client, index) { + + /** + * Reference to this Guacamole.Object. + * + * @private + * @type Guacamole.Object + */ + var guacObject = this; + + /** + * The callbacks associated with all pending input stream requests, if the + * default onbody handling is in use. + * + * @private + * @type Object. + * Map of stream name to corresponding queue of callbacks. The queue of + * callbacks is guaranteed to be in order of request. + */ + var bodyCallbacks = {}; + + /** + * Removes and returns the callback at the head of the callback queue for + * the stream having the given name. If no such callbacks exist, null is + * returned. + * + * @private + * @param {String} name + * The name of the stream to retrieve a callback for. + * + * @returns {Function} + * The next callback associated with the stream having the given name, + * or null if no such callback exists. + */ + var dequeueBodyCallback = function dequeueBodyCallback(name) { + + // If no callbacks defined, simply return null + var callbacks = bodyCallbacks[name]; + if (!callbacks) + return null; + + // Otherwise, pull off first callback, deleting the queue if empty + var callback = callbacks.shift(); + if (callbacks.length === 0) + delete bodyCallbacks[name]; + + // Return found callback + return callback; + + }; + + /** + * Adds the given callback to the tail of the callback queue for the stream + * having the given name. + * + * @private + * @param {String} name + * The name of the stream to associate with the given callback. + * + * @param {Function} callback + * The callback to add to the queue of the stream with the given name. + */ + var enqueueBodyCallback = function enqueueBodyCallback(name, callback) { + + // Get callback queue by name, creating first if necessary + var callbacks = bodyCallbacks[name]; + if (!callbacks) { + callbacks = []; + bodyCallbacks[name] = callbacks; + } + + // Add callback to end of queue + callbacks.push(callback); + + }; + + /** + * The index of this object. + * + * @type Number + */ + this.index = index; + + /** + * Called when this object receives the body of a requested input stream. + * By default, all objects will invoke the callbacks provided to their + * requestInputStream() functions based on the name of the stream + * requested. This behavior can be overridden by specifying a different + * handler here. + * + * @event + * @param {Guacamole.InputStream} inputStream + * The input stream of the received body. + * + * @param {String} mimetype + * The mimetype of the data being received. + * + * @param {String} name + * The name of the stream whose body has been received. + */ + this.onbody = function defaultBodyHandler(inputStream, mimetype, name) { + + // Call queued callback for the received body, if any + var callback = dequeueBodyCallback(name); + if (callback) + callback(inputStream, mimetype); + + }; + + /** + * Called when this object is being undefined. Once undefined, no further + * communication involving this object may occur. + * + * @event + */ + this.onundefine = null; + + /** + * Requests read access to the input stream having the given name. If + * successful, a new input stream will be created. + * + * @param {String} name + * The name of the input stream to request. + * + * @param {Function} [bodyCallback] + * The callback to invoke when the body of the requested input stream + * is received. This callback will be provided a Guacamole.InputStream + * and its mimetype as its two only arguments. If the onbody handler of + * this object is overridden, this callback will not be invoked. + */ + this.requestInputStream = function(name, bodyCallback) { + + // Queue body callback if provided + if (bodyCallback) + queueBodyCallback(name, bodyCallback); + + // Send request for input stream + client.requestObjectInputStream(guacObject.index, name); + + }; + + /** + * Creates a new output stream associated with this object and having the + * given mimetype and name. The legality of a mimetype and name is dictated + * by the object itself. + * + * @param {String} mimetype + * The mimetype of the data which will be sent to the output stream. + * + * @param {String} name + * The defined name of an output stream within this object. + * + * @returns {Guacamole.OutputStream} + * An output stream which will write blobs to the named output stream + * of this object. + */ + this.createOutputStream = function(mimetype, name) { + return client.createObjectOutputStream(guacObject.index, mimetype, name); + }; + +}; + +/** + * The reserved name denoting the root stream of any object. The contents of + * the root stream MUST be a JSON map of stream name to mimetype. + * + * @constant + * @type String + */ +Guacamole.Object.ROOT_STREAM = '/'; From fae71e44f6986a0f5ecec391c36e4fa321e5c1e3 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 20 Jun 2015 13:09:11 -0700 Subject: [PATCH 3/5] GUAC-1172: Fix reference to enqueueBodyCallback() - it's "enqueue", not "queue". --- guacamole-common-js/src/main/webapp/modules/Object.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guacamole-common-js/src/main/webapp/modules/Object.js b/guacamole-common-js/src/main/webapp/modules/Object.js index 07a95beea..4fe650037 100644 --- a/guacamole-common-js/src/main/webapp/modules/Object.js +++ b/guacamole-common-js/src/main/webapp/modules/Object.js @@ -167,7 +167,7 @@ Guacamole.Object = function(client, index) { // Queue body callback if provided if (bodyCallback) - queueBodyCallback(name, bodyCallback); + enqueueBodyCallback(name, bodyCallback); // Send request for input stream client.requestObjectInputStream(guacObject.index, name); From 88a3f12a8bd890762969d4ec5018975a7627945c Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 20 Jun 2015 16:06:54 -0700 Subject: [PATCH 4/5] GUAC-1172: Add constant for stream index mimetype. --- guacamole-common-js/src/main/webapp/modules/Object.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/guacamole-common-js/src/main/webapp/modules/Object.js b/guacamole-common-js/src/main/webapp/modules/Object.js index 4fe650037..34f0b5637 100644 --- a/guacamole-common-js/src/main/webapp/modules/Object.js +++ b/guacamole-common-js/src/main/webapp/modules/Object.js @@ -203,3 +203,13 @@ Guacamole.Object = function(client, index) { * @type String */ Guacamole.Object.ROOT_STREAM = '/'; + +/** + * The mimetype of a stream containing JSON which maps available stream names + * to their corresponding mimetype. The root stream of a Guacamole.Object MUST + * have this mimetype. + * + * @constant + * @type String + */ +Guacamole.Object.STREAM_INDEX_MIMETYPE = 'application/vnd.glyptodon.guacamole.stream-index+json'; From 4ec407b6b66d53159569db19d3c8aa409603be64 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 22 Jun 2015 12:41:11 -0700 Subject: [PATCH 5/5] GUAC-1172: Comply with newly-adopted JavaScript coding style. --- .../src/main/webapp/modules/Client.js | 12 ++++++------ .../src/main/webapp/modules/JSONReader.js | 8 ++++---- .../src/main/webapp/modules/Object.js | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/guacamole-common-js/src/main/webapp/modules/Client.js b/guacamole-common-js/src/main/webapp/modules/Client.js index 63c6ead10..46d535e94 100644 --- a/guacamole-common-js/src/main/webapp/modules/Client.js +++ b/guacamole-common-js/src/main/webapp/modules/Client.js @@ -320,7 +320,7 @@ Guacamole.Client = function(tunnel) { * An output stream which will write blobs to the named output stream * of the given object. */ - this.createObjectOutputStream = function(index, mimetype, name) { + this.createObjectOutputStream = function createObjectOutputStream(index, mimetype, name) { // Allocate index var streamIndex = stream_indices.next(); @@ -331,7 +331,7 @@ Guacamole.Client = function(tunnel) { // Override sendEnd() of stream to automatically free index var oldEnd = stream.sendEnd; - stream.sendEnd = function() { + stream.sendEnd = function freeStreamIndex() { oldEnd(); stream_indices.free(streamIndex); delete output_streams[streamIndex]; @@ -353,7 +353,7 @@ Guacamole.Client = function(tunnel) { * @param {String} name * The name of the input stream to request. */ - this.requestObjectInputStream = function(index, name) { + this.requestObjectInputStream = function requestObjectInputStream(index, name) { // Do not send requests if not connected if (!isConnected()) @@ -645,7 +645,7 @@ Guacamole.Client = function(tunnel) { }, - "body": function(parameters) { + "body" : function handleBody(parameters) { // Get object var objectIndex = parseInt(parameters[0]); @@ -863,7 +863,7 @@ Guacamole.Client = function(tunnel) { }, - "filesystem": function(parameters) { + "filesystem" : function handleFilesystem(parameters) { var objectIndex = parseInt(parameters[0]); var name = parameters[1]; @@ -1118,7 +1118,7 @@ Guacamole.Client = function(tunnel) { }, - "undefine": function(parameters) { + "undefine" : function handleUndefine(parameters) { // Get object var objectIndex = parseInt(parameters[0]); diff --git a/guacamole-common-js/src/main/webapp/modules/JSONReader.js b/guacamole-common-js/src/main/webapp/modules/JSONReader.js index 14e2e16da..f904470c8 100644 --- a/guacamole-common-js/src/main/webapp/modules/JSONReader.js +++ b/guacamole-common-js/src/main/webapp/modules/JSONReader.js @@ -32,7 +32,7 @@ var Guacamole = Guacamole || {}; * @param {Guacamole.InputStream} stream * The stream that JSON will be read from. */ -Guacamole.JSONReader = function(stream) { +Guacamole.JSONReader = function guacamoleJSONReader(stream) { /** * Reference to this Guacamole.JSONReader. @@ -64,7 +64,7 @@ Guacamole.JSONReader = function(stream) { * @return {Number} * The current length of this Guacamole.JSONReader. */ - this.getLength = function() { + this.getLength = function getLength() { return json.length; }; @@ -76,7 +76,7 @@ Guacamole.JSONReader = function(stream) { * The contents of this Guacamole.JSONReader, as parsed from the JSON * contents of the input stream. */ - this.getJSON = function() { + this.getJSON = function getJSON() { return JSON.parse(json); }; @@ -93,7 +93,7 @@ Guacamole.JSONReader = function(stream) { }; // Simply call onend when end received - stringReader.onend = function() { + stringReader.onend = function onend() { if (guacReader.onend) guacReader.onend(); }; diff --git a/guacamole-common-js/src/main/webapp/modules/Object.js b/guacamole-common-js/src/main/webapp/modules/Object.js index 34f0b5637..7d8d0ca13 100644 --- a/guacamole-common-js/src/main/webapp/modules/Object.js +++ b/guacamole-common-js/src/main/webapp/modules/Object.js @@ -33,7 +33,7 @@ var Guacamole = Guacamole || {}; * @param {Number} index * The index of this object. */ -Guacamole.Object = function(client, index) { +Guacamole.Object = function guacamoleObject(client, index) { /** * Reference to this Guacamole.Object. @@ -163,7 +163,7 @@ Guacamole.Object = function(client, index) { * and its mimetype as its two only arguments. If the onbody handler of * this object is overridden, this callback will not be invoked. */ - this.requestInputStream = function(name, bodyCallback) { + this.requestInputStream = function requestInputStream(name, bodyCallback) { // Queue body callback if provided if (bodyCallback) @@ -189,7 +189,7 @@ Guacamole.Object = function(client, index) { * An output stream which will write blobs to the named output stream * of this object. */ - this.createOutputStream = function(mimetype, name) { + this.createOutputStream = function createOutputStream(mimetype, name) { return client.createObjectOutputStream(guacObject.index, mimetype, name); };