-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathBlobWriter.js
245 lines (202 loc) · 7.26 KB
/
BlobWriter.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
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 provided Blob objects.
*
* @constructor
* @param {!Guacamole.OutputStream} stream
* The stream that data will be written to.
*/
Guacamole.BlobWriter = function BlobWriter(stream) {
/**
* Reference to this Guacamole.BlobWriter.
*
* @private
* @type {!Guacamole.BlobWriter}
*/
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 blob over the underlying stream.
*
* @param {!Blob} blob
* The blob to send.
*/
this.sendBlob = function sendBlob(blob) {
var offset = 0;
var reader = new FileReader();
/**
* Reads the next chunk of the blob provided to
* [sendBlob()]{@link Guacamole.BlobWriter#sendBlob}. 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 >= blob.size) {
// Fire completion event for completed blob
if (guacWriter.oncomplete)
guacWriter.oncomplete(blob);
// No further chunks to read
return;
}
// Obtain reference to next chunk as a new blob
var chunk = slice(blob, offset, offset + arrayBufferWriter.blobLength);
offset += arrayBufferWriter.blobLength;
// Attempt to read the blob 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 blob upload progress via progress events
if (guacWriter.onprogress)
guacWriter.onprogress(blob, 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(blob, 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 blob passed to
* [sendBlob()]{@link Guacamole.BlobWriter#sendBlob}. The transfer for the
* the given blob will cease, but the stream will remain open.
*
* @event
* @param {!Blob} blob
* The blob that was being read when the error occurred.
*
* @param {!number} offset
* The offset of the failed read attempt within the blob, in bytes.
*
* @param {!DOMError} error
* The error that occurred.
*/
this.onerror = null;
/**
* Fired for each successfully-read chunk of data as a blob is being sent
* via [sendBlob()]{@link Guacamole.BlobWriter#sendBlob}.
*
* @event
* @param {!Blob} blob
* The blob that is being read.
*
* @param {!number} offset
* The offset of the read that just succeeded.
*/
this.onprogress = null;
/**
* Fired when a blob passed to
* [sendBlob()]{@link Guacamole.BlobWriter#sendBlob} has finished being
* sent.
*
* @event
* @param {!Blob} blob
* The blob that was sent.
*/
this.oncomplete = null;
};