Adding Support For Chunk File Upload
Changes to implement chunk file upload, more information on the README
This commit is contained in:
19
README.md
19
README.md
@@ -67,6 +67,11 @@ Easy to use Angular2 directives for files upload ([demo](http://valor-software.g
|
|||||||
5. `formatDataFunction` - Function to modify the request body. 'DisableMultipart' must be 'true' for this function to be called.
|
5. `formatDataFunction` - Function to modify the request body. 'DisableMultipart' must be 'true' for this function to be called.
|
||||||
6. `formatDataFunctionIsAsync` - Informs if the function sent in 'formatDataFunction' is asynchronous. Defaults to false.
|
6. `formatDataFunctionIsAsync` - Informs if the function sent in 'formatDataFunction' is asynchronous. Defaults to false.
|
||||||
7. `parametersBeforeFiles` - States if additional parameters should be appended before or after the file. Defaults to false.
|
7. `parametersBeforeFiles` - States if additional parameters should be appended before or after the file. Defaults to false.
|
||||||
|
8. `chunkSize` - The Size of each chunk in Bytes, if this parameter is set the file chunk upload functionality will run. Defaults to Null.
|
||||||
|
9. `currentChunkParam` - Parameter Sent with the chunk request, the current chunk number of the file. Defaults to 'current_chunk'.
|
||||||
|
10. `totalChunkParam` - Parameter Sent with the chunk request, the total number of chunks of the file. Defaults to 'total_chunks'.
|
||||||
|
11. `chunkMethod` - After the first chunk, this method is set. Defaults to 'PUT' because is the standard for update.
|
||||||
|
|
||||||
|
|
||||||
### Events
|
### Events
|
||||||
|
|
||||||
@@ -84,6 +89,20 @@ Please follow this guidelines when reporting bugs and feature requests:
|
|||||||
|
|
||||||
Thanks for understanding!
|
Thanks for understanding!
|
||||||
|
|
||||||
|
### Using/Sending Chunk Files Feature
|
||||||
|
|
||||||
|
If you want to send the files chunked you can just set the chunk paramets on the uploader object
|
||||||
|
|
||||||
|
If your chunk request changes the link after the first request you should use this code
|
||||||
|
```
|
||||||
|
this.uploader.onCompleteChunk = (item,response,status,headers)=>{
|
||||||
|
response = JSON.parse(response);
|
||||||
|
if(response['id']){
|
||||||
|
item.url = YOUR_NEW_URL+response['id']+'/';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```txt
|
||||||
|
|
||||||
### License
|
### License
|
||||||
|
|
||||||
The MIT License (see the [LICENSE](https://github.com/valor-software/ng2-file-upload/blob/master/LICENSE) file for the full text)
|
The MIT License (see the [LICENSE](https://github.com/valor-software/ng2-file-upload/blob/master/LICENSE) file for the full text)
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ export class FileItem {
|
|||||||
public index: number = void 0;
|
public index: number = void 0;
|
||||||
public _xhr: XMLHttpRequest;
|
public _xhr: XMLHttpRequest;
|
||||||
public _form: any;
|
public _form: any;
|
||||||
|
public _chunkUploaders: any = [];
|
||||||
|
public _currentChunk: number = 0;
|
||||||
|
public _totalChunks: number = 0;
|
||||||
|
|
||||||
protected uploader: FileUploader;
|
protected uploader: FileUploader;
|
||||||
protected some: File;
|
protected some: File;
|
||||||
@@ -31,6 +34,7 @@ export class FileItem {
|
|||||||
this.options = options;
|
this.options = options;
|
||||||
this.file = new FileLikeObject(some);
|
this.file = new FileLikeObject(some);
|
||||||
this._file = some;
|
this._file = some;
|
||||||
|
|
||||||
if (uploader.options) {
|
if (uploader.options) {
|
||||||
this.method = uploader.options.method || 'POST';
|
this.method = uploader.options.method || 'POST';
|
||||||
this.alias = uploader.options.itemAlias || 'file';
|
this.alias = uploader.options.itemAlias || 'file';
|
||||||
@@ -82,6 +86,9 @@ export class FileItem {
|
|||||||
public onComplete(response: string, status: number, headers: ParsedResponseHeaders): any {
|
public onComplete(response: string, status: number, headers: ParsedResponseHeaders): any {
|
||||||
return { response, status, headers };
|
return { response, status, headers };
|
||||||
}
|
}
|
||||||
|
public onCompleteChunk(response: string, status: number, headers: ParsedResponseHeaders): any {
|
||||||
|
return { response, status, headers };
|
||||||
|
}
|
||||||
|
|
||||||
public _onBeforeUpload(): void {
|
public _onBeforeUpload(): void {
|
||||||
this.isReady = true;
|
this.isReady = true;
|
||||||
@@ -146,6 +153,13 @@ export class FileItem {
|
|||||||
this.remove();
|
this.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public _onCompleteChunk(response: string, status: number, headers: ParsedResponseHeaders): void{
|
||||||
|
this._onCompleteChunkCallnext();
|
||||||
|
this.onCompleteChunk(response, status, headers);
|
||||||
|
}
|
||||||
|
public _onCompleteChunkCallnext(): void{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public _prepareToUploading(): void {
|
public _prepareToUploading(): void {
|
||||||
this.index = this.index || ++this.uploader._nextIndex;
|
this.index = this.index || ++this.uploader._nextIndex;
|
||||||
|
|||||||
@@ -39,6 +39,11 @@ export interface FileUploaderOptions {
|
|||||||
parametersBeforeFiles?: boolean;
|
parametersBeforeFiles?: boolean;
|
||||||
formatDataFunction?: Function;
|
formatDataFunction?: Function;
|
||||||
formatDataFunctionIsAsync?: boolean;
|
formatDataFunctionIsAsync?: boolean;
|
||||||
|
chunkSize?: number;
|
||||||
|
currentChunkParam?: string;
|
||||||
|
totalChunkParam?: string;
|
||||||
|
chunkMethod?: string;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FileUploader {
|
export class FileUploader {
|
||||||
@@ -51,11 +56,18 @@ export class FileUploader {
|
|||||||
public autoUpload: any;
|
public autoUpload: any;
|
||||||
public authTokenHeader: string;
|
public authTokenHeader: string;
|
||||||
public response: EventEmitter<any>;
|
public response: EventEmitter<any>;
|
||||||
|
public chunkSize: number = null;
|
||||||
|
public currentChunkParam: string = "current_chunk";
|
||||||
|
public totalChunkParam: string = "total_chunks";
|
||||||
|
public chunkMethod: string = "PUT";
|
||||||
public options: FileUploaderOptions = {
|
public options: FileUploaderOptions = {
|
||||||
autoUpload: false,
|
autoUpload: false,
|
||||||
isHTML5: true,
|
isHTML5: true,
|
||||||
filters: [],
|
filters: [],
|
||||||
|
chunkSize: null,
|
||||||
|
currentChunkParam: "current_chunk",
|
||||||
|
totalChunkParam: "total_chunks",
|
||||||
|
chunkMethod: "PUT",
|
||||||
removeAfterUpload: false,
|
removeAfterUpload: false,
|
||||||
disableMultipart: false,
|
disableMultipart: false,
|
||||||
formatDataFunction: (item: FileItem) => item._file,
|
formatDataFunction: (item: FileItem) => item._file,
|
||||||
@@ -75,6 +87,10 @@ export class FileUploader {
|
|||||||
this.authToken = this.options.authToken;
|
this.authToken = this.options.authToken;
|
||||||
this.authTokenHeader = this.options.authTokenHeader || 'Authorization';
|
this.authTokenHeader = this.options.authTokenHeader || 'Authorization';
|
||||||
this.autoUpload = this.options.autoUpload;
|
this.autoUpload = this.options.autoUpload;
|
||||||
|
this.chunkSize = this.options.chunkSize;
|
||||||
|
this.currentChunkParam = this.options.currentChunkParam;
|
||||||
|
this.totalChunkParam = this.options.totalChunkParam;
|
||||||
|
this.chunkMethod = this.options.chunkMethod;
|
||||||
this.options.filters.unshift({ name: 'queueLimit', fn: this._queueLimitFilter });
|
this.options.filters.unshift({ name: 'queueLimit', fn: this._queueLimitFilter });
|
||||||
|
|
||||||
if (this.options.maxFileSize) {
|
if (this.options.maxFileSize) {
|
||||||
@@ -245,7 +261,9 @@ export class FileUploader {
|
|||||||
public onCancelItem(item: FileItem, response: string, status: number, headers: ParsedResponseHeaders): any {
|
public onCancelItem(item: FileItem, response: string, status: number, headers: ParsedResponseHeaders): any {
|
||||||
return { item, response, status, headers };
|
return { item, response, status, headers };
|
||||||
}
|
}
|
||||||
|
public onCompleteChunk(item: FileItem, response: string, status: number, headers: ParsedResponseHeaders): any {
|
||||||
|
return { item, response, status, headers };
|
||||||
|
}
|
||||||
public onCompleteItem(item: FileItem, response: string, status: number, headers: ParsedResponseHeaders): any {
|
public onCompleteItem(item: FileItem, response: string, status: number, headers: ParsedResponseHeaders): any {
|
||||||
return { item, response, status, headers };
|
return { item, response, status, headers };
|
||||||
}
|
}
|
||||||
@@ -271,7 +289,12 @@ export class FileUploader {
|
|||||||
item._onError(response, status, headers);
|
item._onError(response, status, headers);
|
||||||
this.onErrorItem(item, response, status, headers);
|
this.onErrorItem(item, response, status, headers);
|
||||||
}
|
}
|
||||||
|
public _onCompleteChunk(item: FileItem, response: string, status: number, headers: ParsedResponseHeaders): void{
|
||||||
|
this.onCompleteChunk(item,response,status,headers);
|
||||||
|
item._onCompleteChunk(response, status, headers);
|
||||||
|
this.progress = this._getTotalProgress();
|
||||||
|
this._render();
|
||||||
|
}
|
||||||
public _onCompleteItem(item: FileItem, response: string, status: number, headers: ParsedResponseHeaders): void {
|
public _onCompleteItem(item: FileItem, response: string, status: number, headers: ParsedResponseHeaders): void {
|
||||||
item._onComplete(response, status, headers);
|
item._onComplete(response, status, headers);
|
||||||
this.onCompleteItem(item, response, status, headers);
|
this.onCompleteItem(item, response, status, headers);
|
||||||
@@ -294,55 +317,37 @@ export class FileUploader {
|
|||||||
return parsedHeaders;
|
return parsedHeaders;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
protected _xhrAppendEvents(xhr: XMLHttpRequest, item: FileItem): XMLHttpRequest{
|
||||||
protected _xhrTransport(item: FileItem): any {
|
|
||||||
let that = this;
|
let that = this;
|
||||||
let xhr = item._xhr = new XMLHttpRequest();
|
|
||||||
let sendable: any;
|
|
||||||
this._onBeforeUploadItem(item);
|
|
||||||
|
|
||||||
if (typeof item._file.size !== 'number') {
|
|
||||||
throw new TypeError('The file specified is no longer valid');
|
|
||||||
}
|
|
||||||
if (!this.options.disableMultipart) {
|
|
||||||
sendable = new FormData();
|
|
||||||
this._onBuildItemForm(item, sendable);
|
|
||||||
|
|
||||||
const appendFile = () => sendable.append(item.alias, item._file, item.file.name);
|
|
||||||
if (!this.options.parametersBeforeFiles) {
|
|
||||||
appendFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
// For AWS, Additional Parameters must come BEFORE Files
|
|
||||||
if (this.options.additionalParameter !== undefined) {
|
|
||||||
Object.keys(this.options.additionalParameter).forEach((key: string) => {
|
|
||||||
let paramVal = this.options.additionalParameter[ key ];
|
|
||||||
// Allow an additional parameter to include the filename
|
|
||||||
if (typeof paramVal === 'string' && paramVal.indexOf('{{file_name}}') >= 0) {
|
|
||||||
paramVal = paramVal.replace('{{file_name}}', item.file.name);
|
|
||||||
}
|
|
||||||
sendable.append(key, paramVal);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.options.parametersBeforeFiles) {
|
|
||||||
appendFile();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sendable = this.options.formatDataFunction(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
xhr.upload.onprogress = (event: any) => {
|
xhr.upload.onprogress = (event: any) => {
|
||||||
let progress = Math.round(event.lengthComputable ? event.loaded * 100 / event.total : 0);
|
let progress = Math.round(event.lengthComputable ? event.loaded * 100 / event.total : 0);
|
||||||
|
if(that.options.chunkSize > 0){
|
||||||
|
progress = Math.round( ((item._currentChunk-1) * 100) / item._totalChunks) + Math.round(progress / item._totalChunks);
|
||||||
|
}
|
||||||
this._onProgressItem(item, progress);
|
this._onProgressItem(item, progress);
|
||||||
};
|
};
|
||||||
xhr.onload = () => {
|
xhr.onload = () => {
|
||||||
|
|
||||||
let headers = this._parseHeaders(xhr.getAllResponseHeaders());
|
let headers = this._parseHeaders(xhr.getAllResponseHeaders());
|
||||||
let response = this._transformResponse(xhr.response, headers);
|
let response = this._transformResponse(xhr.response, headers);
|
||||||
let gist = this._isSuccessCode(xhr.status) ? 'Success' : 'Error';
|
let gist = this._isSuccessCode(xhr.status) ? 'Success' : 'Error';
|
||||||
let method = '_on' + gist + 'Item';
|
let method = '_on' + gist + 'Item';
|
||||||
(this as any)[ method ](item, response, xhr.status, headers);
|
|
||||||
this._onCompleteItem(item, response, xhr.status, headers);
|
|
||||||
|
if(this.options.chunkSize > 0){
|
||||||
|
item._chunkUploaders.pop();
|
||||||
|
if (item._currentChunk >= item._totalChunks) {
|
||||||
|
(this as any)[ method ](item, response, xhr.status, headers);
|
||||||
|
this._onCompleteItem(item, response, xhr.status, headers);
|
||||||
|
}else{
|
||||||
|
|
||||||
|
this._onCompleteChunk(item,response,xhr.status,headers);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
(this as any)[ method ](item, response, xhr.status, headers);
|
||||||
|
this._onCompleteItem(item, response, xhr.status, headers);
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
xhr.onerror = () => {
|
xhr.onerror = () => {
|
||||||
let headers = this._parseHeaders(xhr.getAllResponseHeaders());
|
let headers = this._parseHeaders(xhr.getAllResponseHeaders());
|
||||||
@@ -356,6 +361,7 @@ export class FileUploader {
|
|||||||
this._onCancelItem(item, response, xhr.status, headers);
|
this._onCancelItem(item, response, xhr.status, headers);
|
||||||
this._onCompleteItem(item, response, xhr.status, headers);
|
this._onCompleteItem(item, response, xhr.status, headers);
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.open(item.method, item.url, true);
|
xhr.open(item.method, item.url, true);
|
||||||
xhr.withCredentials = item.withCredentials;
|
xhr.withCredentials = item.withCredentials;
|
||||||
if (this.options.headers) {
|
if (this.options.headers) {
|
||||||
@@ -376,6 +382,98 @@ export class FileUploader {
|
|||||||
that.response.emit(xhr.responseText)
|
that.response.emit(xhr.responseText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return xhr;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _buildMultiPartSendable(item: FileItem,start: number = null, end:number = null): FormData{
|
||||||
|
let sendable: FormData;
|
||||||
|
sendable = new FormData();
|
||||||
|
this._onBuildItemForm(item, sendable);
|
||||||
|
let file: any = item._file;
|
||||||
|
if( start && end ){
|
||||||
|
file = file.slice(start,end);
|
||||||
|
}
|
||||||
|
const appendFile = () => sendable.append(item.alias, file, item.file.name);
|
||||||
|
if (!this.options.parametersBeforeFiles) {
|
||||||
|
appendFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
// For AWS, Additional Parameters must come BEFORE Files
|
||||||
|
if (this.options.additionalParameter !== undefined) {
|
||||||
|
Object.keys(this.options.additionalParameter).forEach((key: string) => {
|
||||||
|
let paramVal = this.options.additionalParameter[ key ];
|
||||||
|
// Allow an additional parameter to include the filename
|
||||||
|
if (typeof paramVal === 'string' && paramVal.indexOf('{{file_name}}') >= 0) {
|
||||||
|
paramVal = paramVal.replace('{{file_name}}', item.file.name);
|
||||||
|
}
|
||||||
|
sendable.append(key, paramVal);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.options.chunkSize > 0 && this.options.totalChunkParam){
|
||||||
|
sendable.append(this.options.totalChunkParam,item._totalChunks.toString() );
|
||||||
|
}
|
||||||
|
if (this.options.chunkSize > 0 && this.options.currentChunkParam){
|
||||||
|
sendable.append(this.options.currentChunkParam,item._currentChunk.toString() );
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.options.parametersBeforeFiles) {
|
||||||
|
appendFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
return sendable;
|
||||||
|
}
|
||||||
|
protected _xhrTransport(item: FileItem): any {
|
||||||
|
let that = this;
|
||||||
|
let xhr = item._xhr = new XMLHttpRequest();
|
||||||
|
let sendable: any;
|
||||||
|
this._onBeforeUploadItem(item);
|
||||||
|
|
||||||
|
if (typeof item._file.size !== 'number') {
|
||||||
|
throw new TypeError('The file specified is no longer valid');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.options.disableMultipart) {
|
||||||
|
/* CHUNCKED FILE UPLOAD */
|
||||||
|
if (this.options.chunkSize > 0){
|
||||||
|
let chunkSize = this.options.chunkSize;
|
||||||
|
let chunkMethod = this.options.chunkMethod;
|
||||||
|
let NUM_CHUNKS = Math.max(Math.ceil(item._file.size / chunkSize), 1);
|
||||||
|
let CUR_CHUNK = 0;
|
||||||
|
let start = 0;
|
||||||
|
let end = chunkSize;
|
||||||
|
|
||||||
|
item._chunkUploaders = [];
|
||||||
|
item._currentChunk = 0;
|
||||||
|
item._totalChunks = NUM_CHUNKS;
|
||||||
|
|
||||||
|
item._onCompleteChunkCallnext = function(): void{
|
||||||
|
item._currentChunk ++;
|
||||||
|
if(item._currentChunk > 1){
|
||||||
|
item.method = chunkMethod;
|
||||||
|
}
|
||||||
|
let sendable = this.uploader._buildMultiPartSendable(item,start,end);
|
||||||
|
let xhr = new XMLHttpRequest();
|
||||||
|
xhr = this.uploader._xhrAppendEvents(xhr,item);
|
||||||
|
|
||||||
|
item._chunkUploaders.push(xhr);
|
||||||
|
xhr.send(sendable);
|
||||||
|
|
||||||
|
start = end;
|
||||||
|
end = start + chunkSize;
|
||||||
|
}
|
||||||
|
item._onCompleteChunkCallnext();
|
||||||
|
this._render();
|
||||||
|
return;
|
||||||
|
}else{
|
||||||
|
sendable = this._buildMultiPartSendable(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
sendable = this.options.formatDataFunction(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append Evenets
|
||||||
|
xhr = this._xhrAppendEvents(xhr,item);
|
||||||
if (this.options.formatDataFunctionIsAsync) {
|
if (this.options.formatDataFunctionIsAsync) {
|
||||||
sendable.then(
|
sendable.then(
|
||||||
(result: any) => xhr.send(JSON.stringify(result))
|
(result: any) => xhr.send(JSON.stringify(result))
|
||||||
|
|||||||
Reference in New Issue
Block a user