Source: main/webapp/modules/BlobWriter.js

  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. var Guacamole = Guacamole || {};
  20. /**
  21. * A writer which automatically writes to the given output stream with the
  22. * contents of provided Blob objects.
  23. *
  24. * @constructor
  25. * @param {!Guacamole.OutputStream} stream
  26. * The stream that data will be written to.
  27. */
  28. Guacamole.BlobWriter = function BlobWriter(stream) {
  29. /**
  30. * Reference to this Guacamole.BlobWriter.
  31. *
  32. * @private
  33. * @type {!Guacamole.BlobWriter}
  34. */
  35. var guacWriter = this;
  36. /**
  37. * Wrapped Guacamole.ArrayBufferWriter which will be used to send any
  38. * provided file data.
  39. *
  40. * @private
  41. * @type {!Guacamole.ArrayBufferWriter}
  42. */
  43. var arrayBufferWriter = new Guacamole.ArrayBufferWriter(stream);
  44. // Initially, simply call onack for acknowledgements
  45. arrayBufferWriter.onack = function(status) {
  46. if (guacWriter.onack)
  47. guacWriter.onack(status);
  48. };
  49. /**
  50. * Browser-independent implementation of Blob.slice() which uses an end
  51. * offset to determine the span of the resulting slice, rather than a
  52. * length.
  53. *
  54. * @private
  55. * @param {!Blob} blob
  56. * The Blob to slice.
  57. *
  58. * @param {!number} start
  59. * The starting offset of the slice, in bytes, inclusive.
  60. *
  61. * @param {!number} end
  62. * The ending offset of the slice, in bytes, exclusive.
  63. *
  64. * @returns {!Blob}
  65. * A Blob containing the data within the given Blob starting at
  66. * <code>start</code> and ending at <code>end - 1</code>.
  67. */
  68. var slice = function slice(blob, start, end) {
  69. // Use prefixed implementations if necessary
  70. var sliceImplementation = (
  71. blob.slice
  72. || blob.webkitSlice
  73. || blob.mozSlice
  74. ).bind(blob);
  75. var length = end - start;
  76. // The old Blob.slice() was length-based (not end-based). Try the
  77. // length version first, if the two calls are not equivalent.
  78. if (length !== end) {
  79. // If the result of the slice() call matches the expected length,
  80. // trust that result. It must be correct.
  81. var sliceResult = sliceImplementation(start, length);
  82. if (sliceResult.size === length)
  83. return sliceResult;
  84. }
  85. // Otherwise, use the most-recent standard: end-based slice()
  86. return sliceImplementation(start, end);
  87. };
  88. /**
  89. * Sends the contents of the given blob over the underlying stream.
  90. *
  91. * @param {!Blob} blob
  92. * The blob to send.
  93. */
  94. this.sendBlob = function sendBlob(blob) {
  95. var offset = 0;
  96. var reader = new FileReader();
  97. /**
  98. * Reads the next chunk of the blob provided to
  99. * [sendBlob()]{@link Guacamole.BlobWriter#sendBlob}. The chunk itself
  100. * is read asynchronously, and will not be available until
  101. * reader.onload fires.
  102. *
  103. * @private
  104. */
  105. var readNextChunk = function readNextChunk() {
  106. // If no further chunks remain, inform of completion and stop
  107. if (offset >= blob.size) {
  108. // Fire completion event for completed blob
  109. if (guacWriter.oncomplete)
  110. guacWriter.oncomplete(blob);
  111. // No further chunks to read
  112. return;
  113. }
  114. // Obtain reference to next chunk as a new blob
  115. var chunk = slice(blob, offset, offset + arrayBufferWriter.blobLength);
  116. offset += arrayBufferWriter.blobLength;
  117. // Attempt to read the blob contents represented by the blob into
  118. // a new array buffer
  119. reader.readAsArrayBuffer(chunk);
  120. };
  121. // Send each chunk over the stream, continue reading the next chunk
  122. reader.onload = function chunkLoadComplete() {
  123. // Send the successfully-read chunk
  124. arrayBufferWriter.sendData(reader.result);
  125. // Continue sending more chunks after the latest chunk is
  126. // acknowledged
  127. arrayBufferWriter.onack = function sendMoreChunks(status) {
  128. if (guacWriter.onack)
  129. guacWriter.onack(status);
  130. // Abort transfer if an error occurs
  131. if (status.isError())
  132. return;
  133. // Inform of blob upload progress via progress events
  134. if (guacWriter.onprogress)
  135. guacWriter.onprogress(blob, offset - arrayBufferWriter.blobLength);
  136. // Queue the next chunk for reading
  137. readNextChunk();
  138. };
  139. };
  140. // If an error prevents further reading, inform of error and stop
  141. reader.onerror = function chunkLoadFailed() {
  142. // Fire error event, including the context of the error
  143. if (guacWriter.onerror)
  144. guacWriter.onerror(blob, offset, reader.error);
  145. };
  146. // Begin reading the first chunk
  147. readNextChunk();
  148. };
  149. /**
  150. * Signals that no further text will be sent, effectively closing the
  151. * stream.
  152. */
  153. this.sendEnd = function sendEnd() {
  154. arrayBufferWriter.sendEnd();
  155. };
  156. /**
  157. * Fired for received data, if acknowledged by the server.
  158. *
  159. * @event
  160. * @param {!Guacamole.Status} status
  161. * The status of the operation.
  162. */
  163. this.onack = null;
  164. /**
  165. * Fired when an error occurs reading a blob passed to
  166. * [sendBlob()]{@link Guacamole.BlobWriter#sendBlob}. The transfer for the
  167. * the given blob will cease, but the stream will remain open.
  168. *
  169. * @event
  170. * @param {!Blob} blob
  171. * The blob that was being read when the error occurred.
  172. *
  173. * @param {!number} offset
  174. * The offset of the failed read attempt within the blob, in bytes.
  175. *
  176. * @param {!DOMError} error
  177. * The error that occurred.
  178. */
  179. this.onerror = null;
  180. /**
  181. * Fired for each successfully-read chunk of data as a blob is being sent
  182. * via [sendBlob()]{@link Guacamole.BlobWriter#sendBlob}.
  183. *
  184. * @event
  185. * @param {!Blob} blob
  186. * The blob that is being read.
  187. *
  188. * @param {!number} offset
  189. * The offset of the read that just succeeded.
  190. */
  191. this.onprogress = null;
  192. /**
  193. * Fired when a blob passed to
  194. * [sendBlob()]{@link Guacamole.BlobWriter#sendBlob} has finished being
  195. * sent.
  196. *
  197. * @event
  198. * @param {!Blob} blob
  199. * The blob that was sent.
  200. */
  201. this.oncomplete = null;
  202. };