Skip to content

Commit 2108ea8

Browse files
committed
Merge pull request #111 from evilaliv3/feature/readHook
feat: read hook
2 parents 08bad3e + 88c9aa7 commit 2108ea8

3 files changed

Lines changed: 86 additions & 35 deletions

File tree

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,10 @@ function, it will be passed a FlowFile, a FlowChunk and isTest boolean (Default:
119119
* `allowDuplicateUploads ` Once a file is uploaded, allow reupload of the same file. By default, if a file is already uploaded, it will be skipped unless the file is removed from the existing Flow object. (Default: `false`)
120120
* `prioritizeFirstAndLastChunk` Prioritize first and last chunks of all files. This can be handy if you can determine if a file is valid for your service from only the first or last chunk. For example, photo or video meta data is usually located in the first part of a file, making it easy to test support from only the first chunk. (Default: `false`)
121121
* `testChunks` Make a GET request to the server for each chunks to see if it already exists. If implemented on the server-side, this will allow for upload resumes even after a browser crash or even a computer restart. (Default: `true`)
122-
* `preprocess` Optional function to process each chunk before testing & sending. Function is passed the chunk as parameter, and should call the `preprocessFinished` method on the chunk when finished. (Default: `null`)
123-
* `generateUniqueIdentifier` Override the function that generates unique identifiers for each file. (Default: `null`)
122+
* `preprocess` Optional function to process each chunk before testing & sending. To the function it will be passed the chunk as parameter, and should call the `preprocessFinished` method on the chunk when finished. (Default: `null`)
123+
* `initFileFn` Optional function to initialize the fileObject. To the function it will be passed a FlowFile and a FlowChunk arguments.
124+
* `readFileFn` Optional function wrapping reading operation from the original file. To the function it will be passed the FlowFile, the startByte and endByte, the fileType and the FlowChunk.
125+
* `generateUniqueIdentifier` Override the function that generates unique identifiers for each file. (Default: `null`)
124126
* `maxChunkRetries` The maximum number of retries for a chunk before the upload is failed. Valid values are any positive integer and `undefined` for no limit. (Default: `0`)
125127
* `chunkRetryInterval` The number of milliseconds to wait before retrying a chunk on a non-permanent error. Valid values are any positive integer and `undefined` for immediate retry. (Default: `undefined`)
126128
* `progressCallbacksInterval` The time interval in milliseconds between progress reports. Set it

src/flow.js

Lines changed: 80 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
* @param {number} [opts.chunkRetryInterval]
3030
* @param {Array.<number>} [opts.permanentErrors]
3131
* @param {Array.<number>} [opts.successStatuses]
32+
* @param {Function} [opts.initFileFn]
33+
* @param {Function} [opts.readFileFn]
3234
* @param {Function} [opts.generateUniqueIdentifier]
3335
* @constructor
3436
*/
@@ -91,9 +93,11 @@
9193
chunkRetryInterval: null,
9294
permanentErrors: [404, 415, 500, 501],
9395
successStatuses: [200, 201, 202],
94-
onDropStopPropagation: false
96+
onDropStopPropagation: false,
97+
initFileFn: null,
98+
readFileFn: webAPIFileRead
9599
};
96-
100+
97101
/**
98102
* Current options
99103
* @type {Object}
@@ -144,6 +148,7 @@
144148
* @type {Object}
145149
*/
146150
this.opts = Flow.extend({}, this.defaults, opts || {});
151+
147152
}
148153

