Merge pull request #203 from glyptodon/object-api

GUAC-1172: Add Guacamole.Object and support for related instructions
This commit is contained in:
James Muehlner
2015-06-22 14:44:21 -07:00
3 changed files with 464 additions and 0 deletions

View File

@@ -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 createObjectOutputStream(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 freeStreamIndex() {
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 requestObjectInputStream(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 handleBody(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 handleFilesystem(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 handleUndefine(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]);

View File

@@ -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 guacamoleJSONReader(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 getLength() {
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 getJSON() {
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 onend() {
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;
};

View File

@@ -0,0 +1,215 @@
/*
* 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 guacamoleObject(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.<String, Function[]>
* 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 requestInputStream(name, bodyCallback) {
// Queue body callback if provided
if (bodyCallback)
enqueueBodyCallback(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 createOutputStream(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 = '/';
/**
* 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';