|
29 | 29 | * @param {number} [opts.chunkRetryInterval] |
30 | 30 | * @param {Array.<number>} [opts.permanentErrors] |
31 | 31 | * @param {Array.<number>} [opts.successStatuses] |
| 32 | + * @param {Function} [opts.read] |
32 | 33 | * @param {Function} [opts.generateUniqueIdentifier] |
33 | 34 | * @constructor |
34 | 35 | */ |
|
91 | 92 | chunkRetryInterval: null, |
92 | 93 | permanentErrors: [404, 415, 500, 501], |
93 | 94 | successStatuses: [200, 201, 202], |
94 | | - onDropStopPropagation: false |
| 95 | + onDropStopPropagation: false, |
| 96 | + read: webAPIFileRead, |
95 | 97 | }; |
96 | | - |
| 98 | + |
97 | 99 | /** |
98 | 100 | * Current options |
99 | 101 | * @type {Object} |
|
144 | 146 | * @type {Object} |
145 | 147 | */ |
146 | 148 | this.opts = Flow.extend({}, this.defaults, opts || {}); |
| 149 | + |
| 150 | + if (!this.opts.fileFactory.supportsPrioritizeFirstAndLastChunk && |
| 151 | + this.defaults.prioritizeFirstAndLastChunk) { |
| 152 | + throw Error("Cannot use prioritizeFirstAndLastChunk and with this fileFactory."); |
| 153 | + } |
| 154 | + |
147 | 155 | } |
148 | 156 |
|
149 | 157 | Flow.prototype = { |
|
697 | 705 | * @type {Flow} |
698 | 706 | */ |
699 | 707 | this.flowObj = flowObj; |
| 708 | + |
| 709 | + /** |
| 710 | + * Used to store the bytes read |
| 711 | + * @type {Blob|string} |
| 712 | + */ |
| 713 | + this.bytes = null; |
700 | 714 |
|
701 | 715 | /** |
702 | 716 | * Reference to file |
|
714 | 728 | * File size |
715 | 729 | * @type {number} |
716 | 730 | */ |
717 | | - this.size = file.size; |
| 731 | + this.size = this.fileProxy.size; |
718 | 732 |
|
719 | 733 | /** |
720 | 734 | * Relative file path |
|
912 | 926 | this._prevProgress = 0; |
913 | 927 | var round = this.flowObj.opts.forceChunkSize ? Math.ceil : Math.floor; |
914 | 928 | var chunks = Math.max( |
915 | | - round(this.file.size / this.flowObj.opts.chunkSize), 1 |
| 929 | + round(this.size / this.flowObj.opts.chunkSize), 1 |
916 | 930 | ); |
917 | 931 | for (var offset = 0; offset < chunks; offset++) { |
918 | 932 | this.chunks.push( |
|
971 | 985 | var outstanding = false; |
972 | 986 | each(this.chunks, function (chunk) { |
973 | 987 | var status = chunk.status(); |
974 | | - if (status === 'pending' || status === 'uploading' || chunk.preprocessState === 1) { |
| 988 | + if (status === 'pending' || status === 'uploading' || status === 'reading' || chunk.preprocessState === 1 || chunk.readState === 1) { |
975 | 989 | outstanding = true; |
976 | 990 | return false; |
977 | 991 | } |
|
1021 | 1035 | return this.file.type && this.file.type.split('/')[1]; |
1022 | 1036 | }, |
1023 | 1037 |
|
1024 | | - /** |
1025 | | - * Get file extension |
1026 | | - * @function |
1027 | | - * @returns {string} |
1028 | | - */ |
1029 | | - getExtension: function () { |
1030 | | - return this.name.substr((~-this.name.lastIndexOf(".") >>> 0) + 2).toLowerCase(); |
1031 | | - } |
1032 | 1038 | }; |
1033 | 1039 |
|
1034 | | - |
1035 | | - |
1036 | | - |
1037 | | - |
1038 | | - |
| 1040 | + /** |
| 1041 | + * Default read function using the webAPI |
| 1042 | + * |
| 1043 | + * @function webAPIFileRead(chunk, startByte, endByte, fileType) |
| 1044 | + * |
| 1045 | + */ |
| 1046 | + function webAPIFileRead(chunk, startByte, endByte, fileType) { |
| 1047 | + var function_name = (chunk.fileObj.file.slice ? 'slice' : |
| 1048 | + (chunk.fileObj.file.mozSlice ? 'mozSlice' : |
| 1049 | + (chunk.fileObj.file.webkitSlice ? 'webkitSlice' : |
| 1050 | + 'slice'))); |
| 1051 | + chunk.readFinished(chunk.fileObj.file[function_name](startByte, endByte, fileType)); |
| 1052 | + } |
1039 | 1053 |
|
1040 | 1054 |
|
1041 | 1055 | /** |
|
1060 | 1074 | */ |
1061 | 1075 | this.fileObj = fileObj; |
1062 | 1076 |
|
1063 | | - /** |
1064 | | - * File size |
1065 | | - * @type {number} |
1066 | | - */ |
1067 | | - this.fileObjSize = fileObj.size; |
1068 | | - |
1069 | 1077 | /** |
1070 | 1078 | * File offset |
1071 | 1079 | * @type {number} |
|
1096 | 1104 | */ |
1097 | 1105 | this.preprocessState = 0; |
1098 | 1106 |
|
| 1107 | + /** |
| 1108 | + * Read state |
| 1109 | + * @type {number} 0 = not read, 1 = reading, 2 = finished |
| 1110 | + */ |
| 1111 | + this.readState = 0; |
| 1112 | + |
| 1113 | + |
1099 | 1114 | /** |
1100 | 1115 | * Bytes transferred from total request size |
1101 | 1116 | * @type {number} |
|
1124 | 1139 | * Chunk end byte in a file |
1125 | 1140 | * @type {number} |
1126 | 1141 | */ |
1127 | | - this.endByte = Math.min(this.fileObjSize, (this.offset + 1) * chunkSize); |
| 1142 | + this.endByte = Math.min(this.fileObj.size, (this.offset + 1) * chunkSize); |
| 1143 | + |
| 1144 | + this.data = null; |
1128 | 1145 |
|
1129 | 1146 | /** |
1130 | 1147 | * XMLHttpRequest |
1131 | 1148 | * @type {XMLHttpRequest} |
1132 | 1149 | */ |
1133 | 1150 | this.xhr = null; |
1134 | 1151 |
|
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 | 1152 | var $ = this; |
1143 | 1153 |
|
1144 | 1154 |
|
|
1192 | 1202 | this.doneHandler = function(event) { |
1193 | 1203 | var status = $.status(); |
1194 | 1204 | if (status === 'success' || status === 'error') { |
| 1205 | + delete this.data; |
1195 | 1206 | $.event(status, $.message()); |
1196 | 1207 | $.flowObj.uploadNextChunk(); |
1197 | 1208 | } else { |
|
1221 | 1232 | flowChunkNumber: this.offset + 1, |
1222 | 1233 | flowChunkSize: this.flowObj.opts.chunkSize, |
1223 | 1234 | flowCurrentChunkSize: this.endByte - this.startByte, |
1224 | | - flowTotalSize: this.fileObjSize, |
| 1235 | + flowTotalSize: this.fileObj.size, |
1225 | 1236 | flowIdentifier: this.fileObj.uniqueIdentifier, |
1226 | 1237 | flowFilename: this.fileObj.name, |
1227 | 1238 | flowRelativePath: this.fileObj.relativePath, |
|
1264 | 1275 | * @function |
1265 | 1276 | */ |
1266 | 1277 | preprocessFinished: function () { |
| 1278 | + // Compute the endByte after the preprocess function to allow an |
| 1279 | + // implementer of preprocess to set the fileObj size |
| 1280 | + this.endByte = Math.min(this.fileObj.size, (this.offset + 1) * chunkSize); |
| 1281 | + if (this.fileObj.size - this.endByte < chunkSize && |
| 1282 | + !this.flowObj.opts.forceChunkSize) { |
| 1283 | + // The last chunk will be bigger than the chunk size, |
| 1284 | + // but less than 2*chunkSize |
| 1285 | + this.endByte = this.fileObj.size; |
| 1286 | + } |
1267 | 1287 | this.preprocessState = 2; |
1268 | 1288 | this.send(); |
1269 | 1289 | }, |
1270 | 1290 |
|
| 1291 | + /** |
| 1292 | + * Finish read state |
| 1293 | + * @function |
| 1294 | + */ |
| 1295 | + readFinished: function (bytes) { |
| 1296 | + this.readState = 2; |
| 1297 | + this.bytes = bytes; |
| 1298 | + this.send(); |
| 1299 | + }, |
| 1300 | + |
| 1301 | + |
1271 | 1302 | /** |
1272 | 1303 | * Uploads the actual data in a POST call |
1273 | 1304 | * @function |
1274 | 1305 | */ |
1275 | 1306 | send: function () { |
1276 | | - var preprocess = this.flowObj.opts.preprocess; |
| 1307 | + var preprocess = this.flowObj.opts.preprocess, |
| 1308 | + read = this.flowObj.opts.read; |
1277 | 1309 | if (typeof preprocess === 'function') { |
1278 | 1310 | switch (this.preprocessState) { |
1279 | 1311 | case 0: |
|
1284 | 1316 | return; |
1285 | 1317 | } |
1286 | 1318 | } |
| 1319 | + if (typeof this.read === 'function') { |
| 1320 | + switch (this.readState) { |
| 1321 | + case 0: |
| 1322 | + this.readState = 1; |
| 1323 | + read(this, this.startByte, this.endByte, this.fileType); |
| 1324 | + return; |
| 1325 | + case 1: |
| 1326 | + return; |
| 1327 | + } |
| 1328 | + } |
1287 | 1329 | if (this.flowObj.opts.testChunks && !this.tested) { |
1288 | 1330 | this.test(); |
1289 | 1331 | return; |
|
1293 | 1335 | this.total = 0; |
1294 | 1336 | this.pendingRetry = false; |
1295 | 1337 |
|
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 | 1338 | // Set up request and listen for event |
1303 | 1339 | this.xhr = new XMLHttpRequest(); |
1304 | 1340 | this.xhr.upload.addEventListener('progress', this.progressHandler, false); |
1305 | 1341 | this.xhr.addEventListener("load", this.doneHandler, false); |
1306 | 1342 | this.xhr.addEventListener("error", this.doneHandler, false); |
1307 | 1343 |
|
1308 | 1344 | var uploadMethod = evalOpts(this.flowObj.opts.uploadMethod, this.fileObj, this); |
1309 | | - var data = this.prepareXhrRequest(uploadMethod, false, this.flowObj.opts.method, bytes); |
| 1345 | + var data = this.prepareXhrRequest(uploadMethod, false, this.flowObj.opts.method, this.bytes); |
1310 | 1346 | this.xhr.send(data); |
1311 | 1347 | }, |
1312 | 1348 |
|
|
1329 | 1365 | * @returns {string} 'pending', 'uploading', 'success', 'error' |
1330 | 1366 | */ |
1331 | 1367 | status: function (isTest) { |
1332 | | - if (this.pendingRetry || this.preprocessState === 1) { |
| 1368 | + if (this.readState === 1) { |
| 1369 | + return 'reading'; |
| 1370 | + } else if (this.pendingRetry || this.preprocessState === 1) { |
1333 | 1371 | // if pending retry then that's effectively the same as actively uploading, |
1334 | 1372 | // there might just be a slight delay before the retry starts |
1335 | 1373 | return 'uploading'; |
|
0 commit comments