From d332b028e3d3a022e4f1cc74204594bc07222ba3 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 19 Jun 2015 21:38:56 -0700 Subject: [PATCH] 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 = '/';