mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-08 06:01:22 +00:00
GUAC-963: Manage file uploads.
This commit is contained in:
@@ -620,100 +620,6 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mapping of upload stream index to notification object
|
|
||||||
var uploadNotifications = {};
|
|
||||||
|
|
||||||
// Mapping of upload stream index to notification ID
|
|
||||||
var uploadNotificationIDs = {};
|
|
||||||
|
|
||||||
$scope.$on('guacClientFileUploadStart', function handleClientFileUploadStart(event, guacClient, streamIndex, mimetype, filename, length) {
|
|
||||||
$scope.$apply(function() {
|
|
||||||
|
|
||||||
var notification = {
|
|
||||||
className : 'upload',
|
|
||||||
title : 'CLIENT.DIALOG_TITLE_FILE_TRANSFER',
|
|
||||||
text : filename
|
|
||||||
};
|
|
||||||
|
|
||||||
uploadNotifications[streamIndex] = notification;
|
|
||||||
uploadNotificationIDs[streamIndex] = $scope.addNotification(notification);
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$scope.$on('guacClientFileUploadProgress', function handleClientFileUploadProgress(event, guacClient, streamIndex, mimetype, filename, length, offset) {
|
|
||||||
$scope.$apply(function() {
|
|
||||||
|
|
||||||
var notification = uploadNotifications[streamIndex];
|
|
||||||
if (notification)
|
|
||||||
notification.progress = getFileProgress('CLIENT.TEXT_FILE_TRANSFER_PROGRESS', offset, length);
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$scope.$on('guacClientFileUploadEnd', function handleClientFileUploadEnd(event, guacClient, streamIndex, mimetype, filename, length) {
|
|
||||||
$scope.$apply(function() {
|
|
||||||
|
|
||||||
var notification = uploadNotifications[streamIndex];
|
|
||||||
var notificationID = uploadNotificationIDs[streamIndex];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close the notification.
|
|
||||||
*/
|
|
||||||
var closeNotification = function closeNotification() {
|
|
||||||
$scope.removeNotification(notificationID);
|
|
||||||
delete uploadNotifications[streamIndex];
|
|
||||||
delete uploadNotificationIDs[streamIndex];
|
|
||||||
};
|
|
||||||
|
|
||||||
// Show that the file has uploaded successfully
|
|
||||||
if (notificationID && notification) {
|
|
||||||
delete notification.progress;
|
|
||||||
notification.actions = [
|
|
||||||
{
|
|
||||||
name : 'CLIENT.ACTION_ACKNOWLEDGE',
|
|
||||||
callback : closeNotification
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$scope.$on('guacClientFileUploadError', function handleClientFileUploadError(event, guacClient, streamIndex, mimetype, fileName, length, status) {
|
|
||||||
$scope.$apply(function() {
|
|
||||||
|
|
||||||
var notification = uploadNotifications[streamIndex];
|
|
||||||
var notificationID = uploadNotificationIDs[streamIndex];
|
|
||||||
|
|
||||||
// Determine translation name of error
|
|
||||||
var errorName = (status in UPLOAD_ERRORS) ? status.toString(16).toUpperCase() : "DEFAULT";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close the notification.
|
|
||||||
*/
|
|
||||||
var closeNotification = function closeNotification() {
|
|
||||||
$scope.removeNotification(notificationID);
|
|
||||||
delete uploadNotifications[streamIndex];
|
|
||||||
delete uploadNotificationIDs[streamIndex];
|
|
||||||
};
|
|
||||||
|
|
||||||
// Show that the file upload has failed
|
|
||||||
if (notificationID && notification) {
|
|
||||||
delete notification.progress;
|
|
||||||
notification.actions = [
|
|
||||||
{
|
|
||||||
name : 'CLIENT.ACTION_ACKNOWLEDGE',
|
|
||||||
callback : closeNotification
|
|
||||||
}
|
|
||||||
];
|
|
||||||
notification.text = "CLIENT.ERROR_UPLOAD_" + errorName;
|
|
||||||
notification.className = "upload error";
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Clean up when view destroyed
|
// Clean up when view destroyed
|
||||||
$scope.$on('$destroy', function clientViewDestroyed() {
|
$scope.$on('$destroy', function clientViewDestroyed() {
|
||||||
|
|
||||||
|
@@ -42,8 +42,11 @@ angular.module('client').directive('guacClient', [function guacClient() {
|
|||||||
templateUrl: 'app/client/templates/guacClient.html',
|
templateUrl: 'app/client/templates/guacClient.html',
|
||||||
controller: ['$scope', '$injector', '$element', function guacClientController($scope, $injector, $element) {
|
controller: ['$scope', '$injector', '$element', function guacClientController($scope, $injector, $element) {
|
||||||
|
|
||||||
|
// Required types
|
||||||
|
var ManagedClient = $injector.get('ManagedClient');
|
||||||
|
|
||||||
// Required services
|
// Required services
|
||||||
var $window = $injector.get('$window');
|
var $window = $injector.get('$window');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the local, hardware mouse cursor is in use.
|
* Whether the local, hardware mouse cursor is in use.
|
||||||
@@ -407,26 +410,6 @@ angular.module('client').directive('guacClient', [function guacClient() {
|
|||||||
client.sendKeyEvent(0, keysym);
|
client.sendKeyEvent(0, keysym);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts the given bytes to a base64-encoded string.
|
|
||||||
*
|
|
||||||
* @param {Uint8Array} bytes A Uint8Array which contains the data to be
|
|
||||||
* encoded as base64.
|
|
||||||
* @return {String} The base64-encoded string.
|
|
||||||
*/
|
|
||||||
function getBase64(bytes) {
|
|
||||||
|
|
||||||
var data = "";
|
|
||||||
|
|
||||||
// Produce binary string from bytes in buffer
|
|
||||||
for (var i=0; i<bytes.byteLength; i++)
|
|
||||||
data += String.fromCharCode(bytes[i]);
|
|
||||||
|
|
||||||
// Convert to base64
|
|
||||||
return $window.btoa(data);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ignores the given event.
|
* Ignores the given event.
|
||||||
*
|
*
|
||||||
@@ -436,68 +419,6 @@ angular.module('client').directive('guacClient', [function guacClient() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Uploads the given file to the server.
|
|
||||||
*
|
|
||||||
* @param {File} file The file to upload.
|
|
||||||
*/
|
|
||||||
function uploadFile(file) {
|
|
||||||
|
|
||||||
// Construct reader for file
|
|
||||||
var reader = new FileReader();
|
|
||||||
reader.onloadend = function() {
|
|
||||||
|
|
||||||
// Open file for writing
|
|
||||||
var stream = client.createFileStream(file.type, file.name);
|
|
||||||
|
|
||||||
var valid = true;
|
|
||||||
var bytes = new Uint8Array(reader.result);
|
|
||||||
var offset = 0;
|
|
||||||
|
|
||||||
// Add upload notification
|
|
||||||
$scope.$emit('guacClientFileUploadStart', client, stream.index, file.type, file.name, bytes.length);
|
|
||||||
|
|
||||||
// Invalidate stream on all errors
|
|
||||||
// Continue upload when acknowledged
|
|
||||||
stream.onack = function(status) {
|
|
||||||
|
|
||||||
// Handle errors
|
|
||||||
if (status.isError()) {
|
|
||||||
valid = false;
|
|
||||||
$scope.$emit('guacClientFileUploadError', client, stream.index, file.type, file.name, bytes.length, status.code);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Abort upload if stream is invalid
|
|
||||||
if (!valid) return false;
|
|
||||||
|
|
||||||
// Encode packet as base64
|
|
||||||
var slice = bytes.subarray(offset, offset+4096);
|
|
||||||
var base64 = getBase64(slice);
|
|
||||||
|
|
||||||
// Write packet
|
|
||||||
stream.sendBlob(base64);
|
|
||||||
|
|
||||||
// Advance to next packet
|
|
||||||
offset += 4096;
|
|
||||||
|
|
||||||
// If at end, stop upload
|
|
||||||
if (offset >= bytes.length) {
|
|
||||||
stream.sendEnd();
|
|
||||||
$scope.$emit('guacClientFileUploadProgress', client, stream.index, file.type, file.name, bytes.length, bytes.length);
|
|
||||||
$scope.$emit('guacClientFileUploadEnd', client, stream.index, file.type, file.name, bytes.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, update progress
|
|
||||||
else
|
|
||||||
$scope.$emit('guacClientFileUploadProgress', client, stream.index, file.type, file.name, bytes.length, offset);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
||||||
reader.readAsArrayBuffer(file);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle and ignore dragenter/dragover
|
// Handle and ignore dragenter/dragover
|
||||||
displayContainer.addEventListener("dragenter", ignoreEvent, false);
|
displayContainer.addEventListener("dragenter", ignoreEvent, false);
|
||||||
@@ -510,12 +431,13 @@ angular.module('client').directive('guacClient', [function guacClient() {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
// Ignore file drops if no attached client
|
// Ignore file drops if no attached client
|
||||||
if (!client) return;
|
if (!$scope.client)
|
||||||
|
return;
|
||||||
|
|
||||||
// Upload each file
|
// Upload each file
|
||||||
var files = e.dataTransfer.files;
|
var files = e.dataTransfer.files;
|
||||||
for (var i=0; i<files.length; i++)
|
for (var i=0; i<files.length; i++)
|
||||||
uploadFile(files[i]);
|
ManagedClient.uploadFile($scope.client, files[i]);
|
||||||
|
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
|
@@ -30,6 +30,7 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',
|
|||||||
var ClientProperties = $injector.get('ClientProperties');
|
var ClientProperties = $injector.get('ClientProperties');
|
||||||
var ManagedClientState = $injector.get('ManagedClientState');
|
var ManagedClientState = $injector.get('ManagedClientState');
|
||||||
var ManagedDisplay = $injector.get('ManagedDisplay');
|
var ManagedDisplay = $injector.get('ManagedDisplay');
|
||||||
|
var ManagedFileUpload = $injector.get('ManagedFileUpload');
|
||||||
|
|
||||||
// Required services
|
// Required services
|
||||||
var $window = $injector.get('$window');
|
var $window = $injector.get('$window');
|
||||||
@@ -100,18 +101,13 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',
|
|||||||
this.clipboardData = template.clipboardData;
|
this.clipboardData = template.clipboardData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current width of the Guacamole display, in pixels.
|
* All uploaded files. As files are uploaded, their progress can be
|
||||||
|
* observed through the elements of this array. It is intended that
|
||||||
|
* this array be manipulated externally as needed.
|
||||||
*
|
*
|
||||||
* @type Number
|
* @type ManagedFileUpload[]
|
||||||
*/
|
*/
|
||||||
this.displayWidth = template.displayWidth || 0;
|
this.uploads = template.uploads || [];
|
||||||
|
|
||||||
/**
|
|
||||||
* The current width of the Guacamole display, in pixels.
|
|
||||||
*
|
|
||||||
* @type Number
|
|
||||||
*/
|
|
||||||
this.displayHeight = template.displayHeight || 0;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current state of the Guacamole client (idle, connecting,
|
* The current state of the Guacamole client (idle, connecting,
|
||||||
@@ -433,6 +429,21 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uploads the given file to the server through the given Guacamole client.
|
||||||
|
* The file transfer can be monitored through the corresponding entry in
|
||||||
|
* the uploads array of the given managedClient.
|
||||||
|
*
|
||||||
|
* @param {ManagedClient} managedClient
|
||||||
|
* The ManagedClient through which the file is to be uploaded.
|
||||||
|
*
|
||||||
|
* @param {File} file
|
||||||
|
* The file to upload.
|
||||||
|
*/
|
||||||
|
ManagedClient.uploadFile = function uploadFile(managedClient, file) {
|
||||||
|
managedClient.uploads.push(ManagedFileUpload.getInstance(managedClient.client, file));
|
||||||
|
};
|
||||||
|
|
||||||
return ManagedClient;
|
return ManagedClient;
|
||||||
|
|
||||||
}]);
|
}]);
|
@@ -0,0 +1,136 @@
|
|||||||
|
/*
|
||||||
|
* 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 ManagedFileTransferState class used by the guacClientManager
|
||||||
|
* service.
|
||||||
|
*/
|
||||||
|
angular.module('client').factory('ManagedFileTransferState', [function defineManagedFileTransferState() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Object which represents the state of a Guacamole stream, including any
|
||||||
|
* error conditions.
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param {ManagedFileTransferState|Object} [template={}]
|
||||||
|
* The object whose properties should be copied within the new
|
||||||
|
* ManagedFileTransferState.
|
||||||
|
*/
|
||||||
|
var ManagedFileTransferState = function ManagedFileTransferState(template) {
|
||||||
|
|
||||||
|
// Use empty object by default
|
||||||
|
template = template || {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current stream state. Valid values are described by
|
||||||
|
* ManagedFileTransferState.StreamState.
|
||||||
|
*
|
||||||
|
* @type String
|
||||||
|
* @default ManagedFileTransferState.StreamState.IDLE
|
||||||
|
*/
|
||||||
|
this.streamState = template.streamState || ManagedFileTransferState.StreamState.IDLE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The status code of the current error condition, if streamState
|
||||||
|
* is ERROR. For all other streamState values, this will be
|
||||||
|
* @link{Guacamole.Status.Code.SUCCESS}.
|
||||||
|
*
|
||||||
|
* @type Number
|
||||||
|
* @default Guacamole.Status.Code.SUCCESS
|
||||||
|
*/
|
||||||
|
this.statusCode = template.statusCode || Guacamole.Status.Code.SUCCESS;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valid stream state strings. Each state string is associated with a
|
||||||
|
* specific state of a Guacamole stream.
|
||||||
|
*/
|
||||||
|
ManagedFileTransferState.StreamState = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The stream has not yet been opened.
|
||||||
|
*
|
||||||
|
* @type String
|
||||||
|
*/
|
||||||
|
IDLE : "IDLE",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The stream has been successfully established. Data can be sent or
|
||||||
|
* received.
|
||||||
|
*
|
||||||
|
* @type String
|
||||||
|
*/
|
||||||
|
OPEN : "OPEN",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The stream has terminated successfully. No errors are indicated.
|
||||||
|
*
|
||||||
|
* @type String
|
||||||
|
*/
|
||||||
|
CLOSED : "CLOSED",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The stream has terminated due to an error. The associated error code
|
||||||
|
* is stored in statusCode.
|
||||||
|
*
|
||||||
|
* @type String
|
||||||
|
*/
|
||||||
|
ERROR : "ERROR"
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current transfer state and, if given, the associated status
|
||||||
|
* code. If an error is already represented, this function has no effect.
|
||||||
|
*
|
||||||
|
* @param {ManagedFileTransferState} transferState
|
||||||
|
* The ManagedFileTransferState to update.
|
||||||
|
*
|
||||||
|
* @param {String} streamState
|
||||||
|
* The stream state to assign to the given ManagedFileTransferState, as
|
||||||
|
* listed within ManagedFileTransferState.StreamState.
|
||||||
|
*
|
||||||
|
* @param {Number} [statusCode]
|
||||||
|
* The status code to assign to the given ManagedFileTransferState, if
|
||||||
|
* any, as listed within Guacamole.Status.Code. If no status code is
|
||||||
|
* specified, the status code of the ManagedFileTransferState is not
|
||||||
|
* touched.
|
||||||
|
*/
|
||||||
|
ManagedFileTransferState.setStreamState = function(transferState, streamState, statusCode) {
|
||||||
|
|
||||||
|
// Do not set state after an error is registered
|
||||||
|
if (transferState.streamState === ManagedFileTransferState.StreamState.ERROR)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Update stream state
|
||||||
|
transferState.streamState = streamState;
|
||||||
|
|
||||||
|
// Set status code, if given
|
||||||
|
if (statusCode)
|
||||||
|
transferState.statusCode = statusCode;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return ManagedFileTransferState;
|
||||||
|
|
||||||
|
}]);
|
218
guacamole/src/main/webapp/app/client/types/ManagedFileUpload.js
Normal file
218
guacamole/src/main/webapp/app/client/types/ManagedFileUpload.js
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
/*
|
||||||
|
* 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 ManagedFileUpload class used by the guacClientManager service.
|
||||||
|
*/
|
||||||
|
angular.module('client').factory('ManagedFileUpload', ['$rootScope', '$injector',
|
||||||
|
function defineManagedFileUpload($rootScope, $injector) {
|
||||||
|
|
||||||
|
// Required types
|
||||||
|
var ManagedFileTransferState = $injector.get('ManagedFileTransferState');
|
||||||
|
|
||||||
|
// Required services
|
||||||
|
var $window = $injector.get('$window');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of bytes to include in each blob for the Guacamole
|
||||||
|
* file stream. Note that this, along with instruction opcode and protocol-
|
||||||
|
* related overhead, must not exceed the 8192 byte maximum imposed by the
|
||||||
|
* Guacamole protocol.
|
||||||
|
*
|
||||||
|
* @type Number
|
||||||
|
*/
|
||||||
|
var STREAM_BLOB_SIZE = 4096;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Object which serves as a surrogate interface, encapsulating a Guacamole
|
||||||
|
* file upload while it is active, allowing it to be detached and
|
||||||
|
* reattached from different client views.
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param {ManagedFileUpload|Object} [template={}]
|
||||||
|
* The object whose properties should be copied within the new
|
||||||
|
* ManagedFileUpload.
|
||||||
|
*/
|
||||||
|
var ManagedFileUpload = function ManagedFileUpload(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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The total number of bytes in the file.
|
||||||
|
*
|
||||||
|
* @type Number
|
||||||
|
*/
|
||||||
|
this.length = template.length;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the given bytes to a base64-encoded string.
|
||||||
|
*
|
||||||
|
* @param {Uint8Array} bytes A Uint8Array which contains the data to be
|
||||||
|
* encoded as base64.
|
||||||
|
* @return {String} The base64-encoded string.
|
||||||
|
*/
|
||||||
|
var getBase64 = function getBase64(bytes) {
|
||||||
|
|
||||||
|
var data = "";
|
||||||
|
|
||||||
|
// Produce binary string from bytes in buffer
|
||||||
|
for (var i=0; i<bytes.byteLength; i++)
|
||||||
|
data += String.fromCharCode(bytes[i]);
|
||||||
|
|
||||||
|
// Convert to base64
|
||||||
|
return $window.btoa(data);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new ManagedFileUpload which uploads the given file to the
|
||||||
|
* server through the given Guacamole client.
|
||||||
|
*
|
||||||
|
* @param {Guacamole.Client} client
|
||||||
|
* The Guacamole client through which the file is to be uploaded.
|
||||||
|
*
|
||||||
|
* @param {File} file
|
||||||
|
* The file to upload.
|
||||||
|
*
|
||||||
|
* @return {ManagedFileUpload}
|
||||||
|
* A new ManagedFileUpload object which can be used to track the
|
||||||
|
* progress of the upload.
|
||||||
|
*/
|
||||||
|
ManagedFileUpload.getInstance = function getInstance(client, file) {
|
||||||
|
|
||||||
|
var managedFileUpload = new ManagedFileUpload();
|
||||||
|
|
||||||
|
// Construct reader for file
|
||||||
|
var reader = new FileReader();
|
||||||
|
reader.onloadend = function() {
|
||||||
|
|
||||||
|
// Open file for writing
|
||||||
|
var stream = client.createFileStream(file.type, file.name);
|
||||||
|
|
||||||
|
var valid = true;
|
||||||
|
var bytes = new Uint8Array(reader.result);
|
||||||
|
var offset = 0;
|
||||||
|
|
||||||
|
$rootScope.$apply(function uploadStreamOpen() {
|
||||||
|
|
||||||
|
// Init managed upload
|
||||||
|
managedFileUpload.filename = file.name;
|
||||||
|
managedFileUpload.mimetype = file.type;
|
||||||
|
managedFileUpload.progress = 0;
|
||||||
|
managedFileUpload.length = bytes.length;
|
||||||
|
|
||||||
|
// Notify that stream is open
|
||||||
|
ManagedFileTransferState.setStreamState(managedFileUpload.transferState,
|
||||||
|
ManagedFileTransferState.StreamState.OPEN);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// Invalidate stream on all errors
|
||||||
|
// Continue upload when acknowledged
|
||||||
|
stream.onack = function(status) {
|
||||||
|
|
||||||
|
// Handle errors
|
||||||
|
if (status.isError()) {
|
||||||
|
valid = false;
|
||||||
|
$rootScope.$apply(function uploadStreamError() {
|
||||||
|
ManagedFileTransferState.setStreamState(managedFileUpload.transferState,
|
||||||
|
ManagedFileTransferState.StreamState.ERROR,
|
||||||
|
status.code);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Abort upload if stream is invalid
|
||||||
|
if (!valid)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Encode packet as base64
|
||||||
|
var slice = bytes.subarray(offset, offset + STREAM_BLOB_SIZE);
|
||||||
|
var base64 = getBase64(slice);
|
||||||
|
|
||||||
|
// Write packet
|
||||||
|
stream.sendBlob(base64);
|
||||||
|
|
||||||
|
// Advance to next packet
|
||||||
|
offset += STREAM_BLOB_SIZE;
|
||||||
|
|
||||||
|
$rootScope.$apply(function uploadStreamProgress() {
|
||||||
|
|
||||||
|
// If at end, stop upload
|
||||||
|
if (offset >= bytes.length) {
|
||||||
|
stream.sendEnd();
|
||||||
|
managedFileUpload.progress = bytes.length;
|
||||||
|
|
||||||
|
// Upload complete
|
||||||
|
ManagedFileTransferState.setStreamState(managedFileUpload.transferState,
|
||||||
|
ManagedFileTransferState.StreamState.CLOSED);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, update progress
|
||||||
|
else
|
||||||
|
managedFileUpload.progress = offset;
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}; // end ack handler
|
||||||
|
|
||||||
|
};
|
||||||
|
reader.readAsArrayBuffer(file);
|
||||||
|
|
||||||
|
return managedFileUpload;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return ManagedFileUpload;
|
||||||
|
|
||||||
|
}]);
|
Reference in New Issue
Block a user