Skip to content

Commit

Permalink
feat: add update method and event (#401)
Browse files Browse the repository at this point in the history
  • Loading branch information
kukhariev committed Oct 21, 2022
1 parent 779ae98 commit eb8c526
Show file tree
Hide file tree
Showing 8 changed files with 78 additions and 21 deletions.
1 change: 1 addition & 0 deletions src/app/directive-way/directive-way.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
<span>
<button (click)="upload()">Upload All</button>
<button (click)="pause()">Pause All</button>
<button (click)="update()">Update All</button>
<button (click)="cancel()">Cancel All</button>
</span>
</div>
Expand Down
4 changes: 4 additions & 0 deletions src/app/directive-way/directive-way.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ export class DirectiveWayComponent {
this.control = { action: 'upload', uploadId };
}

update(uploadId?: string): void {
this.control = { action: 'update', uploadId, metadata: { updated: Date.now() } };
}

onStateChanged(state: UploadState): void {
this.state = state;
const target = this.uploads.find(item => item.uploadId === state.uploadId);
Expand Down
1 change: 1 addition & 0 deletions src/app/on-push/on-push.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<span>
<button (click)="uploadAll()">Upload All</button>
<button (click)="pauseAll()">Pause All</button>
<button (click)="updateAll()">Update All</button>
<button (click)="cancelAll()">Cancel All</button>
</span>
</div>
Expand Down
4 changes: 4 additions & 0 deletions src/app/on-push/on-push.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,8 @@ export class OnPushComponent implements OnDestroy {
uploadAll(): void {
this.uploadService.control({ action: 'upload' });
}

updateAll(): void {
this.uploadService.control({ action: 'update', metadata: { updated: Date.now() } });
}
}
5 changes: 3 additions & 2 deletions src/uploadx/lib/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,10 @@ export type UploadStatus =
| 'error'
| 'cancelled'
| 'paused'
| 'retry';
| 'retry'
| 'updated';

export type UploadAction = 'upload' | 'cancel' | 'pause';
export type UploadAction = 'upload' | 'cancel' | 'pause' | 'update';

export interface UploadState {
/** Uploaded file */
Expand Down
31 changes: 25 additions & 6 deletions src/uploadx/lib/uploader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import { isNumber, unfunc } from './utils';
const actionToStatusMap: { [K in UploadAction]: UploadStatus } = {
pause: 'paused',
upload: 'queue',
cancel: 'cancelled'
cancel: 'cancelled',
update: 'updated'
};

/**
Expand Down Expand Up @@ -100,15 +101,19 @@ export abstract class Uploader implements UploadState {
}

set status(s: UploadStatus) {
if (this._status === s) return;
if (s !== 'updated' && s !== 'cancelled') {
if (this._status === s) return;
if (this._status === 'complete') return;
}
if (this._status === 'cancelled') return;
if (this._status === 'complete' && s !== 'cancelled') return;
if (this._status === 'uploading' && s === 'queue') return;
if (this._status === 'retry') this.retry.cancel();
this._status = s;
if (s === 'paused') this.abort();
if (s === 'cancelled' || s === 'complete' || s === 'error') this.cleanup();
s === 'cancelled' ? this.cancel() : this.stateChange(this);
if (s === 'cancelled') this.cancelAndSendState();
else if (s === 'updated') this.updateAndSendState();
else this.stateChange(this);
}

/**
Expand All @@ -127,7 +132,7 @@ export abstract class Uploader implements UploadState {
*/
async upload(): Promise<void> {
this._status = 'uploading';
while (this.status === 'uploading' || this.status === 'retry') {
while (this.status === 'uploading' || this.status === 'retry' || this.status === 'updated') {
this.status = 'uploading';
try {
this._token ||= await this.updateToken();
Expand Down Expand Up @@ -241,6 +246,13 @@ export abstract class Uploader implements UploadState {
*/
protected abstract getOffset(): Promise<number | undefined>;

/**
* Updating the metadata of the upload
*/
protected update<T = { metadata?: Metadata }>(_data: T): Promise<string> {
return Promise.reject('Not implemented');
}

protected abort(): void {
this.offset = undefined;
this.abortController.abort();
Expand All @@ -252,7 +264,6 @@ export abstract class Uploader implements UploadState {
if (this.url) {
await this.request({ method: 'DELETE' }).catch(() => {});
}
this.stateChange(this);
}

/**
Expand Down Expand Up @@ -283,6 +294,14 @@ export abstract class Uploader implements UploadState {
return Number(this.getValueFromResponse('retry-after')) * 1000;
}

private cancelAndSendState() {
this.cancel().then(() => this.stateChange(this), console.error);
}

private updateAndSendState(): void {
this.update({ metadata: this.metadata }).then(() => this.stateChange(this), console.error);
}

private cleanup = () => {
store.delete(this._url);
store.delete(this.uploadId);
Expand Down
45 changes: 32 additions & 13 deletions src/uploadx/lib/uploaderx.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,36 @@ const fileWithType = new File(['123456'], 'filename.txt', { type: 'text/plain' }
const fileWithoutType = new File([''], 'filename');
describe('UploaderX', () => {
describe('getFileUrl', () => {
let uploader: UploaderX;
let uploaderX: UploaderX;
let req: jasmine.Spy;
let getValueFromResponse: jasmine.Spy;
it('should set headers', async () => {
uploader = new UploaderX(fileWithType, {}, () => {}, {} as Ajax);
req = spyOn<any>(uploader, 'request').and.callFake(({ headers }: any) => {
uploaderX = new UploaderX(fileWithType, {}, () => {}, {} as Ajax);
req = spyOn<any>(uploaderX, 'request').and.callFake(({ headers }: any) => {
expect(headers['X-Upload-Content-Type']).toEqual('text/plain');
expect(headers['X-Upload-Content-Length']).toEqual(6);
});
getValueFromResponse = spyOn<any>(uploader, 'getValueFromResponse').and.returnValue(
getValueFromResponse = spyOn<any>(uploaderX, 'getValueFromResponse').and.returnValue(
'/12345678'
);
expect(uploader.name).toEqual('filename.txt');
expect(uploader.size).toEqual(6);
expect(await uploader.getFileUrl()).toEqual('/12345678');
expect(uploaderX.name).toEqual('filename.txt');
expect(uploaderX.size).toEqual(6);
expect(await uploaderX.getFileUrl()).toEqual('/12345678');
expect(req).toHaveBeenCalled();
expect(getValueFromResponse).toHaveBeenCalled();
});
it('should set default type header', async () => {
uploader = new UploaderX(fileWithoutType, {}, () => {}, {} as Ajax);
req = spyOn<any>(uploader, 'request').and.callFake(({ headers }: any) => {
uploaderX = new UploaderX(fileWithoutType, {}, () => {}, {} as Ajax);
req = spyOn<any>(uploaderX, 'request').and.callFake(({ headers }: any) => {
expect(headers['X-Upload-Content-Type']).toEqual('application/octet-stream');
expect(headers['X-Upload-Content-Length']).toEqual(0);
});
getValueFromResponse = spyOn<any>(uploader, 'getValueFromResponse').and.returnValue(
getValueFromResponse = spyOn<any>(uploaderX, 'getValueFromResponse').and.returnValue(
'/12345678'
);
expect(uploader.name).toEqual('filename');
expect(uploader.size).toEqual(0);
await uploader.getFileUrl();
expect(uploaderX.name).toEqual('filename');
expect(uploaderX.size).toEqual(0);
await uploaderX.getFileUrl();
expect(req).toHaveBeenCalled();
expect(getValueFromResponse).toHaveBeenCalled();
});
Expand Down Expand Up @@ -83,6 +83,25 @@ describe('UploaderX', () => {
});
});

describe('update', () => {
let uploaderX: UploaderX;
let req: jasmine.Spy;
let getValueFromResponse: jasmine.Spy;
it('should send updated data and return location', async () => {
uploaderX = new UploaderX(fileWithType, {}, () => {}, {} as Ajax);
req = spyOn<any>(uploaderX, 'request').and.callFake(({ body, method }: any) => {
expect(body).toEqual('{"metadata":{"updated":true}}');
expect(method).toEqual('PATCH');
});
getValueFromResponse = spyOn<any>(uploaderX, 'getValueFromResponse').and.returnValue(
'/12345678'
);
expect(await uploaderX.update({ metadata: { updated: true } })).toEqual('/12345678');
expect(req).toHaveBeenCalled();
expect(getValueFromResponse).toHaveBeenCalled();
});
});

describe('getRangeEnd', () => {
it('invalid ranges', () => {
expect(getRangeEnd(undefined)).toEqual(-1);
Expand Down
8 changes: 8 additions & 0 deletions src/uploadx/lib/uploaderx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ export class UploaderX extends Uploader {
return this.responseStatus > 201 ? this.getOffsetFromResponse() : this.size;
}

async update<T>(data: T): Promise<string> {
const body = JSON.stringify(data);
const headers = { 'Content-Type': 'application/json; charset=utf-8' };
await this.request({ method: 'PATCH', body, headers });
const location = this.getValueFromResponse('location') || this.url;
return resolveUrl(location, this.endpoint);
}

protected getOffsetFromResponse(): number | undefined {
const range = this.getValueFromResponse('Range');
return range ? getRangeEnd(range) + 1 : undefined;
Expand Down

0 comments on commit eb8c526

Please sign in to comment.