diff --git a/guacamole/src/main/webapp/app/client/controllers/clientController.js b/guacamole/src/main/webapp/app/client/controllers/clientController.js index 37bcad99d..03449a732 100644 --- a/guacamole/src/main/webapp/app/client/controllers/clientController.js +++ b/guacamole/src/main/webapp/app/client/controllers/clientController.js @@ -559,67 +559,6 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams }; - // Mapping of download stream index to notification object - var downloadNotifications = {}; - - // Mapping of download stream index to notification ID - var downloadNotificationIDs = {}; - - $scope.$on('guacClientFileDownloadStart', function handleClientFileDownloadStart(event, guacClient, streamIndex, mimetype, filename) { - $scope.$apply(function() { - - var notification = { - className : 'download', - title : 'CLIENT.DIALOG_TITLE_FILE_TRANSFER', - text : filename - }; - - downloadNotifications[streamIndex] = notification; - downloadNotificationIDs[streamIndex] = $scope.addNotification(notification); - - }); - }); - - $scope.$on('guacClientFileDownloadProgress', function handleClientFileDownloadProgress(event, guacClient, streamIndex, mimetype, filename, length) { - $scope.$apply(function() { - - var notification = downloadNotifications[streamIndex]; - if (notification) - notification.progress = getFileProgress('CLIENT.TEXT_FILE_TRANSFER_PROGRESS', length); - - }); - }); - - $scope.$on('guacClientFileDownloadEnd', function handleClientFileDownloadEnd(event, guacClient, streamIndex, mimetype, filename, blob) { - $scope.$apply(function() { - - var notification = downloadNotifications[streamIndex]; - var notificationID = downloadNotificationIDs[streamIndex]; - - /** - * Saves the current file. - */ - var saveFile = function saveFile() { - saveAs(blob, filename); - $scope.removeNotification(notificationID); - delete downloadNotifications[streamIndex]; - delete downloadNotificationIDs[streamIndex]; - }; - - // Add download action and remove progress indicator - if (notificationID && notification) { - delete notification.progress; - notification.actions = [ - { - name : 'CLIENT.ACTION_SAVE_FILE', - callback : saveFile - } - ]; - } - - }); - }); - // Clean up when view destroyed $scope.$on('$destroy', function clientViewDestroyed() { diff --git a/guacamole/src/main/webapp/app/client/types/ManagedClient.js b/guacamole/src/main/webapp/app/client/types/ManagedClient.js index 75116e6f9..8c53d7b68 100644 --- a/guacamole/src/main/webapp/app/client/types/ManagedClient.js +++ b/guacamole/src/main/webapp/app/client/types/ManagedClient.js @@ -27,10 +27,11 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', function defineManagedClient($rootScope, $injector) { // Required types - var ClientProperties = $injector.get('ClientProperties'); - var ManagedClientState = $injector.get('ManagedClientState'); - var ManagedDisplay = $injector.get('ManagedDisplay'); - var ManagedFileUpload = $injector.get('ManagedFileUpload'); + var ClientProperties = $injector.get('ClientProperties'); + var ManagedClientState = $injector.get('ManagedClientState'); + var ManagedDisplay = $injector.get('ManagedDisplay'); + var ManagedFileDownload = $injector.get('ManagedFileDownload'); + var ManagedFileUpload = $injector.get('ManagedFileUpload'); // Required services var $window = $injector.get('$window'); @@ -100,6 +101,15 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', */ this.clipboardData = template.clipboardData; + /** + * All downloaded files. As files are downloaded, their progress can be + * observed through the elements of this array. It is intended that + * this array be manipulated externally as needed. + * + * @type ManagedFileDownload[] + */ + this.downloads = template.downloads || []; + /** * All uploaded files. As files are uploaded, their progress can be * observed through the elements of this array. It is intended that @@ -366,43 +376,16 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', }; + // Handle any received files + client.onfile = function clientFileReceived(stream, mimetype, filename) { + $rootScope.$apply(function startDownload() { + managedClient.downloads.push(ManagedFileDownload.getInstance(stream, mimetype, filename)); + }); + }; + // Manage the client display managedClient.managedDisplay = ManagedDisplay.getInstance(client.getDisplay()); - /* TODO: Restore file transfer again */ - - /* - // Handle any received files - client.onfile = function onClientFile(stream, mimetype, filename) { - - // Begin file download - var guacFileStartEvent = $rootScope.$emit('guacClientFileDownloadStart', client, stream.index, mimetype, filename); - if (!guacFileStartEvent.defaultPrevented) { - - var blob_reader = new Guacamole.BlobReader(stream, mimetype); - - // Update progress as data is received - blob_reader.onprogress = function onprogress() { - $rootScope.$emit('guacClientFileDownloadProgress', client, stream.index, mimetype, filename, blob_reader.getLength()); - stream.sendAck("Received", Guacamole.Status.Code.SUCCESS); - }; - - // When complete, prompt for download - blob_reader.onend = function onend() { - $rootScope.$emit('guacClientFileDownloadEnd', client, stream.index, mimetype, filename, blob_reader.getBlob()); - }; - - stream.sendAck("Ready", Guacamole.Status.Code.SUCCESS); - - } - - // Respond with UNSUPPORTED if download (default action) canceled within event handler - else - stream.sendAck("Download canceled", Guacamole.Status.Code.UNSUPPORTED); - - }; - */ - // Connect the Guacamole client client.connect(getConnectString(id, connectionParameters)); diff --git a/guacamole/src/main/webapp/app/client/types/ManagedFileDownload.js b/guacamole/src/main/webapp/app/client/types/ManagedFileDownload.js new file mode 100644 index 000000000..f6c8ffb77 --- /dev/null +++ b/guacamole/src/main/webapp/app/client/types/ManagedFileDownload.js @@ -0,0 +1,153 @@ +/* + * 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 ManagedFileDownload class used by the guacClientManager service. + */ +angular.module('client').factory('ManagedFileDownload', ['$rootScope', '$injector', + function defineManagedFileDownload($rootScope, $injector) { + + // Required types + var ManagedFileTransferState = $injector.get('ManagedFileTransferState'); + + /** + * Object which serves as a surrogate interface, encapsulating a Guacamole + * file download while it is active, allowing it to be detached and + * reattached from different client views. + * + * @constructor + * @param {ManagedFileDownload|Object} [template={}] + * The object whose properties should be copied within the new + * ManagedFileDownload. + */ + var ManagedFileDownload = function ManagedFileDownload(template) { + + // Use empty object by default + template = template || {}; + + /** + * The current state of the file transfer stream. + * + * @type ManagedFileTransferState + */ + this.transferState = template.transferState || new ManagedFileTransferState(); + + /** + * The mimetype of the file being transferred. + * + * @type String + */ + this.mimetype = template.mimetype; + + /** + * The filename of the file being transferred. + * + * @type String + */ + this.filename = template.filename; + + /** + * The number of bytes transferred so far. + * + * @type Number + */ + this.progress = template.progress; + + /** + * A blob containing the complete downloaded file. This is available + * only after the download has finished. + * + * @type Blob + */ + this.blob = template.blob; + + }; + + /** + * Creates a new ManagedFileDownload which downloads the contents of the + * given stream as a file having the given mimetype and filename. + * + * @param {Guacamole.InputStream} stream + * The stream whose contents should be downloaded as a file. + * + * @param {String} mimetype + * The mimetype of the stream contents. + * + * @param {String} filename + * The filename of the file being received over the steram. + * + * @return {ManagedFileDownload} + * A new ManagedFileDownload object which can be used to track the + * progress of the download. + */ + ManagedFileDownload.getInstance = function getInstance(stream, mimetype, filename) { + + // Init new file download object + var managedFileDownload = new ManagedFileDownload({ + mimetype : mimetype, + filename : filename, + progress : 0, + transferState : new ManagedFileTransferState({ + streamState : ManagedFileTransferState.StreamState.OPEN + }) + }); + + // Begin file download + var blob_reader = new Guacamole.BlobReader(stream, mimetype); + + // Update progress as data is received + blob_reader.onprogress = function onprogress() { + + // Update progress + $rootScope.$apply(function downloadStreamProgress() { + managedFileDownload.progress = blob_reader.getLength(); + }); + + // Signal server that data was received + stream.sendAck("Received", Guacamole.Status.Code.SUCCESS); + + }; + + // Save blob and close stream when complete + blob_reader.onend = function onend() { + $rootScope.$apply(function downloadStreamEnd() { + + // Save blob + managedFileDownload.blob = blob_reader.getBlob(); + + // Mark stream as closed + ManagedFileTransferState.setStreamState(managedFileDownload.transferState, + ManagedFileTransferState.StreamState.CLOSED); + + }); + }; + + // Signal server that data is ready to be received + stream.sendAck("Ready", Guacamole.Status.Code.SUCCESS); + + return managedFileDownload; + + }; + + return ManagedFileDownload; + +}]); \ No newline at end of file