|
29 | 29 | * @param {number} [opts.chunkRetryInterval] |
30 | 30 | * @param {Array.<number>} [opts.permanentErrors] |
31 | 31 | * @param {Array.<number>} [opts.successStatuses] |
| 32 | + * @param {Function} [opts.initFileFn] |
| 33 | + * @param {Function} [opts.readFileFn] |
32 | 34 | * @param {Function} [opts.generateUniqueIdentifier] |
33 | 35 | * @constructor |
34 | 36 | */ |
|
91 | 93 | chunkRetryInterval: null, |
92 | 94 | permanentErrors: [404, 415, 500, 501], |
93 | 95 | successStatuses: [200, 201, 202], |
94 | | - onDropStopPropagation: false |
| 96 | + onDropStopPropagation: false, |
| 97 | + initFileFn: null, |
| 98 | + readFileFn: webAPIFileRead |
95 | 99 | }; |
96 | | - |
| 100 | + |
97 | 101 | /** |
98 | 102 | * Current options |
99 | 103 | * @type {Object} |
|
144 | 148 | * @type {Object} |
145 | 149 | */ |
146 | 150 | this.opts = Flow.extend({}, this.defaults, opts || {}); |
| 151 | + |
147 | 152 | } |
148 | 153 |
|
149 | 154 | Flow.prototype = { |
|
697 | 702 | * @type {Flow} |
698 | 703 | */ |
699 | 704 | this.flowObj = flowObj; |
| 705 | + |
| 706 | + /** |
| 707 | + * Used to store the bytes read |
| 708 | + * @type {Blob|string} |
| 709 | + */ |
| 710 | + this.bytes = null; |
700 | 711 |
|
701 | 712 | /** |
702 | 713 | * Reference to file |
|
906 | 917 | * @function |
907 | 918 | */ |
908 | 919 | bootstrap: function () { |
| 920 | + if (typeof this.flowObj.opts.initFileFn === "function") { |
| 921 | + this.flowObj.opts.initFileFn(this); |
| 922 | + } |
| 923 | + |
909 | 924 | this.abort(true); |
910 | 925 | this.error = false; |
911 | 926 | // Rebuild stack of chunks from file |
912 | 927 | this._prevProgress = 0; |
913 | 928 | var round = this.flowObj.opts.forceChunkSize ? Math.ceil : Math.floor; |
914 | 929 | var chunks = Math.max( |
915 | | - round(this.file.size / this.flowObj.opts.chunkSize), 1 |
| 930 | + round(this.size / this.flowObj.opts.chunkSize), 1 |
916 | 931 | ); |
917 | 932 | for (var offset = 0; offset < chunks; offset++) { |
918 | 933 | this.chunks.push( |
|
971 | 986 | var outstanding = false; |
972 | 987 | each(this.chunks, function (chunk) { |
973 | 988 | var status = chunk.status(); |
974 | | - if (status === 'pending' || status === 'uploading' || chunk.preprocessState === 1) { |
| 989 | + if (status === 'pending' || status === 'uploading' || status === 'reading' || chunk.preprocessState === 1 || chunk.readState === 1) { |
975 | 990 | outstanding = true; |
976 | 991 | return false; |
977 | 992 | } |
|
1031 | 1046 | } |
1032 | 1047 | }; |
1033 | 1048 |
|
| 1049 | + /** |
| 1050 | + * Default read function using the webAPI |
| 1051 | + * |
| 1052 | + * @function webAPIFileRead(fileObj, fileType, startByte, endByte, chunk) |
| 1053 | + * |
| 1054 | + */ |
| 1055 | + function webAPIFileRead(fileObj, fileType, startByte, endByte, chunk) { |
| 1056 | + var function_name = 'slice'; |
1034 | 1057 |
|
| 1058 | + if (fileObj.file.slice) |
| 1059 | + function_name = 'slice'; |
| 1060 | + else if (fileObj.file.mozSlice) |
| 1061 | + function_name = 'mozSlice'; |
| 1062 | + else if (fileObj.file.webkitSlice) |
| 1063 | + function_name = 'webkitSlice'; |
1035 | 1064 |
|
1036 | | - |
1037 | | - |
1038 | | - |
| 1065 | + chunk.readFinished(fileObj.file[function_name](startByte, endByte, fileType)); |
| 1066 | + } |
1039 | 1067 |
|
1040 | 1068 |
|
1041 | 1069 | /** |
|
1060 | 1088 | */ |
1061 | 1089 | this.fileObj = fileObj; |
1062 | 1090 |
|
1063 | | - /** |
1064 | | - * File size |
1065 | | - * @type {number} |
1066 | | - */ |
1067 | | - this.fileObjSize = fileObj.size; |
1068 | | - |
1069 | 1091 | /** |
1070 | 1092 | * File offset |
1071 | 1093 | * @type {number} |
|
1096 | 1118 | */ |
1097 | 1119 | this.preprocessState = 0; |
1098 | 1120 |
|
| 1121 | + /** |
| 1122 | + * Read state |
| 1123 | + * @type {number} 0 = not read, 1 = reading, 2 = finished |
| 1124 | + */ |
| 1125 | + this.readState = 0; |
| 1126 | + |
| 1127 | + |
1099 | 1128 | /** |
1100 | 1129 | * Bytes transferred from total request size |
1101 | 1130 | * @type {number} |
|
1112 | 1141 | * Size of a chunk |
1113 | 1142 | * @type {number} |
1114 | 1143 | */ |
1115 | | - var chunkSize = this.flowObj.opts.chunkSize; |
| 1144 | + this.chunkSize = this.flowObj.opts.chunkSize; |
1116 | 1145 |
|
1117 | 1146 | /** |
1118 | 1147 | * Chunk start byte in a file |
1119 | 1148 | * @type {number} |
1120 | 1149 | */ |
1121 | | - this.startByte = this.offset * chunkSize; |
| 1150 | + this.startByte = this.offset * this.chunkSize; |
1122 | 1151 |
|
1123 | 1152 | /** |
1124 | 1153 | * Chunk end byte in a file |
1125 | 1154 | * @type {number} |
1126 | 1155 | */ |
1127 | | - this.endByte = Math.min(this.fileObjSize, (this.offset + 1) * chunkSize); |
| 1156 | + this.endByte = Math.min(this.fileObj.size, (this.offset + 1) * this.chunkSize); |
1128 | 1157 |
|
1129 | 1158 | /** |
1130 | 1159 | * XMLHttpRequest |
1131 | 1160 | * @type {XMLHttpRequest} |
1132 | 1161 | */ |
1133 | 1162 | this.xhr = null; |
1134 | 1163 |
|
1135 | | - if (this.fileObjSize - this.endByte < chunkSize && |
1136 | | - !this.flowObj.opts.forceChunkSize) { |
1137 | | - // The last chunk will be bigger than the chunk size, |
1138 | | - // but less than 2*chunkSize |
1139 | | - this.endByte = this.fileObjSize; |
1140 | | - } |
1141 | | - |
1142 | 1164 | var $ = this; |
1143 | 1165 |
|
1144 | 1166 |
|
|
1192 | 1214 | this.doneHandler = function(event) { |
1193 | 1215 | var status = $.status(); |
1194 | 1216 | if (status === 'success' || status === 'error') { |
| 1217 | + delete this.data; |
1195 | 1218 | $.event(status, $.message()); |
1196 | 1219 | $.flowObj.uploadNextChunk(); |
1197 | 1220 | } else { |
|
1221 | 1244 | flowChunkNumber: this.offset + 1, |
1222 | 1245 | flowChunkSize: this.flowObj.opts.chunkSize, |
1223 | 1246 | flowCurrentChunkSize: this.endByte - this.startByte, |
1224 | | - flowTotalSize: this.fileObjSize, |
| 1247 | + flowTotalSize: this.fileObj.size, |
1225 | 1248 | flowIdentifier: this.fileObj.uniqueIdentifier, |
1226 | 1249 | flowFilename: this.fileObj.name, |
1227 | 1250 | flowRelativePath: this.fileObj.relativePath, |
|
1264 | 1287 | * @function |
1265 | 1288 | */ |
1266 | 1289 | preprocessFinished: function () { |
| 1290 | + // Compute the endByte after the preprocess function to allow an |
| 1291 | + // implementer of preprocess to set the fileObj size |
| 1292 | + this.endByte = Math.min(this.fileObj.size, (this.offset + 1) * this.chunkSize); |
| 1293 | + if (this.fileObj.size - this.endByte < this.chunkSize && |
| 1294 | + !this.flowObj.opts.forceChunkSize) { |
| 1295 | + // The last chunk will be bigger than the chunk size, |
| 1296 | + // but less than 2*this.chunkSize |
| 1297 | + this.endByte = this.fileObj.size; |
| 1298 | + } |
1267 | 1299 | this.preprocessState = 2; |
1268 | 1300 | this.send(); |
1269 | 1301 | }, |
1270 | 1302 |
|
| 1303 | + /** |
| 1304 | + * Finish read state |
| 1305 | + * @function |
| 1306 | + */ |
| 1307 | + readFinished: function (bytes) { |
| 1308 | + this.readState = 2; |
| 1309 | + this.bytes = bytes; |
| 1310 | + this.send(); |
| 1311 | + }, |
| 1312 | + |
| 1313 | + |
1271 | 1314 | /** |
1272 | 1315 | * Uploads the actual data in a POST call |
1273 | 1316 | * @function |
1274 | 1317 | */ |
1275 | 1318 | send: function () { |
1276 | 1319 | var preprocess = this.flowObj.opts.preprocess; |
| 1320 | + var read = this.flowObj.opts.readFileFn; |
1277 | 1321 | if (typeof preprocess === 'function') { |
1278 | 1322 | switch (this.preprocessState) { |
1279 | 1323 | case 0: |
|
1284 | 1328 | return; |
1285 | 1329 | } |
1286 | 1330 | } |
| 1331 | + switch (this.readState) { |
| 1332 | + case 0: |
| 1333 | + this.readState = 1; |
| 1334 | + read(this.fileObj, this.startByte, this.endByte, this.fileType, this); |
| 1335 | + return; |
| 1336 | + case 1: |
| 1337 | + return; |
| 1338 | + } |
1287 | 1339 | if (this.flowObj.opts.testChunks && !this.tested) { |
1288 | 1340 | this.test(); |
1289 | 1341 | return; |
|
1293 | 1345 | this.total = 0; |
1294 | 1346 | this.pendingRetry = false; |
1295 | 1347 |
|
1296 | | - var func = (this.fileObj.file.slice ? 'slice' : |
1297 | | - (this.fileObj.file.mozSlice ? 'mozSlice' : |
1298 | | - (this.fileObj.file.webkitSlice ? 'webkitSlice' : |
1299 | | - 'slice'))); |
1300 | | - var bytes = this.fileObj.file[func](this.startByte, this.endByte, this.fileObj.file.type); |
1301 | | - |
1302 | 1348 | // Set up request and listen for event |
1303 | 1349 | this.xhr = new XMLHttpRequest(); |
1304 | 1350 | this.xhr.upload.addEventListener('progress', this.progressHandler, false); |
1305 | 1351 | this.xhr.addEventListener("load", this.doneHandler, false); |
1306 | 1352 | this.xhr.addEventListener("error", this.doneHandler, false); |
1307 | 1353 |
|
1308 | 1354 | var uploadMethod = evalOpts(this.flowObj.opts.uploadMethod, this.fileObj, this); |
1309 | | - var data = this.prepareXhrRequest(uploadMethod, false, this.flowObj.opts.method, bytes); |
| 1355 | + var data = this.prepareXhrRequest(uploadMethod, false, this.flowObj.opts.method, this.bytes); |
1310 | 1356 | this.xhr.send(data); |
1311 | 1357 | }, |
1312 | 1358 |
|
|
1329 | 1375 | * @returns {string} 'pending', 'uploading', 'success', 'error' |
1330 | 1376 | */ |
1331 | 1377 | status: function (isTest) { |
1332 | | - if (this.pendingRetry || this.preprocessState === 1) { |
| 1378 | + if (this.readState === 1) { |
| 1379 | + return 'reading'; |
| 1380 | + } else if (this.pendingRetry || this.preprocessState === 1) { |
1333 | 1381 | // if pending retry then that's effectively the same as actively uploading, |
1334 | 1382 | // there might just be a slight delay before the retry starts |
1335 | 1383 | return 'uploading'; |
|
0 commit comments