From 2770ef3a37961ff00b355826829c031b55a79493 Mon Sep 17 00:00:00 2001 From: James Muehlner Date: Tue, 30 Jul 2024 23:03:24 +0000 Subject: [PATCH] GUACAMOLE-1974: Allow deferring received pipe streams for later consumption by name-specific handlers. --- .../src/app/client/types/ManagedClient.js | 140 +++++++++++++++++- 1 file changed, 139 insertions(+), 1 deletion(-) diff --git a/guacamole/src/main/frontend/src/app/client/types/ManagedClient.js b/guacamole/src/main/frontend/src/app/client/types/ManagedClient.js index 01769ea94f..b7e83a3a05 100644 --- a/guacamole/src/main/frontend/src/app/client/types/ManagedClient.js +++ b/guacamole/src/main/frontend/src/app/client/types/ManagedClient.js @@ -62,12 +62,49 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', */ var THUMBNAIL_UPDATE_FREQUENCY = 5000; + /** + * A deferred pipe stream, that has yet to be consumed, as well as all + * axuilary information needed to pull data from the stream. + * + * @constructor + * @param {DeferredPipeStream|Object} [template={}] + * The object whose properties should be copied within the new + * DeferredPipeStream. + */ + var DeferredPipeStream = function DeferredPipeStream(template) { + + // Use empty object by default + template = template || {}; + + /** + * The stream that will receive data from the server. + * + * @type Guacamole.InputStream + */ + this.stream = template.stream; + + /** + * The mimetype of the data which will be received. + * + * @type String + */ + this.mimetype = template.mimetype; + + /** + * The name of the pipe. + * + * @type String + */ + this.name = template.name; + + }; + /** * Object which serves as a surrogate interface, encapsulating a Guacamole * client while it is active, allowing it to be maintained in the * background. One or more ManagedClients are grouped within * ManagedClientGroups before being attached to the client view. - * + * * @constructor * @param {ManagedClient|Object} [template={}] * The object whose properties should be copied within the new @@ -240,6 +277,22 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', */ this.arguments = template.arguments || {}; + /** + * Any received pipe streams that have not been consumed by an onpipe + * handler or registered pipe handler, indexed by pipe stream name. + * + * @type {Object.} + */ + this.deferredPipeStreams = template.deferredPipeStreams || {}; + + /** + * Handlers for deferred pipe streams, indexed by the name of the pipe + * stream that the handler should handle. + * + * @type {Object.} + */ + this.deferredPipeStreamHandlers = template.deferredPipeStreamHandlers || {}; + }; /** @@ -553,6 +606,25 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', }; + // A default onpipe implementation that will automatically defer any + // received pipe streams, automatically invoking any registered handlers + // that may already be set for the received name + client.onpipe = (stream, mimetype, name) => { + + // Defer the pipe stream + managedClient.deferredPipeStreams[name] = new DeferredPipeStream( + { stream, mimetype, name }); + + // Invoke the handler now, if set + const handler = managedClient.deferredPipeStreamHandlers[name]; + if (handler) { + + // Handle the stream, and clear from the deferred streams + handler(stream, mimetype, name); + delete managedClient.deferredPipeStreams[name]; + } + }; + // Test for argument mutability whenever an argument value is // received client.onargv = function clientArgumentValueReceived(stream, mimetype, name) { @@ -1004,6 +1076,72 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', }; + + /** + * Register a handler that will be automatically invoked for any deferred + * pipe stream with the provided name, either when a pipe stream with a + * name matching a registered handler is received, or immediately when this + * function is called, if such a pipe stream has already been received. + * + * NOTE: Pipe streams are automatically deferred by the default onpipe + * implementation. To preserve this behavior when using a custom onpipe + * callback, make sure to defer to the default implementation as needed. + * + * @param {ManagedClient} managedClient + * The client for which the deferred pipe stream handler should be set. + * + * @param {String} name + * The name of the pipe stream that should be handeled by the provided + * handler. If another handler is already registered for this name, it + * will be replaced by the handler provided to this function. + * + * @param {Function} handler + * The handler that should handle any deferred pipe stream with the + * provided name. This function must take the same arguments as the + * standard onpipe handler - namely, the stream itself, the mimetype, + * and the name. + */ + ManagedClient.registerDeferredPipeHandler = function registerDeferredPipeHandler( + managedClient, name, handler) { + managedClient.deferredPipeStreamHandlers[name] = handler; + + // Invoke the handler now, if the pipestream has already been received + if (managedClient.deferredPipeStreams[name]) { + + // Invoke the handler with the deferred pipe stream + var deferredStream = managedClient.deferredPipeStreams[name]; + handler(deferredStream.stream, + deferredStream.mimetype, + deferredStream.name); + + // Clean up the now-consumed pipe stream + delete managedClient.deferredPipeStreams[name]; + } + }; + + /** + * Detach the provided deferred pipe stream handler, if it is currently + * registered for the provided pipe stream name. + * + * @param {String} name + * The name of the associated pipe stream for the handler that should + * be detached. + * + * @param {Function} handler + * The handler that should be detached. + * + * @param {ManagedClient} managedClient + * The client for which the deferred pipe stream handler should be + * detached. + */ + ManagedClient.detachDeferredPipeHandler = function detachDeferredPipeHandler( + managedClient, name, handler) { + + // Remove the handler if found + if (managedClient.deferredPipeStreamHandlers[name] === handler) + delete managedClient.deferredPipeStreamHandlers[name]; + }; + return ManagedClient; }]);