fine_uploader 3.1.1 → 3.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE.txt +1 -3
- data/Vendorfile +16 -5
- data/lib/fine_uploader/version.rb +1 -1
- data/vendor/assets/javascripts/fine_uploader/ajax.requester.js +184 -0
- data/vendor/assets/javascripts/fine_uploader/button.js +62 -58
- data/vendor/assets/javascripts/fine_uploader/deletefile.ajax.requester.js +44 -0
- data/vendor/assets/javascripts/fine_uploader/dnd.js +2 -0
- data/vendor/assets/javascripts/fine_uploader/handler.base.js +164 -92
- data/vendor/assets/javascripts/fine_uploader/handler.form.js +220 -133
- data/vendor/assets/javascripts/fine_uploader/handler.xhr.js +594 -132
- data/vendor/assets/javascripts/fine_uploader/header.js +3 -4
- data/vendor/assets/javascripts/fine_uploader/iframe.xss.response.js +6 -0
- data/vendor/assets/javascripts/fine_uploader/jquery-plugin.js +13 -4
- data/vendor/assets/javascripts/fine_uploader/paste.js +49 -0
- data/vendor/assets/javascripts/fine_uploader/promise.js +45 -0
- data/vendor/assets/javascripts/fine_uploader/uploader.basic.js +547 -175
- data/vendor/assets/javascripts/fine_uploader/uploader.js +197 -42
- data/vendor/assets/javascripts/fine_uploader/util.js +117 -12
- data/vendor/assets/javascripts/fine_uploader/window.receive.message.js +32 -0
- data/vendor/assets/javascripts/fine_uploader.js +17 -0
- data/vendor/assets/stylesheets/fine_uploader.css.scss +6 -7
- metadata +9 -2
@@ -1,168 +1,630 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
1
|
+
/*globals qq, File, XMLHttpRequest, FormData, Blob*/
|
2
|
+
qq.UploadHandlerXhr = function(o, uploadCompleteCallback, logCallback) {
|
3
|
+
"use strict";
|
4
|
+
|
5
|
+
var options = o,
|
6
|
+
uploadComplete = uploadCompleteCallback,
|
7
|
+
log = logCallback,
|
8
|
+
fileState = [],
|
9
|
+
cookieItemDelimiter = "|",
|
10
|
+
chunkFiles = options.chunking.enabled && qq.isFileChunkingSupported(),
|
11
|
+
resumeEnabled = options.resume.enabled && chunkFiles && qq.areCookiesEnabled(),
|
12
|
+
resumeId = getResumeId(),
|
13
|
+
multipart = options.forceMultipart || options.paramsInBody,
|
14
|
+
api;
|
14
15
|
|
15
|
-
// @inherits qq.UploadHandlerAbstract
|
16
|
-
qq.extend(qq.UploadHandlerXhr.prototype, qq.UploadHandlerAbstract.prototype)
|
17
|
-
|
18
|
-
qq.extend(qq.UploadHandlerXhr.prototype, {
|
19
|
-
/**
|
20
|
-
* Adds file to the queue
|
21
|
-
* Returns id to use with upload, cancel
|
22
|
-
**/
|
23
|
-
add: function(file){
|
24
|
-
if (!(file instanceof File)){
|
25
|
-
throw new Error('Passed obj in not a File (in qq.UploadHandlerXhr)');
|
26
|
-
}
|
27
|
-
|
28
|
-
return this._files.push(file) - 1;
|
29
|
-
},
|
30
|
-
getName: function(id){
|
31
|
-
var file = this._files[id];
|
32
|
-
// fix missing name in Safari 4
|
33
|
-
//NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
|
34
|
-
return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
|
35
|
-
},
|
36
|
-
getSize: function(id){
|
37
|
-
var file = this._files[id];
|
38
|
-
return file.fileSize != null ? file.fileSize : file.size;
|
39
|
-
},
|
40
|
-
/**
|
41
|
-
* Returns uploaded bytes for file identified by id
|
42
|
-
*/
|
43
|
-
getLoaded: function(id){
|
44
|
-
return this._loaded[id] || 0;
|
45
|
-
},
|
46
|
-
isValid: function(id) {
|
47
|
-
return this._files[id] !== undefined;
|
48
|
-
},
|
49
|
-
reset: function() {
|
50
|
-
qq.UploadHandlerAbstract.prototype.reset.apply(this, arguments);
|
51
|
-
this._files = [];
|
52
|
-
this._xhrs = [];
|
53
|
-
this._loaded = [];
|
54
|
-
},
|
55
|
-
/**
|
56
|
-
* Sends the file identified by id to the server
|
57
|
-
*/
|
58
|
-
_upload: function(id){
|
59
|
-
var file = this._files[id],
|
60
|
-
name = this.getName(id),
|
61
|
-
size = this.getSize(id),
|
62
|
-
self = this,
|
63
|
-
url = this._options.endpoint,
|
64
|
-
protocol = this._options.demoMode ? "GET" : "POST",
|
65
|
-
xhr, formData, paramName, key, params;
|
66
|
-
|
67
|
-
this._options.onUpload(id, this.getName(id), true);
|
68
|
-
|
69
|
-
this._loaded[id] = 0;
|
70
|
-
|
71
|
-
xhr = this._xhrs[id] = new XMLHttpRequest();
|
72
16
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
self._options.onProgress(id, name, e.loaded, e.total);
|
77
|
-
}
|
78
|
-
};
|
17
|
+
function addChunkingSpecificParams(id, params, chunkData) {
|
18
|
+
var size = api.getSize(id),
|
19
|
+
name = api.getName(id);
|
79
20
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
21
|
+
params[options.chunking.paramNames.partIndex] = chunkData.part;
|
22
|
+
params[options.chunking.paramNames.partByteOffset] = chunkData.start;
|
23
|
+
params[options.chunking.paramNames.chunkSize] = chunkData.size;
|
24
|
+
params[options.chunking.paramNames.totalParts] = chunkData.count;
|
25
|
+
params[options.totalFileSizeParamName] = size;
|
26
|
+
|
27
|
+
/**
|
28
|
+
* When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
|
29
|
+
* or an empty string. So, we will need to include the actual file name as a param in this case.
|
30
|
+
*/
|
31
|
+
if (multipart) {
|
32
|
+
params[options.chunking.paramNames.filename] = name;
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
function addResumeSpecificParams(params) {
|
37
|
+
params[options.resume.paramNames.resuming] = true;
|
38
|
+
}
|
39
|
+
|
40
|
+
function getChunk(fileOrBlob, startByte, endByte) {
|
41
|
+
if (fileOrBlob.slice) {
|
42
|
+
return fileOrBlob.slice(startByte, endByte);
|
43
|
+
}
|
44
|
+
else if (fileOrBlob.mozSlice) {
|
45
|
+
return fileOrBlob.mozSlice(startByte, endByte);
|
46
|
+
}
|
47
|
+
else if (fileOrBlob.webkitSlice) {
|
48
|
+
return fileOrBlob.webkitSlice(startByte, endByte);
|
49
|
+
}
|
50
|
+
}
|
51
|
+
|
52
|
+
function getChunkData(id, chunkIndex) {
|
53
|
+
var chunkSize = options.chunking.partSize,
|
54
|
+
fileSize = api.getSize(id),
|
55
|
+
fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
|
56
|
+
startBytes = chunkSize * chunkIndex,
|
57
|
+
endBytes = startBytes+chunkSize >= fileSize ? fileSize : startBytes+chunkSize,
|
58
|
+
totalChunks = getTotalChunks(id);
|
59
|
+
|
60
|
+
return {
|
61
|
+
part: chunkIndex,
|
62
|
+
start: startBytes,
|
63
|
+
end: endBytes,
|
64
|
+
count: totalChunks,
|
65
|
+
blob: getChunk(fileOrBlob, startBytes, endBytes),
|
66
|
+
size: endBytes - startBytes
|
84
67
|
};
|
68
|
+
}
|
69
|
+
|
70
|
+
function getTotalChunks(id) {
|
71
|
+
var fileSize = api.getSize(id),
|
72
|
+
chunkSize = options.chunking.partSize;
|
73
|
+
|
74
|
+
return Math.ceil(fileSize / chunkSize);
|
75
|
+
}
|
76
|
+
|
77
|
+
function createXhr(id) {
|
78
|
+
var xhr = new XMLHttpRequest();
|
79
|
+
|
80
|
+
fileState[id].xhr = xhr;
|
85
81
|
|
86
|
-
|
82
|
+
return xhr;
|
83
|
+
}
|
84
|
+
|
85
|
+
function setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id) {
|
86
|
+
var formData = new FormData(),
|
87
|
+
method = options.demoMode ? "GET" : "POST",
|
88
|
+
endpoint = options.endpointStore.getEndpoint(id),
|
89
|
+
url = endpoint,
|
90
|
+
name = api.getName(id),
|
91
|
+
size = api.getSize(id),
|
92
|
+
blobData = fileState[id].blobData;
|
93
|
+
|
94
|
+
params[options.uuidParamName] = fileState[id].uuid;
|
95
|
+
|
96
|
+
if (multipart) {
|
97
|
+
params[options.totalFileSizeParamName] = size;
|
98
|
+
|
99
|
+
if (blobData) {
|
100
|
+
/**
|
101
|
+
* When a Blob is sent in a multipart request, the filename value in the content-disposition header is either "blob"
|
102
|
+
* or an empty string. So, we will need to include the actual file name as a param in this case.
|
103
|
+
*/
|
104
|
+
params[options.blobs.paramNames.name] = blobData.name;
|
105
|
+
}
|
106
|
+
}
|
87
107
|
|
88
108
|
//build query string
|
89
|
-
if (!
|
90
|
-
|
91
|
-
|
109
|
+
if (!options.paramsInBody) {
|
110
|
+
if (!multipart) {
|
111
|
+
params[options.inputName] = name;
|
112
|
+
}
|
113
|
+
url = qq.obj2url(params, endpoint);
|
92
114
|
}
|
93
115
|
|
94
|
-
xhr.open(
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
formData = new FormData();
|
116
|
+
xhr.open(method, url, true);
|
117
|
+
|
118
|
+
if (options.cors.expected && options.cors.sendCredentials) {
|
119
|
+
xhr.withCredentials = true;
|
120
|
+
}
|
100
121
|
|
101
|
-
|
122
|
+
if (multipart) {
|
123
|
+
if (options.paramsInBody) {
|
102
124
|
qq.obj2FormData(params, formData);
|
103
125
|
}
|
104
126
|
|
105
|
-
formData.append(
|
106
|
-
|
107
|
-
}
|
127
|
+
formData.append(options.inputName, fileOrBlob);
|
128
|
+
return formData;
|
129
|
+
}
|
130
|
+
|
131
|
+
return fileOrBlob;
|
132
|
+
}
|
133
|
+
|
134
|
+
function setHeaders(id, xhr) {
|
135
|
+
var extraHeaders = options.customHeaders,
|
136
|
+
fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
|
137
|
+
|
138
|
+
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
|
139
|
+
xhr.setRequestHeader("Cache-Control", "no-cache");
|
140
|
+
|
141
|
+
if (!multipart) {
|
108
142
|
xhr.setRequestHeader("Content-Type", "application/octet-stream");
|
109
143
|
//NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
|
110
|
-
xhr.setRequestHeader("X-Mime-Type",
|
144
|
+
xhr.setRequestHeader("X-Mime-Type", fileOrBlob.type);
|
111
145
|
}
|
112
146
|
|
113
|
-
|
114
|
-
|
115
|
-
|
147
|
+
qq.each(extraHeaders, function(name, val) {
|
148
|
+
xhr.setRequestHeader(name, val);
|
149
|
+
});
|
150
|
+
}
|
151
|
+
|
152
|
+
function handleCompletedItem(id, response, xhr) {
|
153
|
+
var name = api.getName(id),
|
154
|
+
size = api.getSize(id);
|
155
|
+
|
156
|
+
fileState[id].attemptingResume = false;
|
157
|
+
|
158
|
+
options.onProgress(id, name, size, size);
|
159
|
+
|
160
|
+
options.onComplete(id, name, response, xhr);
|
161
|
+
delete fileState[id].xhr;
|
162
|
+
uploadComplete(id);
|
163
|
+
}
|
164
|
+
|
165
|
+
function uploadNextChunk(id) {
|
166
|
+
var chunkIdx = fileState[id].remainingChunkIdxs[0],
|
167
|
+
chunkData = getChunkData(id, chunkIdx),
|
168
|
+
xhr = createXhr(id),
|
169
|
+
size = api.getSize(id),
|
170
|
+
name = api.getName(id),
|
171
|
+
toSend, params;
|
172
|
+
|
173
|
+
if (fileState[id].loaded === undefined) {
|
174
|
+
fileState[id].loaded = 0;
|
175
|
+
}
|
176
|
+
|
177
|
+
if (resumeEnabled && fileState[id].file) {
|
178
|
+
persistChunkData(id, chunkData);
|
179
|
+
}
|
180
|
+
|
181
|
+
xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
|
182
|
+
|
183
|
+
xhr.upload.onprogress = function(e) {
|
184
|
+
if (e.lengthComputable) {
|
185
|
+
var totalLoaded = e.loaded + fileState[id].loaded,
|
186
|
+
estTotalRequestsSize = calcAllRequestsSizeForChunkedUpload(id, chunkIdx, e.total);
|
187
|
+
|
188
|
+
options.onProgress(id, name, totalLoaded, estTotalRequestsSize);
|
116
189
|
}
|
190
|
+
};
|
191
|
+
|
192
|
+
options.onUploadChunk(id, name, getChunkDataForCallback(chunkData));
|
193
|
+
|
194
|
+
params = options.paramsStore.getParams(id);
|
195
|
+
addChunkingSpecificParams(id, params, chunkData);
|
196
|
+
|
197
|
+
if (fileState[id].attemptingResume) {
|
198
|
+
addResumeSpecificParams(params);
|
117
199
|
}
|
118
200
|
|
119
|
-
|
120
|
-
xhr
|
121
|
-
},
|
122
|
-
_onComplete: function(id, xhr){
|
123
|
-
"use strict";
|
124
|
-
// the request was aborted/cancelled
|
125
|
-
if (!this._files[id]) { return; }
|
201
|
+
toSend = setParamsAndGetEntityToSend(params, xhr, chunkData.blob, id);
|
202
|
+
setHeaders(id, xhr);
|
126
203
|
|
127
|
-
|
128
|
-
|
129
|
-
|
204
|
+
log('Sending chunked upload request for item ' + id + ": bytes " + (chunkData.start+1) + "-" + chunkData.end + " of " + size);
|
205
|
+
xhr.send(toSend);
|
206
|
+
}
|
130
207
|
|
131
|
-
|
208
|
+
function calcAllRequestsSizeForChunkedUpload(id, chunkIdx, requestSize) {
|
209
|
+
var chunkData = getChunkData(id, chunkIdx),
|
210
|
+
blobSize = chunkData.size,
|
211
|
+
overhead = requestSize - blobSize,
|
212
|
+
size = api.getSize(id),
|
213
|
+
chunkCount = chunkData.count,
|
214
|
+
initialRequestOverhead = fileState[id].initialRequestOverhead,
|
215
|
+
overheadDiff = overhead - initialRequestOverhead;
|
132
216
|
|
133
|
-
|
134
|
-
this.log("responseText = " + xhr.responseText);
|
217
|
+
fileState[id].lastRequestOverhead = overhead;
|
135
218
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
219
|
+
if (chunkIdx === 0) {
|
220
|
+
fileState[id].lastChunkIdxProgress = 0;
|
221
|
+
fileState[id].initialRequestOverhead = overhead;
|
222
|
+
fileState[id].estTotalRequestsSize = size + (chunkCount * overhead);
|
223
|
+
}
|
224
|
+
else if (fileState[id].lastChunkIdxProgress !== chunkIdx) {
|
225
|
+
fileState[id].lastChunkIdxProgress = chunkIdx;
|
226
|
+
fileState[id].estTotalRequestsSize += overheadDiff;
|
227
|
+
}
|
228
|
+
|
229
|
+
return fileState[id].estTotalRequestsSize;
|
230
|
+
}
|
231
|
+
|
232
|
+
function getLastRequestOverhead(id) {
|
233
|
+
if (multipart) {
|
234
|
+
return fileState[id].lastRequestOverhead;
|
235
|
+
}
|
236
|
+
else {
|
237
|
+
return 0;
|
238
|
+
}
|
239
|
+
}
|
240
|
+
|
241
|
+
function handleSuccessfullyCompletedChunk(id, response, xhr) {
|
242
|
+
var chunkIdx = fileState[id].remainingChunkIdxs.shift(),
|
243
|
+
chunkData = getChunkData(id, chunkIdx);
|
244
|
+
|
245
|
+
fileState[id].attemptingResume = false;
|
246
|
+
fileState[id].loaded += chunkData.size + getLastRequestOverhead(id);
|
247
|
+
|
248
|
+
if (fileState[id].remainingChunkIdxs.length > 0) {
|
249
|
+
uploadNextChunk(id);
|
250
|
+
}
|
251
|
+
else {
|
252
|
+
if (resumeEnabled) {
|
253
|
+
deletePersistedChunkData(id);
|
141
254
|
}
|
142
|
-
|
143
|
-
|
255
|
+
|
256
|
+
handleCompletedItem(id, response, xhr);
|
257
|
+
}
|
258
|
+
}
|
259
|
+
|
260
|
+
function isErrorResponse(xhr, response) {
|
261
|
+
return xhr.status !== 200 || !response.success || response.reset;
|
262
|
+
}
|
263
|
+
|
264
|
+
function parseResponse(xhr) {
|
265
|
+
var response;
|
266
|
+
|
267
|
+
try {
|
268
|
+
response = qq.parseJson(xhr.responseText);
|
269
|
+
}
|
270
|
+
catch(error) {
|
271
|
+
log('Error when attempting to parse xhr response text (' + error + ')', 'error');
|
144
272
|
response = {};
|
145
273
|
}
|
146
274
|
|
147
|
-
|
148
|
-
|
149
|
-
|
275
|
+
return response;
|
276
|
+
}
|
277
|
+
|
278
|
+
function handleResetResponse(id) {
|
279
|
+
log('Server has ordered chunking effort to be restarted on next attempt for item ID ' + id, 'error');
|
280
|
+
|
281
|
+
if (resumeEnabled) {
|
282
|
+
deletePersistedChunkData(id);
|
283
|
+
fileState[id].attemptingResume = false;
|
284
|
+
}
|
285
|
+
|
286
|
+
fileState[id].remainingChunkIdxs = [];
|
287
|
+
delete fileState[id].loaded;
|
288
|
+
delete fileState[id].estTotalRequestsSize;
|
289
|
+
delete fileState[id].initialRequestOverhead;
|
290
|
+
}
|
291
|
+
|
292
|
+
function handleResetResponseOnResumeAttempt(id) {
|
293
|
+
fileState[id].attemptingResume = false;
|
294
|
+
log("Server has declared that it cannot handle resume for item ID " + id + " - starting from the first chunk", 'error');
|
295
|
+
handleResetResponse(id);
|
296
|
+
api.upload(id, true);
|
297
|
+
}
|
298
|
+
|
299
|
+
function handleNonResetErrorResponse(id, response, xhr) {
|
300
|
+
var name = api.getName(id);
|
301
|
+
|
302
|
+
if (options.onAutoRetry(id, name, response, xhr)) {
|
303
|
+
return;
|
304
|
+
}
|
305
|
+
else {
|
306
|
+
handleCompletedItem(id, response, xhr);
|
307
|
+
}
|
308
|
+
}
|
309
|
+
|
310
|
+
function onComplete(id, xhr) {
|
311
|
+
var response;
|
312
|
+
|
313
|
+
// the request was aborted/cancelled
|
314
|
+
if (!fileState[id]) {
|
315
|
+
return;
|
316
|
+
}
|
317
|
+
|
318
|
+
log("xhr - server response received for " + id);
|
319
|
+
log("responseText = " + xhr.responseText);
|
320
|
+
response = parseResponse(xhr);
|
321
|
+
|
322
|
+
if (isErrorResponse(xhr, response)) {
|
323
|
+
if (response.reset) {
|
324
|
+
handleResetResponse(id);
|
325
|
+
}
|
326
|
+
|
327
|
+
if (fileState[id].attemptingResume && response.reset) {
|
328
|
+
handleResetResponseOnResumeAttempt(id);
|
150
329
|
}
|
330
|
+
else {
|
331
|
+
handleNonResetErrorResponse(id, response, xhr);
|
332
|
+
}
|
333
|
+
}
|
334
|
+
else if (chunkFiles) {
|
335
|
+
handleSuccessfullyCompletedChunk(id, response, xhr);
|
336
|
+
}
|
337
|
+
else {
|
338
|
+
handleCompletedItem(id, response, xhr);
|
151
339
|
}
|
340
|
+
}
|
341
|
+
|
342
|
+
function getChunkDataForCallback(chunkData) {
|
343
|
+
return {
|
344
|
+
partIndex: chunkData.part,
|
345
|
+
startByte: chunkData.start + 1,
|
346
|
+
endByte: chunkData.end,
|
347
|
+
totalParts: chunkData.count
|
348
|
+
};
|
349
|
+
}
|
350
|
+
|
351
|
+
function getReadyStateChangeHandler(id, xhr) {
|
352
|
+
return function() {
|
353
|
+
if (xhr.readyState === 4) {
|
354
|
+
onComplete(id, xhr);
|
355
|
+
}
|
356
|
+
};
|
357
|
+
}
|
358
|
+
|
359
|
+
function persistChunkData(id, chunkData) {
|
360
|
+
var fileUuid = api.getUuid(id),
|
361
|
+
lastByteSent = fileState[id].loaded,
|
362
|
+
initialRequestOverhead = fileState[id].initialRequestOverhead,
|
363
|
+
estTotalRequestsSize = fileState[id].estTotalRequestsSize,
|
364
|
+
cookieName = getChunkDataCookieName(id),
|
365
|
+
cookieValue = fileUuid +
|
366
|
+
cookieItemDelimiter + chunkData.part +
|
367
|
+
cookieItemDelimiter + lastByteSent +
|
368
|
+
cookieItemDelimiter + initialRequestOverhead +
|
369
|
+
cookieItemDelimiter + estTotalRequestsSize,
|
370
|
+
cookieExpDays = options.resume.cookiesExpireIn;
|
371
|
+
|
372
|
+
qq.setCookie(cookieName, cookieValue, cookieExpDays);
|
373
|
+
}
|
374
|
+
|
375
|
+
function deletePersistedChunkData(id) {
|
376
|
+
if (fileState[id].file) {
|
377
|
+
var cookieName = getChunkDataCookieName(id);
|
378
|
+
qq.deleteCookie(cookieName);
|
379
|
+
}
|
380
|
+
}
|
381
|
+
|
382
|
+
function getPersistedChunkData(id) {
|
383
|
+
var chunkCookieValue = qq.getCookie(getChunkDataCookieName(id)),
|
384
|
+
filename = api.getName(id),
|
385
|
+
sections, uuid, partIndex, lastByteSent, initialRequestOverhead, estTotalRequestsSize;
|
386
|
+
|
387
|
+
if (chunkCookieValue) {
|
388
|
+
sections = chunkCookieValue.split(cookieItemDelimiter);
|
389
|
+
|
390
|
+
if (sections.length === 5) {
|
391
|
+
uuid = sections[0];
|
392
|
+
partIndex = parseInt(sections[1], 10);
|
393
|
+
lastByteSent = parseInt(sections[2], 10);
|
394
|
+
initialRequestOverhead = parseInt(sections[3], 10);
|
395
|
+
estTotalRequestsSize = parseInt(sections[4], 10);
|
396
|
+
|
397
|
+
return {
|
398
|
+
uuid: uuid,
|
399
|
+
part: partIndex,
|
400
|
+
lastByteSent: lastByteSent,
|
401
|
+
initialRequestOverhead: initialRequestOverhead,
|
402
|
+
estTotalRequestsSize: estTotalRequestsSize
|
403
|
+
};
|
404
|
+
}
|
405
|
+
else {
|
406
|
+
log('Ignoring previously stored resume/chunk cookie for ' + filename + " - old cookie format", "warn");
|
407
|
+
}
|
408
|
+
}
|
409
|
+
}
|
410
|
+
|
411
|
+
function getChunkDataCookieName(id) {
|
412
|
+
var filename = api.getName(id),
|
413
|
+
fileSize = api.getSize(id),
|
414
|
+
maxChunkSize = options.chunking.partSize,
|
415
|
+
cookieName;
|
416
|
+
|
417
|
+
cookieName = "qqfilechunk" + cookieItemDelimiter + encodeURIComponent(filename) + cookieItemDelimiter + fileSize + cookieItemDelimiter + maxChunkSize;
|
418
|
+
|
419
|
+
if (resumeId !== undefined) {
|
420
|
+
cookieName += cookieItemDelimiter + resumeId;
|
421
|
+
}
|
422
|
+
|
423
|
+
return cookieName;
|
424
|
+
}
|
425
|
+
|
426
|
+
function getResumeId() {
|
427
|
+
if (options.resume.id !== null &&
|
428
|
+
options.resume.id !== undefined &&
|
429
|
+
!qq.isFunction(options.resume.id) &&
|
430
|
+
!qq.isObject(options.resume.id)) {
|
431
|
+
|
432
|
+
return options.resume.id;
|
433
|
+
}
|
434
|
+
}
|
152
435
|
|
153
|
-
|
436
|
+
function handleFileChunkingUpload(id, retry) {
|
437
|
+
var name = api.getName(id),
|
438
|
+
firstChunkIndex = 0,
|
439
|
+
persistedChunkInfoForResume, firstChunkDataForResume, currentChunkIndex;
|
154
440
|
|
155
|
-
|
156
|
-
|
157
|
-
},
|
158
|
-
_cancel: function(id){
|
159
|
-
this._options.onCancel(id, this.getName(id));
|
441
|
+
if (!fileState[id].remainingChunkIdxs || fileState[id].remainingChunkIdxs.length === 0) {
|
442
|
+
fileState[id].remainingChunkIdxs = [];
|
160
443
|
|
161
|
-
|
444
|
+
if (resumeEnabled && !retry && fileState[id].file) {
|
445
|
+
persistedChunkInfoForResume = getPersistedChunkData(id);
|
446
|
+
if (persistedChunkInfoForResume) {
|
447
|
+
firstChunkDataForResume = getChunkData(id, persistedChunkInfoForResume.part);
|
448
|
+
if (options.onResume(id, name, getChunkDataForCallback(firstChunkDataForResume)) !== false) {
|
449
|
+
firstChunkIndex = persistedChunkInfoForResume.part;
|
450
|
+
fileState[id].uuid = persistedChunkInfoForResume.uuid;
|
451
|
+
fileState[id].loaded = persistedChunkInfoForResume.lastByteSent;
|
452
|
+
fileState[id].estTotalRequestsSize = persistedChunkInfoForResume.estTotalRequestsSize;
|
453
|
+
fileState[id].initialRequestOverhead = persistedChunkInfoForResume.initialRequestOverhead;
|
454
|
+
fileState[id].attemptingResume = true;
|
455
|
+
log('Resuming ' + name + " at partition index " + firstChunkIndex);
|
456
|
+
}
|
457
|
+
}
|
458
|
+
}
|
162
459
|
|
163
|
-
|
164
|
-
|
165
|
-
|
460
|
+
for (currentChunkIndex = getTotalChunks(id)-1; currentChunkIndex >= firstChunkIndex; currentChunkIndex-=1) {
|
461
|
+
fileState[id].remainingChunkIdxs.unshift(currentChunkIndex);
|
462
|
+
}
|
166
463
|
}
|
464
|
+
|
465
|
+
uploadNextChunk(id);
|
466
|
+
}
|
467
|
+
|
468
|
+
function handleStandardFileUpload(id) {
|
469
|
+
var fileOrBlob = fileState[id].file || fileState[id].blobData.blob,
|
470
|
+
name = api.getName(id),
|
471
|
+
xhr, params, toSend;
|
472
|
+
|
473
|
+
fileState[id].loaded = 0;
|
474
|
+
|
475
|
+
xhr = createXhr(id);
|
476
|
+
|
477
|
+
xhr.upload.onprogress = function(e){
|
478
|
+
if (e.lengthComputable){
|
479
|
+
fileState[id].loaded = e.loaded;
|
480
|
+
options.onProgress(id, name, e.loaded, e.total);
|
481
|
+
}
|
482
|
+
};
|
483
|
+
|
484
|
+
xhr.onreadystatechange = getReadyStateChangeHandler(id, xhr);
|
485
|
+
|
486
|
+
params = options.paramsStore.getParams(id);
|
487
|
+
toSend = setParamsAndGetEntityToSend(params, xhr, fileOrBlob, id);
|
488
|
+
setHeaders(id, xhr);
|
489
|
+
|
490
|
+
log('Sending upload request for ' + id);
|
491
|
+
xhr.send(toSend);
|
167
492
|
}
|
168
|
-
|
493
|
+
|
494
|
+
|
495
|
+
api = {
|
496
|
+
/**
|
497
|
+
* Adds File or Blob to the queue
|
498
|
+
* Returns id to use with upload, cancel
|
499
|
+
**/
|
500
|
+
add: function(fileOrBlobData){
|
501
|
+
var id;
|
502
|
+
|
503
|
+
if (fileOrBlobData instanceof File) {
|
504
|
+
id = fileState.push({file: fileOrBlobData}) - 1;
|
505
|
+
}
|
506
|
+
else if (fileOrBlobData.blob instanceof Blob) {
|
507
|
+
id = fileState.push({blobData: fileOrBlobData}) - 1;
|
508
|
+
}
|
509
|
+
else {
|
510
|
+
throw new Error('Passed obj in not a File or BlobData (in qq.UploadHandlerXhr)');
|
511
|
+
}
|
512
|
+
|
513
|
+
fileState[id].uuid = qq.getUniqueId();
|
514
|
+
return id;
|
515
|
+
},
|
516
|
+
getName: function(id){
|
517
|
+
if (api.isValid(id)) {
|
518
|
+
var file = fileState[id].file,
|
519
|
+
blobData = fileState[id].blobData;
|
520
|
+
|
521
|
+
if (file) {
|
522
|
+
// fix missing name in Safari 4
|
523
|
+
//NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
|
524
|
+
return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
|
525
|
+
}
|
526
|
+
else {
|
527
|
+
return blobData.name;
|
528
|
+
}
|
529
|
+
}
|
530
|
+
else {
|
531
|
+
log(id + " is not a valid item ID.", "error");
|
532
|
+
}
|
533
|
+
},
|
534
|
+
getSize: function(id){
|
535
|
+
/*jshint eqnull: true*/
|
536
|
+
var fileOrBlob = fileState[id].file || fileState[id].blobData.blob;
|
537
|
+
|
538
|
+
if (qq.isFileOrInput(fileOrBlob)) {
|
539
|
+
return fileOrBlob.fileSize != null ? fileOrBlob.fileSize : fileOrBlob.size;
|
540
|
+
}
|
541
|
+
else {
|
542
|
+
return fileOrBlob.size;
|
543
|
+
}
|
544
|
+
},
|
545
|
+
getFile: function(id) {
|
546
|
+
if (fileState[id]) {
|
547
|
+
return fileState[id].file || fileState[id].blobData.blob;
|
548
|
+
}
|
549
|
+
},
|
550
|
+
/**
|
551
|
+
* Returns uploaded bytes for file identified by id
|
552
|
+
*/
|
553
|
+
getLoaded: function(id){
|
554
|
+
return fileState[id].loaded || 0;
|
555
|
+
},
|
556
|
+
isValid: function(id) {
|
557
|
+
return fileState[id] !== undefined;
|
558
|
+
},
|
559
|
+
reset: function() {
|
560
|
+
fileState = [];
|
561
|
+
},
|
562
|
+
getUuid: function(id) {
|
563
|
+
return fileState[id].uuid;
|
564
|
+
},
|
565
|
+
/**
|
566
|
+
* Sends the file identified by id to the server
|
567
|
+
*/
|
568
|
+
upload: function(id, retry){
|
569
|
+
var name = this.getName(id);
|
570
|
+
|
571
|
+
options.onUpload(id, name);
|
572
|
+
|
573
|
+
if (chunkFiles) {
|
574
|
+
handleFileChunkingUpload(id, retry);
|
575
|
+
}
|
576
|
+
else {
|
577
|
+
handleStandardFileUpload(id);
|
578
|
+
}
|
579
|
+
},
|
580
|
+
cancel: function(id){
|
581
|
+
var xhr = fileState[id].xhr;
|
582
|
+
|
583
|
+
options.onCancel(id, this.getName(id));
|
584
|
+
|
585
|
+
if (xhr) {
|
586
|
+
xhr.onreadystatechange = null;
|
587
|
+
xhr.abort();
|
588
|
+
}
|
589
|
+
|
590
|
+
if (resumeEnabled) {
|
591
|
+
deletePersistedChunkData(id);
|
592
|
+
}
|
593
|
+
|
594
|
+
delete fileState[id];
|
595
|
+
},
|
596
|
+
getResumableFilesData: function() {
|
597
|
+
var matchingCookieNames = [],
|
598
|
+
resumableFilesData = [];
|
599
|
+
|
600
|
+
if (chunkFiles && resumeEnabled) {
|
601
|
+
if (resumeId === undefined) {
|
602
|
+
matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
|
603
|
+
cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "="));
|
604
|
+
}
|
605
|
+
else {
|
606
|
+
matchingCookieNames = qq.getCookieNames(new RegExp("^qqfilechunk\\" + cookieItemDelimiter + ".+\\" +
|
607
|
+
cookieItemDelimiter + "\\d+\\" + cookieItemDelimiter + options.chunking.partSize + "\\" +
|
608
|
+
cookieItemDelimiter + resumeId + "="));
|
609
|
+
}
|
610
|
+
|
611
|
+
qq.each(matchingCookieNames, function(idx, cookieName) {
|
612
|
+
var cookiesNameParts = cookieName.split(cookieItemDelimiter);
|
613
|
+
var cookieValueParts = qq.getCookie(cookieName).split(cookieItemDelimiter);
|
614
|
+
|
615
|
+
resumableFilesData.push({
|
616
|
+
name: decodeURIComponent(cookiesNameParts[1]),
|
617
|
+
size: cookiesNameParts[2],
|
618
|
+
uuid: cookieValueParts[0],
|
619
|
+
partIdx: cookieValueParts[1]
|
620
|
+
});
|
621
|
+
});
|
622
|
+
|
623
|
+
return resumableFilesData;
|
624
|
+
}
|
625
|
+
return [];
|
626
|
+
}
|
627
|
+
};
|
628
|
+
|
629
|
+
return api;
|
630
|
+
};
|