fine_uploader 3.1.1 → 3.4.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
};
|