diff --git a/guacamole/src/main/webapp/app/client/types/ManagedClient.js b/guacamole/src/main/webapp/app/client/types/ManagedClient.js index 26fe249b1..2e8a61857 100644 --- a/guacamole/src/main/webapp/app/client/types/ManagedClient.js +++ b/guacamole/src/main/webapp/app/client/types/ManagedClient.js @@ -31,6 +31,7 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', var ManagedClientState = $injector.get('ManagedClientState'); var ManagedDisplay = $injector.get('ManagedDisplay'); var ManagedFileDownload = $injector.get('ManagedFileDownload'); + var ManagedFilesystem = $injector.get('ManagedFilesystem'); var ManagedFileUpload = $injector.get('ManagedFileUpload'); // Required services @@ -119,6 +120,15 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', */ this.uploads = template.uploads || []; + /** + * All currently-exposed filesystems. When the Guacamole server exposes + * a filesystem object, that object will be made available as a + * ManagedFilesystem within this array. + * + * @type ManagedFilesystem[] + */ + this.filesystems = template.filesystems || []; + /** * The current state of the Guacamole client (idle, connecting, * connected, terminated with error, etc.). @@ -382,6 +392,13 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', }); }; + // Handle any received filesystem objects + client.onfilesystem = function fileSystemReceived(object, name) { + $rootScope.$apply(function exposeFilesystem() { + managedClient.filesystems.push(ManagedFilesystem.getInstance(object, name)); + }); + }; + // Manage the client display managedClient.managedDisplay = ManagedDisplay.getInstance(client.getDisplay()); diff --git a/guacamole/src/main/webapp/app/client/types/ManagedFilesystem.js b/guacamole/src/main/webapp/app/client/types/ManagedFilesystem.js new file mode 100644 index 000000000..2c58d5d74 --- /dev/null +++ b/guacamole/src/main/webapp/app/client/types/ManagedFilesystem.js @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2014 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. + */ + +/** + * Provides the ManagedFilesystem class used by ManagedClient to represent + * available remote filesystems. + */ +angular.module('client').factory('ManagedFilesystem', ['$rootScope', '$injector', + function defineManagedFilesystem($rootScope, $injector) { + + /** + * Object which serves as a surrogate interface, encapsulating a Guacamole + * filesystem object while it is active, allowing it to be detached and + * reattached from different client views. + * + * @constructor + * @param {ManagedFilesystem|Object} [template={}] + * The object whose properties should be copied within the new + * ManagedFilesystem. + */ + var ManagedFilesystem = function ManagedFilesystem(template) { + + // Use empty object by default + template = template || {}; + + /** + * The Guacamole filesystem object, as received via a "filesystem" + * instruction. + * + * @type Guacamole.Object + */ + this.object = template.object; + + /** + * The declared, human-readable name of the filesystem + * + * @type String + */ + this.name = template.name; + + /** + * The root directory of the filesystem. + * + * @type ManagedFilesystem.File + */ + this.root = template.root; + + }; + + /** + * Refreshes the contents of the given file, if that file is a directory. + * Only the immediate children of the file are refreshed. Files further + * down the directory tree are not refreshed. + * + * @param {ManagedFilesystem} filesystem + * The filesystem associated with the file being refreshed. + * + * @param {ManagedFilesystem.File} file + * The file being refreshed. + */ + ManagedFilesystem.refresh = function updateDirectory(filesystem, file) { + + // Do not attempt to refresh the contents of directories + if (file.mimetype !== Guacamole.Object.STREAM_INDEX_MIMETYPE) + return; + + // Request contents of given file + filesystem.object.requestInputStream(file.streamName, function handleStream(stream, mimetype) { + + // Ignore stream if mimetype is wrong + if (mimetype !== Guacamole.Object.STREAM_INDEX_MIMETYPE) { + stream.sendAck('Unexpected mimetype', Guacamole.Status.Code.UNSUPPORTED); + return; + } + + // Signal server that data is ready to be received + stream.sendAck('Ready', Guacamole.Status.Code.SUCCESS); + + // Read stream as JSON + var reader = new Guacamole.JSONReader(stream); + + // Acknowledge received JSON blobs + reader.onprogress = function onprogress() { + stream.sendAck("Received", Guacamole.Status.Code.SUCCESS); + }; + + // Reset contents of directory + reader.onend = function jsonReady() { + $rootScope.$evalAsync(function updateFileContents() { + + // Empty contents + file.files = {}; + + // Determine the expected filename prefix of each stream + var expectedPrefix = file.streamName; + if (expectedPrefix.charAt(expectedPrefix.length - 1) !== '/') + expectedPrefix += '/'; + + // For each received stream name + var mimetypes = reader.getJSON(); + for (var name in mimetypes) { + + // Assert prefix is correct + if (name.substring(0, expectedPrefix.length) !== expectedPrefix) + continue; + + // Extract filename from stream name + var filename = name.substring(expectedPrefix.length); + + // Deduce type from mimetype + var type = ManagedFilesystem.File.Type.NORMAL; + if (mimetypes[name] === Guacamole.Object.STREAM_INDEX_MIMETYPE) + type = ManagedFilesystem.File.Type.DIRECTORY; + + // Add file entry + file.files[filename] = new ManagedFilesystem.File({ + mimetype : mimetypes[name], + streamName : name, + type : type, + name : filename + }); + + } + + }); + }; + + }); + + }; + + /** + * Creates a new ManagedFilesystem instance from the given Guacamole.Object + * and human-readable name. Upon creation, a request to populate the + * contents of the root directory will be automatically dispatched. + * + * @param {Guacamole.Object} object + * The Guacamole.Object defining the filesystem. + * + * @param {String} name + * A human-readable name for the filesystem. + * + * @returns {ManagedFilesystem} + * The newly-created ManagedFilesystem. + */ + ManagedFilesystem.getInstance = function getInstance(object, name) { + + // Init new filesystem object + var managedFilesystem = new ManagedFilesystem({ + object : object, + name : name, + root : new ManagedFilesystem.File({ + mimetype : Guacamole.Object.STREAM_INDEX_MIMETYPE, + streamName : Guacamole.Object.ROOT_STREAM, + type : ManagedFilesystem.File.Type.DIRECTORY + }) + }); + + // Retrieve contents of root + ManagedFilesystem.refresh(managedFilesystem, managedFilesystem.root); + + return managedFilesystem; + + }; + + /** + * A file within a ManagedFilesystem. Each ManagedFilesystem.File provides + * sufficient information for retrieval or replacement of the file's + * contents, as well as the file's name and type. + * + * @param {ManagedFilesystem|Object} [template={}] + * The object whose properties should be copied within the new + * ManagedFilesystem.File. + */ + ManagedFilesystem.File = function File(template) { + + /** + * The mimetype of the data contained within this file. + * + * @type String + */ + this.mimetype = template.mimetype; + + /** + * The name of the stream representing this files contents within its + * associated filesystem object. + * + * @type String + */ + this.streamName = template.streamName; + + /** + * The type of this file. All legal file type strings are defined + * within ManagedFilesystem.File.Type. + * + * @type String + */ + this.type = template.type; + + /** + * The name of this file. + * + * @type String + */ + this.name = template.name; + + /** + * Map of all known files containined within this file by name. This is + * only applicable to directories. + * + * @type Object. + */ + this.files = template.files || {}; + + }; + + /** + * All legal type strings for a ManagedFilesystem.File. + * + * @type Object. + */ + ManagedFilesystem.File.Type = { + + /** + * A normal file. As ManagedFilesystem does not currently represent any + * other non-directory types of files, like symbolic links, this type + * string may be used for any non-directory file. + * + * @type String + */ + NORMAL : 'NORMAL', + + /** + * A directory. + * + * @type String + */ + DIRECTORY : 'DIRECTORY' + + }; + + return ManagedFilesystem; + +}]);