149154
Flow.prototype = {
@@ -697,6 +702,12 @@
697702
* @type {Flow}
698703
*/
699704
this.flowObj = flowObj;
705+
706+
/**
707+
* Used to store the bytes read
708+
* @type {Blob|string}
709+
*/
710+
this.bytes = null;
700711

701712
/**
702713
* Reference to file
@@ -906,13 +917,17 @@
906917
* @function
907918
*/
908919
bootstrap: function () {
920+
if (typeof this.flowObj.opts.initFileFn === "function") {
921+
this.flowObj.opts.initFileFn(this);
922+
}
923+
909924
this.abort(true);
910925
this.error = false;
911926
// Rebuild stack of chunks from file
912927
this._prevProgress = 0;
913928
var round = this.flowObj.opts.forceChunkSize ? Math.ceil : Math.floor;
914929
var chunks = Math.max(
915-
round(this.file.size / this.flowObj.opts.chunkSize), 1
930+
round(this.size / this.flowObj.opts.chunkSize), 1
916931
);
917932
for (var offset = 0; offset < chunks; offset++) {
918933
this.chunks.push(
@@ -971,7 +986,7 @@
971986
var outstanding = false;
972987
each(this.chunks, function (chunk) {
973988
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) {
975990
outstanding = true;
976991
return false;
977992
}
@@ -1031,11 +1046,24 @@
10311046
}
10321047
};
10331048

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';
10341057

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';
10351064

1036-
1037-
1038-
1065+
chunk.readFinished(fileObj.file[function_name](startByte, endByte, fileType));
1066+
}
10391067

10401068

10411069
/**
@@ -1060,12 +1088,6 @@
10601088
*/
10611089
this.fileObj = fileObj;
10621090

1063-
/**
1064-
* File size
1065-
* @type {number}
1066-
*/
1067-
this.fileObjSize = fileObj.size;
1068-
10691091
/**
10701092
* File offset
10711093
* @type {number}
@@ -1096,6 +1118,13 @@
10961118
*/
10971119
this.preprocessState = 0;
10981120

1121+
/**
1122+
* Read state
1123+
* @type {number} 0 = not read, 1 = reading, 2 = finished
1124+
*/
1125+
this.readState = 0;
1126+
1127+
10991128
/**
11001129
* Bytes transferred from total request size
11011130
* @type {number}
@@ -1112,33 +1141,26 @@
11121141
* Size of a chunk
11131142
* @type {number}
11141143
*/
1115-
var chunkSize = this.flowObj.opts.chunkSize;
1144+
this.chunkSize = this.flowObj.opts.chunkSize;
11161145

11171146
/**
11181147
* Chunk start byte in a file
11191148
* @type {number}
11201149
*/
1121-
this.startByte = this.offset * chunkSize;
1150+
this.startByte = this.offset * this.chunkSize;
11221151

11231152
/**
11241153
* Chunk end byte in a file
11251154
* @type {number}
11261155
*/
1127-
this.endByte = Math.min(this.fileObjSize, (this.offset + 1) * chunkSize);
1156+
this.endByte = Math.min(this.fileObj.size, (this.offset + 1) * this.chunkSize);
11281157

11291158
/**
11301159
* XMLHttpRequest
11311160
* @type {XMLHttpRequest}
11321161
*/
11331162
this.xhr = null;
11341163

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-
11421164
var $ = this;
11431165

11441166

@@ -1192,6 +1214,7 @@
11921214
this.doneHandler = function(event) {
11931215
var status = $.status();
11941216
if (status === 'success' || status === 'error') {
1217+
delete this.data;
11951218
$.event(status, $.message());
11961219
$.flowObj.uploadNextChunk();
11971220
} else {
@@ -1221,7 +1244,7 @@
12211244
flowChunkNumber: this.offset + 1,
12221245
flowChunkSize: this.flowObj.opts.chunkSize,
12231246
flowCurrentChunkSize: this.endByte - this.startByte,
1224-
flowTotalSize: this.fileObjSize,
1247+
flowTotalSize: this.fileObj.size,
12251248
flowIdentifier: this.fileObj.uniqueIdentifier,
12261249
flowFilename: this.fileObj.name,
12271250
flowRelativePath: this.fileObj.relativePath,
@@ -1264,16 +1287,37 @@
12641287
* @function
12651288
*/
12661289
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+
}
12671299
this.preprocessState = 2;
12681300
this.send();
12691301
},
12701302

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+
12711314
/**
12721315
* Uploads the actual data in a POST call
12731316
* @function
12741317
*/
12751318
send: function () {
12761319
var preprocess = this.flowObj.opts.preprocess;
1320+
var read = this.flowObj.opts.readFileFn;
12771321
if (typeof preprocess === 'function') {
12781322
switch (this.preprocessState) {
12791323
case 0:
@@ -1284,6 +1328,14 @@
12841328
return;
12851329
}
12861330
}
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+
}
12871339
if (this.flowObj.opts.testChunks && !this.tested) {
12881340
this.test();
12891341
return;
@@ -1293,20 +1345,14 @@
12931345
this.total = 0;
12941346
this.pendingRetry = false;
12951347

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-
13021348
// Set up request and listen for event
13031349
this.xhr = new XMLHttpRequest();
13041350
this.xhr.upload.addEventListener('progress', this.progressHandler, false);
13051351
this.xhr.addEventListener("load", this.doneHandler, false);
13061352
this.xhr.addEventListener("error", this.doneHandler, false);
13071353

13081354
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);
13101356
this.xhr.send(data);
13111357
},
13121358

@@ -1329,7 +1375,9 @@
13291375
* @returns {string} 'pending', 'uploading', 'success', 'error'
13301376
*/
13311377
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) {
13331381
// if pending retry then that's effectively the same as actively uploading,
13341382
// there might just be a slight delay before the retry starts
13351383
return 'uploading';

test/fileSpec.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,5 @@ describe('FlowFile functions', function() {
3434
file.name = '.dwq.dq.wd.qdw.E';
3535
expect(file.getExtension()).toBe('e');
3636
});
37-
});
37+
38+
});

0 commit comments

Comments
 (0)