mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-07 13:41:21 +00:00
GUACAMOLE-44: Merge file slicing change.
This commit is contained in:
@@ -61,6 +61,20 @@ Guacamole.ArrayBufferWriter = function(stream) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum length of any blob sent by this Guacamole.ArrayBufferWriter,
|
||||||
|
* in bytes. Data sent via
|
||||||
|
* [sendData()]{@link Guacamole.ArrayBufferWriter#sendData} which exceeds
|
||||||
|
* this length will be split into multiple blobs. As the Guacamole protocol
|
||||||
|
* limits the maximum size of any instruction or instruction element to
|
||||||
|
* 8192 bytes, and the contents of blobs will be base64-encoded, this value
|
||||||
|
* should only be increased with extreme caution.
|
||||||
|
*
|
||||||
|
* @type {Number}
|
||||||
|
* @default {@link Guacamole.ArrayBufferWriter.DEFAULT_BLOB_LENGTH}
|
||||||
|
*/
|
||||||
|
this.blobLength = Guacamole.ArrayBufferWriter.DEFAULT_BLOB_LENGTH;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends the given data.
|
* Sends the given data.
|
||||||
*
|
*
|
||||||
@@ -71,13 +85,13 @@ Guacamole.ArrayBufferWriter = function(stream) {
|
|||||||
var bytes = new Uint8Array(data);
|
var bytes = new Uint8Array(data);
|
||||||
|
|
||||||
// If small enough to fit into single instruction, send as-is
|
// If small enough to fit into single instruction, send as-is
|
||||||
if (bytes.length <= 6048)
|
if (bytes.length <= guac_writer.blobLength)
|
||||||
__send_blob(bytes);
|
__send_blob(bytes);
|
||||||
|
|
||||||
// Otherwise, send as multiple instructions
|
// Otherwise, send as multiple instructions
|
||||||
else {
|
else {
|
||||||
for (var offset=0; offset<bytes.length; offset += 6048)
|
for (var offset=0; offset<bytes.length; offset += guac_writer.blobLength)
|
||||||
__send_blob(bytes.subarray(offset, offset + 6048));
|
__send_blob(bytes.subarray(offset, offset + guac_writer.blobLength));
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
@@ -97,4 +111,13 @@ Guacamole.ArrayBufferWriter = function(stream) {
|
|||||||
*/
|
*/
|
||||||
this.onack = null;
|
this.onack = null;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default maximum blob length for new Guacamole.ArrayBufferWriter
|
||||||
|
* instances.
|
||||||
|
*
|
||||||
|
* @constant
|
||||||
|
* @type {Number}
|
||||||
|
*/
|
||||||
|
Guacamole.ArrayBufferWriter.DEFAULT_BLOB_LENGTH = 6048;
|
||||||
|
245
guacamole-common-js/src/main/webapp/modules/FileWriter.js
Normal file
245
guacamole-common-js/src/main/webapp/modules/FileWriter.js
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var Guacamole = Guacamole || {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A writer which automatically writes to the given output stream with the
|
||||||
|
* contents of a local files, supplied as standard File objects.
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param {Guacamole.OutputStream} stream
|
||||||
|
* The stream that data will be written to.
|
||||||
|
*/
|
||||||
|
Guacamole.FileWriter = function FileWriter(stream) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to this Guacamole.FileWriter.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @type {Guacamole.FileWriter}
|
||||||
|
*/
|
||||||
|
var guacWriter = this;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapped Guacamole.ArrayBufferWriter which will be used to send any
|
||||||
|
* provided file data.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @type {Guacamole.ArrayBufferWriter}
|
||||||
|
*/
|
||||||
|
var arrayBufferWriter = new Guacamole.ArrayBufferWriter(stream);
|
||||||
|
|
||||||
|
// Initially, simply call onack for acknowledgements
|
||||||
|
arrayBufferWriter.onack = function(status) {
|
||||||
|
if (guacWriter.onack)
|
||||||
|
guacWriter.onack(status);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Browser-independent implementation of Blob.slice() which uses an end
|
||||||
|
* offset to determine the span of the resulting slice, rather than a
|
||||||
|
* length.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Blob} blob
|
||||||
|
* The Blob to slice.
|
||||||
|
*
|
||||||
|
* @param {Number} start
|
||||||
|
* The starting offset of the slice, in bytes, inclusive.
|
||||||
|
*
|
||||||
|
* @param {Number} end
|
||||||
|
* The ending offset of the slice, in bytes, exclusive.
|
||||||
|
*
|
||||||
|
* @returns {Blob}
|
||||||
|
* A Blob containing the data within the given Blob starting at
|
||||||
|
* <code>start</code> and ending at <code>end - 1</code>.
|
||||||
|
*/
|
||||||
|
var slice = function slice(blob, start, end) {
|
||||||
|
|
||||||
|
// Use prefixed implementations if necessary
|
||||||
|
var sliceImplementation = (
|
||||||
|
blob.slice
|
||||||
|
|| blob.webkitSlice
|
||||||
|
|| blob.mozSlice
|
||||||
|
).bind(blob);
|
||||||
|
|
||||||
|
var length = end - start;
|
||||||
|
|
||||||
|
// The old Blob.slice() was length-based (not end-based). Try the
|
||||||
|
// length version first, if the two calls are not equivalent.
|
||||||
|
if (length !== end) {
|
||||||
|
|
||||||
|
// If the result of the slice() call matches the expected length,
|
||||||
|
// trust that result. It must be correct.
|
||||||
|
var sliceResult = sliceImplementation(start, length);
|
||||||
|
if (sliceResult.size === length)
|
||||||
|
return sliceResult;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, use the most-recent standard: end-based slice()
|
||||||
|
return sliceImplementation(start, end);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the contents of the given file over the underlying stream.
|
||||||
|
*
|
||||||
|
* @param {File} file
|
||||||
|
* The file to send.
|
||||||
|
*/
|
||||||
|
this.sendFile = function sendFile(file) {
|
||||||
|
|
||||||
|
var offset = 0;
|
||||||
|
var reader = new FileReader();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the next chunk of the file provided to
|
||||||
|
* [sendFile()]{@link Guacamole.FileWriter#sendFile}. The chunk itself
|
||||||
|
* is read asynchronously, and will not be available until
|
||||||
|
* reader.onload fires.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
var readNextChunk = function readNextChunk() {
|
||||||
|
|
||||||
|
// If no further chunks remain, inform of completion and stop
|
||||||
|
if (offset >= file.size) {
|
||||||
|
|
||||||
|
// Fire completion event for completed file
|
||||||
|
if (guacWriter.oncomplete)
|
||||||
|
guacWriter.oncomplete(file);
|
||||||
|
|
||||||
|
// No further chunks to read
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtain reference to next chunk as a new blob
|
||||||
|
var chunk = slice(file, offset, offset + arrayBufferWriter.blobLength);
|
||||||
|
offset += arrayBufferWriter.blobLength;
|
||||||
|
|
||||||
|
// Attempt to read the file contents represented by the blob into
|
||||||
|
// a new array buffer
|
||||||
|
reader.readAsArrayBuffer(chunk);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send each chunk over the stream, continue reading the next chunk
|
||||||
|
reader.onload = function chunkLoadComplete() {
|
||||||
|
|
||||||
|
// Send the successfully-read chunk
|
||||||
|
arrayBufferWriter.sendData(reader.result);
|
||||||
|
|
||||||
|
// Continue sending more chunks after the latest chunk is
|
||||||
|
// acknowledged
|
||||||
|
arrayBufferWriter.onack = function sendMoreChunks(status) {
|
||||||
|
|
||||||
|
if (guacWriter.onack)
|
||||||
|
guacWriter.onack(status);
|
||||||
|
|
||||||
|
// Abort transfer if an error occurs
|
||||||
|
if (status.isError())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Inform of file upload progress via progress events
|
||||||
|
if (guacWriter.onprogress)
|
||||||
|
guacWriter.onprogress(file, offset - arrayBufferWriter.blobLength);
|
||||||
|
|
||||||
|
// Queue the next chunk for reading
|
||||||
|
readNextChunk();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// If an error prevents further reading, inform of error and stop
|
||||||
|
reader.onerror = function chunkLoadFailed() {
|
||||||
|
|
||||||
|
// Fire error event, including the context of the error
|
||||||
|
if (guacWriter.onerror)
|
||||||
|
guacWriter.onerror(file, offset, reader.error);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// Begin reading the first chunk
|
||||||
|
readNextChunk();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals that no further text will be sent, effectively closing the
|
||||||
|
* stream.
|
||||||
|
*/
|
||||||
|
this.sendEnd = function sendEnd() {
|
||||||
|
arrayBufferWriter.sendEnd();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fired for received data, if acknowledged by the server.
|
||||||
|
*
|
||||||
|
* @event
|
||||||
|
* @param {Guacamole.Status} status
|
||||||
|
* The status of the operation.
|
||||||
|
*/
|
||||||
|
this.onack = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fired when an error occurs reading a file passed to
|
||||||
|
* [sendFile()]{@link Guacamole.FileWriter#sendFile}. The file transfer for
|
||||||
|
* the given file will cease, but the stream will remain open.
|
||||||
|
*
|
||||||
|
* @event
|
||||||
|
* @param {File} file
|
||||||
|
* The file that was being read when the error occurred.
|
||||||
|
*
|
||||||
|
* @param {Number} offset
|
||||||
|
* The offset of the failed read attempt within the file, in bytes.
|
||||||
|
*
|
||||||
|
* @param {DOMError} error
|
||||||
|
* The error that occurred.
|
||||||
|
*/
|
||||||
|
this.onerror = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fired for each successfully-read chunk of file data as a file is being
|
||||||
|
* sent via [sendFile()]{@link Guacamole.FileWriter#sendFile}.
|
||||||
|
*
|
||||||
|
* @event
|
||||||
|
* @param {File} file
|
||||||
|
* The file that is being read.
|
||||||
|
*
|
||||||
|
* @param {Number} offset
|
||||||
|
* The offset of the read that just succeeded.
|
||||||
|
*/
|
||||||
|
this.onprogress = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fired when a file passed to
|
||||||
|
* [sendFile()]{@link Guacamole.FileWriter#sendFile} has finished being
|
||||||
|
* sent.
|
||||||
|
*
|
||||||
|
* @event
|
||||||
|
* @param {File} file
|
||||||
|
* The file that was sent.
|
||||||
|
*/
|
||||||
|
this.oncomplete = null;
|
||||||
|
|
||||||
|
};
|
@@ -29,16 +29,6 @@ angular.module('client').factory('ManagedFileUpload', ['$rootScope', '$injector'
|
|||||||
// Required services
|
// Required services
|
||||||
var $window = $injector.get('$window');
|
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
|
* Object which serves as a surrogate interface, encapsulating a Guacamole
|
||||||
* file upload while it is active, allowing it to be detached and
|
* file upload while it is active, allowing it to be detached and
|
||||||
@@ -136,93 +126,91 @@ angular.module('client').factory('ManagedFileUpload', ['$rootScope', '$injector'
|
|||||||
ManagedFileUpload.getInstance = function getInstance(client, file, object, streamName) {
|
ManagedFileUpload.getInstance = function getInstance(client, file, object, streamName) {
|
||||||
|
|
||||||
var managedFileUpload = new ManagedFileUpload();
|
var managedFileUpload = new ManagedFileUpload();
|
||||||
|
var streamAcknowledged = false;
|
||||||
|
|
||||||
// Construct reader for file
|
// Open file for writing
|
||||||
var reader = new FileReader();
|
var stream;
|
||||||
reader.onloadend = function fileContentsLoaded() {
|
if (!object)
|
||||||
|
stream = client.createFileStream(file.type, file.name);
|
||||||
|
|
||||||
// Open file for writing
|
// If object/streamName specified, upload to that instead of a file
|
||||||
var stream;
|
// stream
|
||||||
if (!object)
|
else
|
||||||
stream = client.createFileStream(file.type, file.name);
|
stream = object.createOutputStream(file.type, streamName);
|
||||||
|
|
||||||
// If object/streamName specified, upload to that instead of a file
|
// Notify that the file transfer is pending
|
||||||
// stream
|
$rootScope.$apply(function uploadStreamOpen() {
|
||||||
else
|
|
||||||
stream = object.createOutputStream(file.type, streamName);
|
|
||||||
|
|
||||||
var valid = true;
|
// Init managed upload
|
||||||
var bytes = new Uint8Array(reader.result);
|
managedFileUpload.filename = file.name;
|
||||||
var offset = 0;
|
managedFileUpload.mimetype = file.type;
|
||||||
|
managedFileUpload.progress = 0;
|
||||||
|
managedFileUpload.length = file.size;
|
||||||
|
|
||||||
$rootScope.$apply(function uploadStreamOpen() {
|
// Notify that stream is open
|
||||||
|
ManagedFileTransferState.setStreamState(managedFileUpload.transferState,
|
||||||
|
ManagedFileTransferState.StreamState.OPEN);
|
||||||
|
|
||||||
// Init managed upload
|
});
|
||||||
managedFileUpload.filename = file.name;
|
|
||||||
managedFileUpload.mimetype = file.type;
|
|
||||||
managedFileUpload.progress = 0;
|
|
||||||
managedFileUpload.length = bytes.length;
|
|
||||||
|
|
||||||
// Notify that stream is open
|
var writer = new Guacamole.FileWriter(stream);
|
||||||
ManagedFileTransferState.setStreamState(managedFileUpload.transferState,
|
|
||||||
ManagedFileTransferState.StreamState.OPEN);
|
|
||||||
|
|
||||||
});
|
// Begin upload when stream is acknowledged, notify of any errors
|
||||||
|
writer.onack = function ackReceived(status) {
|
||||||
// Invalidate stream on all errors
|
|
||||||
// Continue upload when acknowledged
|
|
||||||
stream.onack = function ackReceived(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);
|
|
||||||
|
|
||||||
// Notify of upload completion
|
|
||||||
$rootScope.$broadcast('guacUploadComplete', file.name);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, update progress
|
|
||||||
else
|
|
||||||
managedFileUpload.progress = offset;
|
|
||||||
|
|
||||||
|
// Notify of any errors from the Guacamole server
|
||||||
|
if (status.isError()) {
|
||||||
|
$rootScope.$apply(function uploadStreamError() {
|
||||||
|
ManagedFileTransferState.setStreamState(managedFileUpload.transferState,
|
||||||
|
ManagedFileTransferState.StreamState.ERROR,
|
||||||
|
status.code);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}; // end ack handler
|
// Begin sending the requested file once stream is acknowledged
|
||||||
|
else if (!streamAcknowledged) {
|
||||||
|
writer.sendFile(file);
|
||||||
|
streamAcknowledged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// Abort and notify if the file cannot be read
|
||||||
|
writer.onerror = function fileReadError(file, offset, error) {
|
||||||
|
|
||||||
|
// Abort transfer
|
||||||
|
writer.sendEnd();
|
||||||
|
|
||||||
|
// Upload failed
|
||||||
|
ManagedFileTransferState.setStreamState(managedFileUpload.transferState,
|
||||||
|
ManagedFileTransferState.StreamState.ERROR);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// Notify of upload progress
|
||||||
|
writer.onprogress = function uploadProgressing(file, length) {
|
||||||
|
|
||||||
|
$rootScope.$apply(function uploadStreamProgress() {
|
||||||
|
managedFileUpload.progress = length;
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clean up and notify when upload completes
|
||||||
|
writer.oncomplete = function uploadComplete(file) {
|
||||||
|
|
||||||
|
// If at end, stop upload
|
||||||
|
writer.sendEnd();
|
||||||
|
managedFileUpload.progress = file.size;
|
||||||
|
|
||||||
|
// Upload complete
|
||||||
|
ManagedFileTransferState.setStreamState(managedFileUpload.transferState,
|
||||||
|
ManagedFileTransferState.StreamState.CLOSED);
|
||||||
|
|
||||||
|
// Notify of upload completion
|
||||||
|
$rootScope.$broadcast('guacUploadComplete', file.name);
|
||||||
|
|
||||||
};
|
};
|
||||||
reader.readAsArrayBuffer(file);
|
|
||||||
|
|
||||||
return managedFileUpload;
|
return managedFileUpload;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user