mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-07 05:31:22 +00:00
Migrate to cleaner InputStream and OutputStream with Reader wrappers.
This commit is contained in:
@@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* 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 || {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reader which automatically handles the given input stream, returning
|
||||||
|
* strictly received packets as array buffers. Note that this object will
|
||||||
|
* overwrite any installed event handlers on the given Guacamole.InputStream.
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param {Guacamole.InputStream} stream The stream that data will be read
|
||||||
|
* from.
|
||||||
|
*/
|
||||||
|
Guacamole.ArrayBufferReader = function(stream) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to this Guacamole.InputStream.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
var guac_reader = this;
|
||||||
|
|
||||||
|
// Receive blobs as array buffers
|
||||||
|
stream.onblob = function(data) {
|
||||||
|
|
||||||
|
// Convert to ArrayBuffer
|
||||||
|
var binary = window.atob(data);
|
||||||
|
var arrayBuffer = new ArrayBuffer(binary.length);
|
||||||
|
var bufferView = new Uint8Array(arrayBuffer);
|
||||||
|
|
||||||
|
for (var i=0; i<binary.length; i++)
|
||||||
|
bufferView[i] = binary.charCodeAt(i);
|
||||||
|
|
||||||
|
// Call handler, if present
|
||||||
|
if (guac_stream.ondata)
|
||||||
|
guac_stream.ondata(arrayBuffer);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// Simply call onend when end received
|
||||||
|
stream.onend = function() {
|
||||||
|
if (guac_reader.onend)
|
||||||
|
guac_reader.onend();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fired once for every blob of data received.
|
||||||
|
*
|
||||||
|
* @event
|
||||||
|
* @param {ArrayBuffer} buffer The data packet received.
|
||||||
|
*/
|
||||||
|
this.ondata = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fired once this stream is finished and no further data will be written.
|
||||||
|
* @event
|
||||||
|
*/
|
||||||
|
this.onend = null;
|
||||||
|
|
||||||
|
};
|
128
guacamole-common-js/src/main/webapp/modules/BlobReader.js
Normal file
128
guacamole-common-js/src/main/webapp/modules/BlobReader.js
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
/*
|
||||||
|
* 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 || {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reader which automatically handles the given input stream, assembling all
|
||||||
|
* received blobs into a single blob by appending them to each other in order.
|
||||||
|
* Note that this object will overwrite any installed event handlers on the
|
||||||
|
* given Guacamole.InputStream.
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param {Guacamole.InputStream} stream The stream that data will be read
|
||||||
|
* from.
|
||||||
|
* @param {String} mimetype The mimetype of the blob being built.
|
||||||
|
*/
|
||||||
|
Guacamole.BlobReader = function(stream, mimetype) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to this Guacamole.InputStream.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
var guac_reader = this;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The length of this Guacamole.InputStream in bytes.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
var length = 0;
|
||||||
|
|
||||||
|
// Get blob builder
|
||||||
|
var blob_builder;
|
||||||
|
if (window.BlobBuilder) blob_builder = new BlobBuilder();
|
||||||
|
else if (window.WebKitBlobBuilder) blob_builder = new WebKitBlobBuilder();
|
||||||
|
else if (window.MozBlobBuilder) blob_builder = new MozBlobBuilder();
|
||||||
|
else
|
||||||
|
blob_builder = new (function() {
|
||||||
|
|
||||||
|
var blobs = [];
|
||||||
|
|
||||||
|
/** @ignore */
|
||||||
|
this.append = function(data) {
|
||||||
|
blobs.push(new Blob([data], {"type": mimetype}));
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @ignore */
|
||||||
|
this.getBlob = function() {
|
||||||
|
return new Blob(blobs, {"type": mimetype});
|
||||||
|
};
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Append received blobs
|
||||||
|
stream.onblob = function(data) {
|
||||||
|
|
||||||
|
// Convert to ArrayBuffer
|
||||||
|
var binary = window.atob(data);
|
||||||
|
var arrayBuffer = new ArrayBuffer(binary.length);
|
||||||
|
var bufferView = new Uint8Array(arrayBuffer);
|
||||||
|
|
||||||
|
for (var i=0; i<binary.length; i++)
|
||||||
|
bufferView[i] = binary.charCodeAt(i);
|
||||||
|
|
||||||
|
blob_builder.append(arrayBuffer);
|
||||||
|
length += arrayBuffer.byteLength;
|
||||||
|
|
||||||
|
// Call handler, if present
|
||||||
|
if (guac_reader.onprogress)
|
||||||
|
guac_reader.onprogress(arrayBuffer.byteLength);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// Simply call onend when end received
|
||||||
|
stream.onend = function() {
|
||||||
|
if (guac_reader.onend)
|
||||||
|
guac_reader.onend();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current length of this Guacamole.InputStream, in bytes.
|
||||||
|
* @return {Number} The current length of this Guacamole.InputStream.
|
||||||
|
*/
|
||||||
|
this.getLength = function() {
|
||||||
|
return length;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the contents of this Guacamole.BlobReader as a Blob.
|
||||||
|
* @return {Blob} The contents of this Guacamole.BlobReader.
|
||||||
|
*/
|
||||||
|
this.getBlob = function() {
|
||||||
|
return blob_builder.getBlob();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fired once for every blob of data received.
|
||||||
|
*
|
||||||
|
* @event
|
||||||
|
* @param {Number} length The number of bytes received.
|
||||||
|
*/
|
||||||
|
this.onprogress = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fired once this stream is finished and no further data will be written.
|
||||||
|
* @event
|
||||||
|
*/
|
||||||
|
this.onend = null;
|
||||||
|
|
||||||
|
};
|
@@ -268,38 +268,80 @@ Guacamole.Client = function(tunnel) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens a new file for writing, having the given index, mimetype and
|
* Opens a new file for writing, having the given index, mimetype and
|
||||||
* filename.
|
* filename. The stream to associate with this file must already exist.
|
||||||
*
|
*
|
||||||
* @param {Number} index The index of the file to write to. This index must
|
|
||||||
* be unused.
|
|
||||||
* @param {String} mimetype The mimetype of the file being sent.
|
* @param {String} mimetype The mimetype of the file being sent.
|
||||||
* @param {String} filename The filename of the file being sent.
|
* @param {String} filename The filename of the file being sent.
|
||||||
|
* @return {Guacamole.OutputStream} The created file stream.
|
||||||
*/
|
*/
|
||||||
this.beginFileStream = function(index, mimetype, filename) {
|
this.createFileStream = function(mimetype, filename) {
|
||||||
|
|
||||||
// Do not send requests if not connected
|
// Allocate index
|
||||||
if (!isConnected())
|
var index = stream_indices.next();
|
||||||
return;
|
|
||||||
|
|
||||||
|
// Create new stream
|
||||||
tunnel.sendMessage("file", index, mimetype, filename);
|
tunnel.sendMessage("file", index, mimetype, filename);
|
||||||
|
var stream = output_streams[index] = new Guacamole.OutputStream(guac_client, index);
|
||||||
|
|
||||||
|
// Override sendEnd() of stream to automatically free index
|
||||||
|
var old_end = stream.sendEnd;
|
||||||
|
stream.sendEnd = function() {
|
||||||
|
old_end();
|
||||||
|
stream_indices.free(index);
|
||||||
|
delete output_streams[index];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Return new, overridden stream
|
||||||
|
return stream;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens a new pipe for writing, having the given index, mimetype and
|
* Opens a new pipe for writing, having the given name and mimetype. The
|
||||||
* name.
|
* stream to associate with this pipe must already exist.
|
||||||
*
|
*
|
||||||
* @param {Number} index The index of the pipe to write to. This index must
|
|
||||||
* be unused.
|
|
||||||
* @param {String} mimetype The mimetype of the data being sent.
|
* @param {String} mimetype The mimetype of the data being sent.
|
||||||
* @param {String} name The name of the pipe.
|
* @param {String} name The name of the pipe.
|
||||||
|
* @return {Guacamole.OutputStream} The created file stream.
|
||||||
*/
|
*/
|
||||||
this.beginPipeStream = function(index, mimetype, name) {
|
this.createPipeStream = function(mimetype, name) {
|
||||||
|
|
||||||
|
// Allocate index
|
||||||
|
var index = stream_indices.next();
|
||||||
|
|
||||||
|
// Create new stream
|
||||||
|
tunnel.sendMessage("pipe", index, mimetype, name);
|
||||||
|
var stream = output_streams[index] = new Guacamole.OutputStream(guac_client, index);
|
||||||
|
|
||||||
|
// Override sendEnd() of stream to automatically free index
|
||||||
|
var old_end = stream.sendEnd;
|
||||||
|
stream.sendEnd = function() {
|
||||||
|
old_end();
|
||||||
|
stream_indices.free(index);
|
||||||
|
delete output_streams[index];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Return new, overridden stream
|
||||||
|
return stream;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Acknowledge receipt of a blob on the stream with the given index.
|
||||||
|
*
|
||||||
|
* @param {Number} index The index of the stream associated with the
|
||||||
|
* received blob.
|
||||||
|
* @param {String} message A human-readable message describing the error
|
||||||
|
* or status.
|
||||||
|
* @param {Number} code The error code, if any, or 0 for success.
|
||||||
|
*/
|
||||||
|
this.sendAck = function(index, message, code) {
|
||||||
|
|
||||||
// Do not send requests if not connected
|
// Do not send requests if not connected
|
||||||
if (!isConnected())
|
if (!isConnected())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
tunnel.sendMessage("pipe", index, mimetype, name);
|
tunnel.sendMessage("ack", index, message, code);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -331,63 +373,6 @@ Guacamole.Client = function(tunnel) {
|
|||||||
tunnel.sendMessage("end", index);
|
tunnel.sendMessage("end", index);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens a new file for writing, having the given index, mimetype and
|
|
||||||
* filename.
|
|
||||||
*
|
|
||||||
* @param {String} mimetype The mimetype of the file being sent.
|
|
||||||
* @param {String} filename The filename of the file being sent.
|
|
||||||
*/
|
|
||||||
this.createFileStream = function(mimetype, filename) {
|
|
||||||
|
|
||||||
// Allocate index
|
|
||||||
var index = stream_indices.next();
|
|
||||||
|
|
||||||
// Create new stream
|
|
||||||
guac_client.beginFileStream(index, mimetype, filename);
|
|
||||||
var stream = output_streams[index] = new Guacamole.OutputStream(guac_client, index);
|
|
||||||
|
|
||||||
// Override close() of stream to automatically free index
|
|
||||||
var old_close = stream.close;
|
|
||||||
stream.close = function() {
|
|
||||||
old_close();
|
|
||||||
stream_indices.free(index);
|
|
||||||
delete output_streams[index];
|
|
||||||
};
|
|
||||||
|
|
||||||
// Return new, overridden stream
|
|
||||||
return stream;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens a new pipe for writing, having the given name and mimetype.
|
|
||||||
*
|
|
||||||
* @param {String} mimetype The mimetype of the data being sent.
|
|
||||||
* @param {String} name The name of the pipe.
|
|
||||||
*/
|
|
||||||
this.createPipeStream = function(mimetype, name) {
|
|
||||||
|
|
||||||
// Allocate index
|
|
||||||
var index = stream_indices.next();
|
|
||||||
|
|
||||||
// Create new stream
|
|
||||||
guac_client.beginPipeStream(index, mimetype, name);
|
|
||||||
var stream = output_streams[index] = new Guacamole.OutputStream(guac_client, index);
|
|
||||||
|
|
||||||
// Override close() of stream to automatically free index
|
|
||||||
var old_close = stream.close;
|
|
||||||
stream.close = function() {
|
|
||||||
old_close();
|
|
||||||
stream_indices.free(index);
|
|
||||||
delete output_streams[index];
|
|
||||||
};
|
|
||||||
|
|
||||||
// Return new, overridden stream
|
|
||||||
return stream;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fired whenever the state of this Guacamole.Client changes.
|
* Fired whenever the state of this Guacamole.Client changes.
|
||||||
*
|
*
|
||||||
@@ -434,27 +419,25 @@ Guacamole.Client = function(tunnel) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Fired when a file stream is created. The stream provided to this event
|
* Fired when a file stream is created. The stream provided to this event
|
||||||
* handler will contain its own event handlers for received data and the
|
* handler will contain its own event handlers for received data.
|
||||||
* close event.
|
|
||||||
*
|
*
|
||||||
* @event
|
* @event
|
||||||
|
* @param {Guacamole.InputStream} stream The stream that will receive data
|
||||||
|
* from the server.
|
||||||
* @param {String} mimetype The mimetype of the file received.
|
* @param {String} mimetype The mimetype of the file received.
|
||||||
* @param {String} filename The name of the file received.
|
* @param {String} filename The name of the file received.
|
||||||
* @return {Guacamole.InputStream} The stream that will receive data from
|
|
||||||
* the server.
|
|
||||||
*/
|
*/
|
||||||
this.onfile = null;
|
this.onfile = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fired when a pipe stream is created. The stream provided to this event
|
* Fired when a pipe stream is created. The stream provided to this event
|
||||||
* handler will contain its own event handlers for received data and the
|
* handler will contain its own event handlers for received data;
|
||||||
* close event.
|
|
||||||
*
|
*
|
||||||
* @event
|
* @event
|
||||||
|
* @param {Guacamole.InputStream} stream The stream that will receive data
|
||||||
|
* from the server.
|
||||||
* @param {String} mimetype The mimetype of the data which will be received.
|
* @param {String} mimetype The mimetype of the data which will be received.
|
||||||
* @param {String} name The name of the pipe.
|
* @param {String} name The name of the pipe.
|
||||||
* @return {Guacamole.InputStream} The stream that will receive data from
|
|
||||||
* the server.
|
|
||||||
*/
|
*/
|
||||||
this.onpipe = null;
|
this.onpipe = null;
|
||||||
|
|
||||||
@@ -579,21 +562,16 @@ Guacamole.Client = function(tunnel) {
|
|||||||
var stream = output_streams[stream_index];
|
var stream = output_streams[stream_index];
|
||||||
if (stream) {
|
if (stream) {
|
||||||
|
|
||||||
|
// Signal ack if handler defined
|
||||||
|
if (stream.onack)
|
||||||
|
stream.onack(reason, code);
|
||||||
|
|
||||||
// If code is an error, invalidate stream
|
// If code is an error, invalidate stream
|
||||||
if (code >= 0x0100) {
|
if (code >= 0x0100) {
|
||||||
|
|
||||||
// Signal error
|
|
||||||
if (stream.onerror)
|
|
||||||
stream.onerror(reason, code);
|
|
||||||
|
|
||||||
stream_indices.free(stream_index);
|
stream_indices.free(stream_index);
|
||||||
delete output_streams[stream_index];
|
delete output_streams[stream_index];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signal error if handler defined
|
|
||||||
else if (stream.onack)
|
|
||||||
stream.onack(reason, code);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
@@ -621,14 +599,18 @@ Guacamole.Client = function(tunnel) {
|
|||||||
|
|
||||||
// Create stream
|
// Create stream
|
||||||
var stream = streams[stream_index] =
|
var stream = streams[stream_index] =
|
||||||
new Guacamole.InputStream(mimetype);
|
new Guacamole.InputStream(guac_client, stream_index);
|
||||||
|
|
||||||
stream.onclose = function() {
|
// Assemble entire stream as a blob
|
||||||
channel.play(mimetype, duration, stream.getBlob());
|
var blob_reader = new Guacamole.BlobReader(stream, mimetype);
|
||||||
|
|
||||||
|
// Play blob as audio
|
||||||
|
blob_reader.onend = function() {
|
||||||
|
channel.play(mimetype, duration, blob_reader.getBlob());
|
||||||
};
|
};
|
||||||
|
|
||||||
// Send success response
|
// Send success response
|
||||||
tunnel.sendMessage("ack", stream_index, "OK", 0x0000);
|
guac_client.sendAck(stream_index, "OK", 0x0000);
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -640,10 +622,10 @@ Guacamole.Client = function(tunnel) {
|
|||||||
var stream = streams[stream_index];
|
var stream = streams[stream_index];
|
||||||
|
|
||||||
// Write data
|
// Write data
|
||||||
stream.receive(data);
|
stream.onblob(data);
|
||||||
|
|
||||||
// Send success response
|
// Send success response
|
||||||
tunnel.sendMessage("ack", stream_index, "OK", 0x0000);
|
guac_client.sendAck(stream_index, "OK", 0x0000);
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -833,8 +815,9 @@ Guacamole.Client = function(tunnel) {
|
|||||||
var stream_index = parseInt(parameters[0]);
|
var stream_index = parseInt(parameters[0]);
|
||||||
var stream = streams[stream_index];
|
var stream = streams[stream_index];
|
||||||
|
|
||||||
// Close stream
|
// Signal end of stream
|
||||||
stream.close();
|
if (stream.onend)
|
||||||
|
stream.onend();
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -846,23 +829,13 @@ Guacamole.Client = function(tunnel) {
|
|||||||
|
|
||||||
// Create stream
|
// Create stream
|
||||||
if (guac_client.onfile) {
|
if (guac_client.onfile) {
|
||||||
|
var stream = streams[stream_index] = new Guacamole.InputStream(guac_client, stream_index);
|
||||||
// Attempt to create stream
|
guac_client.onfile(stream, mimetype, filename);
|
||||||
var stream = guac_client.onfile(mimetype, filename);
|
|
||||||
if (stream) {
|
|
||||||
streams[stream_index] = stream;
|
|
||||||
tunnel.sendMessage("ack", stream_index, "OK", 0x0000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notify if creation failed
|
|
||||||
else
|
|
||||||
tunnel.sendMessage("ack", stream_index, "Unable to receive file", 0x0201);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, unsupported
|
// Otherwise, unsupported
|
||||||
else
|
else
|
||||||
tunnel.sendMessage("ack", stream_index, "File transfer unsupported", 0x0100);
|
guac_client.sendAck(stream_index, "File transfer unsupported", 0x0100);
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -947,23 +920,13 @@ Guacamole.Client = function(tunnel) {
|
|||||||
|
|
||||||
// Create stream
|
// Create stream
|
||||||
if (guac_client.onpipe) {
|
if (guac_client.onpipe) {
|
||||||
|
var stream = streams[stream_index] = new Guacamole.InputStream(guac_client, stream_index);
|
||||||
// Attempt to create stream
|
guac_client.onpipe(stream, mimetype, name);
|
||||||
var stream = guac_client.onpipe(mimetype, name);
|
|
||||||
if (stream) {
|
|
||||||
streams[stream_index] = stream;
|
|
||||||
tunnel.sendMessage("ack", stream_index, "OK", 0x0000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notify if creation failed
|
|
||||||
else
|
|
||||||
tunnel.sendMessage("ack", stream_index, "Unable to create pipe", 0x0201);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, unsupported
|
// Otherwise, unsupported
|
||||||
else
|
else
|
||||||
tunnel.sendMessage("ack", stream_index, "Named pipes unsupported", 0x0100);
|
guac_client.sendAck(stream_index, "Named pipes unsupported", 0x0100);
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1227,10 +1190,13 @@ Guacamole.Client = function(tunnel) {
|
|||||||
|
|
||||||
// Create stream
|
// Create stream
|
||||||
var stream = streams[stream_index] =
|
var stream = streams[stream_index] =
|
||||||
new Guacamole.InputStream(mimetype);
|
new Guacamole.InputStream(guac_client, stream_index);
|
||||||
|
|
||||||
// Play video once closed
|
// Assemble entire stream as a blob
|
||||||
stream.onclose = function() {
|
var blob_reader = new Guacamole.BlobReader(stream, mimetype);
|
||||||
|
|
||||||
|
// Play video once finished
|
||||||
|
blob_reader.onend = function() {
|
||||||
|
|
||||||
// Read data from blob from stream
|
// Read data from blob from stream
|
||||||
var reader = new FileReader();
|
var reader = new FileReader();
|
||||||
@@ -1247,7 +1213,7 @@ Guacamole.Client = function(tunnel) {
|
|||||||
layer.play(mimetype, duration, "data:" + mimetype + ";base64," + window.btoa(binary));
|
layer.play(mimetype, duration, "data:" + mimetype + ";base64," + window.btoa(binary));
|
||||||
|
|
||||||
};
|
};
|
||||||
reader.readAsArrayBuffer(stream.getBlob());
|
reader.readAsArrayBuffer(blob_reader.getBlob());
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -27,266 +27,47 @@ var Guacamole = Guacamole || {};
|
|||||||
* transfer of files or other binary data.
|
* transfer of files or other binary data.
|
||||||
*
|
*
|
||||||
* @constructor
|
* @constructor
|
||||||
* @param {String} mimetype The mimetype of the data this stream will receive.
|
* @param {Guacamole.Client} client The client owning this stream.
|
||||||
|
* @param {Number} index The index of this stream.
|
||||||
*/
|
*/
|
||||||
Guacamole.InputStream = function(mimetype) {
|
Guacamole.InputStream = function(client, index) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The mimetype of the data contained within this blob.
|
* Reference to this stream.
|
||||||
|
* @private
|
||||||
*/
|
*/
|
||||||
this.mimetype = mimetype;
|
var guac_stream = this;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Receives the given base64-encoded data.
|
* The index of this stream.
|
||||||
|
* @type Number
|
||||||
|
*/
|
||||||
|
this.index = index;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a blob of data is received.
|
||||||
*
|
*
|
||||||
|
* @event
|
||||||
* @param {String} data The received base64 data.
|
* @param {String} data The received base64 data.
|
||||||
*/
|
*/
|
||||||
this.receive = function(data) {};
|
this.onblob = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes this Guacamole.InputStream such that no further data will be
|
* Called when this stream is closed.
|
||||||
* written.
|
|
||||||
*/
|
|
||||||
this.close = function() {};
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An input stream which receives all data packets as individual ArrayBuffer
|
|
||||||
* objects.
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @param {String} mimetype The mimetype of the data this stream will receive.
|
|
||||||
*/
|
|
||||||
Guacamole.ArrayBufferInputStream = function(mimetype) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reference to this Guacamole.InputStream.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var guac_stream = this;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This is an input stream.
|
|
||||||
*/
|
|
||||||
Guacamole.InputStream.apply(this, [mimetype]);
|
|
||||||
|
|
||||||
// Receive implementation
|
|
||||||
this.receive = function(data) {
|
|
||||||
|
|
||||||
// Convert to ArrayBuffer
|
|
||||||
var binary = window.atob(data);
|
|
||||||
var arrayBuffer = new ArrayBuffer(binary.length);
|
|
||||||
var bufferView = new Uint8Array(arrayBuffer);
|
|
||||||
|
|
||||||
for (var i=0; i<binary.length; i++)
|
|
||||||
bufferView[i] = binary.charCodeAt(i);
|
|
||||||
|
|
||||||
// Call handler, if present
|
|
||||||
if (guac_stream.onreceive)
|
|
||||||
guac_stream.onreceive(arrayBuffer);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
// Close implementation
|
|
||||||
this.close = function() {
|
|
||||||
|
|
||||||
// Call handler, if present
|
|
||||||
if (guac_stream.onclose)
|
|
||||||
guac_stream.onclose();
|
|
||||||
|
|
||||||
// NOTE: Currently not enforced.
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fired once for every blob of data received.
|
|
||||||
*
|
*
|
||||||
* @event
|
* @event
|
||||||
* @param {ArrayBuffer} buffer The data packet received.
|
|
||||||
*/
|
*/
|
||||||
this.onreceive = null;
|
this.onend = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fired once this stream is finished and no further data will be written.
|
* Acknowledges the receipt of a blob.
|
||||||
* @event
|
|
||||||
*/
|
|
||||||
this.onclose = null;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
Guacamole.ArrayBufferInputStream.prototype = new Guacamole.InputStream();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An input stream which continuously builds a single blob by appending each
|
|
||||||
* individual blob received. Only the size of each blob received is exposed.
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @augments Guacamole.InputStream
|
|
||||||
* @param {String} mimetype The mimetype of the data this stream will receive.
|
|
||||||
*/
|
|
||||||
Guacamole.BlobInputStream = function(mimetype) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reference to this Guacamole.InputStream.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var guac_stream = this;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The length of this Guacamole.InputStream in bytes.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var length = 0;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This is an input stream.
|
|
||||||
*/
|
|
||||||
Guacamole.InputStream.apply(this, [mimetype]);
|
|
||||||
|
|
||||||
// Get blob builder
|
|
||||||
var blob_builder;
|
|
||||||
if (window.BlobBuilder) blob_builder = new BlobBuilder();
|
|
||||||
else if (window.WebKitBlobBuilder) blob_builder = new WebKitBlobBuilder();
|
|
||||||
else if (window.MozBlobBuilder) blob_builder = new MozBlobBuilder();
|
|
||||||
else
|
|
||||||
blob_builder = new (function() {
|
|
||||||
|
|
||||||
var blobs = [];
|
|
||||||
|
|
||||||
/** @ignore */
|
|
||||||
this.append = function(data) {
|
|
||||||
blobs.push(new Blob([data], {"type": mimetype}));
|
|
||||||
};
|
|
||||||
|
|
||||||
/** @ignore */
|
|
||||||
this.getBlob = function() {
|
|
||||||
return new Blob(blobs, {"type": mimetype});
|
|
||||||
};
|
|
||||||
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Receive implementation
|
|
||||||
this.receive = function(data) {
|
|
||||||
|
|
||||||
// Convert to ArrayBuffer
|
|
||||||
var binary = window.atob(data);
|
|
||||||
var arrayBuffer = new ArrayBuffer(binary.length);
|
|
||||||
var bufferView = new Uint8Array(arrayBuffer);
|
|
||||||
|
|
||||||
for (var i=0; i<binary.length; i++)
|
|
||||||
bufferView[i] = binary.charCodeAt(i);
|
|
||||||
|
|
||||||
blob_builder.append(arrayBuffer);
|
|
||||||
length += arrayBuffer.byteLength;
|
|
||||||
|
|
||||||
// Call handler, if present
|
|
||||||
if (guac_stream.onprogress)
|
|
||||||
guac_stream.onprogress(arrayBuffer.byteLength);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
// Close implementation
|
|
||||||
this.close = function() {
|
|
||||||
|
|
||||||
// Call handler, if present
|
|
||||||
if (guac_stream.onclose)
|
|
||||||
guac_stream.onclose();
|
|
||||||
|
|
||||||
// NOTE: Currently not enforced.
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current length of this Guacamole.InputStream, in bytes.
|
|
||||||
* @return {Number} The current length of this Guacamole.InputStream.
|
|
||||||
*/
|
|
||||||
this.getLength = function() {
|
|
||||||
return length;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the contents of this Guacamole.InputStream as a Blob.
|
|
||||||
* @return {Blob} The contents of this Guacamole.InputStream.
|
|
||||||
*/
|
|
||||||
this.getBlob = function() {
|
|
||||||
return blob_builder.getBlob();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fired once for every blob of data received.
|
|
||||||
*
|
*
|
||||||
* @event
|
* @param {String} message A human-readable message describing the error
|
||||||
* @param {Number} length The number of bytes received.
|
* or status.
|
||||||
|
* @param {Number} code The error code, if any, or 0 for success.
|
||||||
*/
|
*/
|
||||||
this.onprogress = null;
|
this.sendAck = function(message, code) {
|
||||||
|
client.sendAck(guac_stream.index, message, code);
|
||||||
/**
|
|
||||||
* Fired once this stream is finished and no further data will be written.
|
|
||||||
* @event
|
|
||||||
*/
|
|
||||||
this.onclose = null;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
Guacamole.BlobInputStream.prototype = new Guacamole.InputStream();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An input stream which receives strictly text data.
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @param {String} mimetype The mimetype of the data this stream will receive.
|
|
||||||
*/
|
|
||||||
Guacamole.StringInputStream = function(mimetype) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reference to this Guacamole.InputStream.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
var guac_stream = this;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This is an input stream.
|
|
||||||
*/
|
|
||||||
Guacamole.InputStream.apply(this, [mimetype]);
|
|
||||||
|
|
||||||
// Receive implementation
|
|
||||||
this.receive = function(data) {
|
|
||||||
|
|
||||||
// Convert to string
|
|
||||||
var text = window.atob(data);
|
|
||||||
|
|
||||||
// Call handler, if present
|
|
||||||
if (guac_stream.onreceive)
|
|
||||||
guac_stream.onreceive(text);
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Close implementation
|
|
||||||
this.close = function() {
|
|
||||||
|
|
||||||
// Call handler, if present
|
|
||||||
if (guac_stream.onclose)
|
|
||||||
guac_stream.onclose();
|
|
||||||
|
|
||||||
// NOTE: Currently not enforced.
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fired once for every blob of data received.
|
|
||||||
*
|
|
||||||
* @event
|
|
||||||
* @param {String} text The data packet received.
|
|
||||||
*/
|
|
||||||
this.onreceive = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fired once this stream is finished and no further data will be written.
|
|
||||||
* @event
|
|
||||||
*/
|
|
||||||
this.onclose = null;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Guacamole.StringInputStream.prototype = new Guacamole.InputStream();
|
|
||||||
|
@@ -43,17 +43,9 @@ Guacamole.OutputStream = function(client, index) {
|
|||||||
*/
|
*/
|
||||||
this.index = index;
|
this.index = index;
|
||||||
|
|
||||||
/**
|
|
||||||
* Fired when the stream is being closed due to an error.
|
|
||||||
*
|
|
||||||
* @param {String} reason A human-readable reason describing the error.
|
|
||||||
* @param {Number} code The error code associated with the error.
|
|
||||||
*/
|
|
||||||
this.onerror = null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fired whenever an acknowledgement is received from the server, indicating
|
* Fired whenever an acknowledgement is received from the server, indicating
|
||||||
* that a stream operation has completed.
|
* that a stream operation has completed, or an error has occurred.
|
||||||
*
|
*
|
||||||
* @event
|
* @event
|
||||||
* @param {String} message A human-readable status message related to the
|
* @param {String} message A human-readable status message related to the
|
||||||
@@ -67,14 +59,14 @@ Guacamole.OutputStream = function(client, index) {
|
|||||||
*
|
*
|
||||||
* @param {String} data The base64-encoded data to send.
|
* @param {String} data The base64-encoded data to send.
|
||||||
*/
|
*/
|
||||||
this.write = function(data) {
|
this.sendBlob = function(data) {
|
||||||
client.sendBlob(guac_stream.index, data);
|
client.sendBlob(guac_stream.index, data);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes this stream.
|
* Closes this stream.
|
||||||
*/
|
*/
|
||||||
this.close = function() {
|
this.sendEnd = function() {
|
||||||
client.endStream(guac_stream.index);
|
client.endStream(guac_stream.index);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
74
guacamole-common-js/src/main/webapp/modules/StringReader.js
Normal file
74
guacamole-common-js/src/main/webapp/modules/StringReader.js
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* 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 || {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reader which automatically handles the given input stream, returning
|
||||||
|
* strictly text data. Note that this object will overwrite any installed event
|
||||||
|
* handlers on the given Guacamole.InputStream.
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param {Guacamole.InputStream} stream The stream that data will be read
|
||||||
|
* from.
|
||||||
|
*/
|
||||||
|
Guacamole.StringReader = function(stream) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to this Guacamole.InputStream.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
var guac_reader = this;
|
||||||
|
|
||||||
|
// Receive blobs as strings
|
||||||
|
stream.onblob = function(data) {
|
||||||
|
|
||||||
|
// Convert to string
|
||||||
|
var text = window.atob(data);
|
||||||
|
|
||||||
|
// Call handler, if present
|
||||||
|
if (guac_reader.ontext)
|
||||||
|
guac_reader.ontext(text);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// Simply call onend when end received
|
||||||
|
stream.onend = function() {
|
||||||
|
if (guac_reader.onend)
|
||||||
|
guac_reader.onend();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fired once for every blob of text data received.
|
||||||
|
*
|
||||||
|
* @event
|
||||||
|
* @param {String} text The data packet received.
|
||||||
|
*/
|
||||||
|
this.ontext = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fired once this stream is finished and no further data will be written.
|
||||||
|
* @event
|
||||||
|
*/
|
||||||
|
this.onend = null;
|
||||||
|
|
||||||
|
};
|
@@ -815,9 +815,7 @@ GuacUI.Client.attach = function(guac) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
guac.onfile = function(mimetype, filename) {
|
guac.onfile = function(stream, mimetype, filename) {
|
||||||
|
|
||||||
var stream = new Guacamole.BlobInputStream(mimetype);
|
|
||||||
|
|
||||||
var download = new GuacUI.Download(filename);
|
var download = new GuacUI.Download(filename);
|
||||||
download.updateProgress(getSizeString(0));
|
download.updateProgress(getSizeString(0));
|
||||||
@@ -845,8 +843,6 @@ GuacUI.Client.attach = function(guac) {
|
|||||||
GuacUI.Client.notification_area.removeChild(download.getElement());
|
GuacUI.Client.notification_area.removeChild(download.getElement());
|
||||||
};
|
};
|
||||||
|
|
||||||
return stream;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
Reference in New Issue
Block a user