Skip to content

Commit

Permalink
fix: use chunk upload only for services that support it
Browse files Browse the repository at this point in the history
  • Loading branch information
eransakal committed Oct 29, 2017
1 parent c3cfd95 commit 43dd5e2
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 75 deletions.
92 changes: 66 additions & 26 deletions src/kaltura-clients/kaltura-http-client-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export interface KalturaHttpClientBaseConfiguration extends KalturaClientBaseCon
}

interface ChunkData {
enabled: boolean;
resume: boolean;
resumeAt: number;
finalChunk: boolean;
Expand Down Expand Up @@ -36,18 +37,43 @@ export abstract class KalturaHttpClientBase extends KalturaClientBase {
};
}

protected _chunkUploadSupported(request: KalturaUploadRequest<any>): boolean
{
// SUPPORTED BY BROWSER?
// Check if these features are support by the browser:
// - File object type
// - Blob object type
// - FileList object type
// - slicing files
const supportedByBrowser = (
(typeof(File) !== 'undefined')
&&
(typeof(Blob) !== 'undefined')
&&
(typeof(FileList) !== 'undefined')
&&
(!!(<any>Blob.prototype).webkitSlice || !!(<any>Blob.prototype).mozSlice || !!(<any>Blob.prototype).slice || false)
);
const supportedByRequest = request.supportChunkUpload();

return supportedByBrowser && supportedByRequest;
}

protected _transmitFileUploadRequest(request : KalturaUploadRequest<any>): CancelableAction {
return new CancelableAction((resolve, reject) => {
const uploadedFileSize = !isNaN(request.uploadedFileSize) && isFinite(request.uploadedFileSize) && request.uploadedFileSize > 0 ? request.uploadedFileSize : 0;
const data: ChunkData = { resume: !!uploadedFileSize, finalChunk: false, resumeAt: uploadedFileSize };
const data: ChunkData = { enabled: this._chunkUploadSupported(request),
resume: !!uploadedFileSize,
finalChunk: false,
resumeAt: uploadedFileSize };

const handleChunkUploadError = reason => {
chunkUploadRequest = null;
reject(reason);
};

const handleChunkUploadSuccess = response => {
if (data.finalChunk) {
if (!data.enabled || data.finalChunk) {
chunkUploadRequest = null;
resolve(response);
} else {
Expand Down Expand Up @@ -84,29 +110,36 @@ export abstract class KalturaHttpClientBase extends KalturaClientBase {
delete parameters.service;
delete parameters.action;

let actualChunkSize = 5e6; // default
if (this.chunkFileSize) {
if (this.chunkFileSize < 1e6) {
console.warn(`user requested for invalid upload chunk size '${this.chunkFileSize}'. minimal value 1Mb. using minimal value 1Mb instead`);
actualChunkSize = 1e6;
} else {
actualChunkSize = this.chunkFileSize;
console.log(`using user requetsed chunk size '${this.chunkFileSize}'`);
}
} else {
console.log(`user requested for invalid (empty) upload chunk size. minimal value 1Mb. using default value 5Mb instead`);
}
let fileStart = 0;
let uploadSize: number = null;

if (uploadChunkData.enabled) {
uploadSize = 5e6; // default
if (this.chunkFileSize) {
if (this.chunkFileSize < 1e6) {
console.warn(`user requested for invalid upload chunk size '${this.chunkFileSize}'. minimal value 1Mb. using minimal value 1Mb instead`);
uploadSize = 1e6;
} else {
uploadSize = this.chunkFileSize;
console.log(`using user requetsed chunk size '${this.chunkFileSize}'`);
}
} else {
console.log(`using default chunk size 5Mb`);
}

uploadChunkData.finalChunk = (file.size - uploadChunkData.resumeAt) <= actualChunkSize;
uploadChunkData.finalChunk = (file.size - uploadChunkData.resumeAt) <= uploadSize;

const start = uploadChunkData.resumeAt;
const end = uploadChunkData.finalChunk ? file.size : start + actualChunkSize;
fileStart = uploadChunkData.resumeAt;
const fileEnd = uploadChunkData.finalChunk ? file.size : fileStart + uploadSize;

data.set(request.getFilePropertyName(), file.slice(start, end, file.type), file.name);
data.set(request.getFilePropertyName(), file.slice(fileStart, fileEnd, file.type), file.name);

parameters.resume = uploadChunkData.resume;
parameters.resumeAt = uploadChunkData.resumeAt;
parameters.finalChunk = uploadChunkData.finalChunk;
parameters.resume = uploadChunkData.resume;
parameters.resumeAt = uploadChunkData.resumeAt;
parameters.finalChunk = uploadChunkData.finalChunk;
}else {
console.log(`chunk upload not supported by browser or by request. Uploading the file as-is`);
}

// build endpoint
const querystring = this._buildQuerystring(parameters);
Expand All @@ -131,9 +164,13 @@ export abstract class KalturaHttpClientBase extends KalturaClientBase {
if (resp instanceof Error) {
reject(resp);
} else {
if (!uploadChunkData.finalChunk) {
uploadChunkData.resumeAt = Number(resp.uploadedFileSize);
uploadChunkData.resume = true;
if (uploadChunkData.enabled) {
if (typeof resp.uploadedFileSize === "undefined" || resp.uploadedFileSize === null) {
resp = new Error(`uploaded chunk of file failed, expected response with property 'uploadedFileSize'`);
} else if (!uploadChunkData.finalChunk) {
uploadChunkData.resumeAt = Number(resp.uploadedFileSize);
uploadChunkData.resume = true;
}
}

resolve(resp);
Expand All @@ -145,8 +182,11 @@ export abstract class KalturaHttpClientBase extends KalturaClientBase {
if (progressCallback) {
xhr.upload.addEventListener("progress", e => {
if (e.lengthComputable) {
const chunkSize = uploadChunkData.finalChunk ? file.size - start : actualChunkSize;
progressCallback.apply(request, [Math.floor(e.loaded / e.total * chunkSize) + start, file.size]);
let chunkSize = uploadSize;
if (uploadChunkData.enabled) {
chunkSize = uploadChunkData.finalChunk ? file.size - fileStart : uploadSize;
}
progressCallback.apply(request, [Math.floor(e.loaded / e.total * chunkSize) + fileStart, file.size]);
} else {
// Unable to compute progress information since the total size is unknown
}
Expand Down
6 changes: 6 additions & 0 deletions src/kaltura-object-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ export abstract class KalturaObjectBase{
return { properties : {}};
}

public hasMetadataProperty(propertyName: string): boolean
{
return !!this._getMetadata().properties[propertyName];
}

toRequestObject() : {} {
const metadata = this._getMetadata();
Expand Down Expand Up @@ -108,6 +112,8 @@ export abstract class KalturaObjectBase{
return result;
}



protected _parseResponseProperty(propertyName : string, property : KalturaObjectPropertyMetadata, source : any) : any {

let result;
Expand Down
2 changes: 1 addition & 1 deletion src/kaltura-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export abstract class KalturaRequest<T> extends KalturaRequestBase {
protected callback: (response: KalturaResponse<T>) => void;
private responseType : string;
private responseSubType : string;
private _responseConstructor : { new() : KalturaObjectBase}; // NOTICE: this property is not used directly. It is here to force import of that type for bundling issues.
protected _responseConstructor : { new() : KalturaObjectBase}; // NOTICE: this property is not used directly. It is here to force import of that type for bundling issues.

constructor(data : KalturaRequestBaseArgs, {responseType, responseSubType, responseConstructor} : {responseType : string, responseSubType? : string, responseConstructor : { new() : KalturaObjectBase} } ) {
super(data);
Expand Down
106 changes: 58 additions & 48 deletions src/kaltura-upload-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,58 +8,68 @@ export interface KalturaUploadRequestArgs extends KalturaRequestArgs {
}

export class KalturaUploadRequest<T> extends KalturaRequest<T> {
private _progressCallback: ProgressCallback;
public uploadedFileSize: number = 0;

constructor(data: KalturaUploadRequestArgs, { responseType, responseSubType, responseConstructor }: { responseType: string, responseSubType?: string, responseConstructor: { new(): KalturaObjectBase } }) {
super(data, { responseType, responseSubType, responseConstructor });
this.uploadedFileSize = data.uploadedFileSize;
}

setProgress(callback: ProgressCallback): this {
this._progressCallback = callback;
return this;
}

public _getProgressCallback(): ProgressCallback {
return this._progressCallback;
}

public getFilePropertyName(): string {
const metadataProperties = this._getMetadata().properties;
return Object.keys(metadataProperties).find(propertyName => metadataProperties[propertyName].type === "f");
}

public getFileData(): File {
const filePropertyName = this.getFilePropertyName();
return filePropertyName ? this[filePropertyName] : null;
}

public getFormData(): FormData {
let result = null;
const filePropertyName = this.getFilePropertyName();

if (filePropertyName) {
const file = this[filePropertyName];

if (file) {
result = new FormData();
result.append("fileName", file.name);
result.append(filePropertyName, file);
}
private _progressCallback: ProgressCallback;
public uploadedFileSize: number = 0;

constructor(data: KalturaUploadRequestArgs, {responseType, responseSubType, responseConstructor}: { responseType: string, responseSubType?: string, responseConstructor: { new(): KalturaObjectBase } }) {
super(data, {responseType, responseSubType, responseConstructor});
this.uploadedFileSize = data.uploadedFileSize;
}

setProgress(callback: ProgressCallback): this {
this._progressCallback = callback;
return this;
}

public _getProgressCallback(): ProgressCallback {
return this._progressCallback;
}

return result;
}
public supportChunkUpload(): boolean {
// chunk upload currently assume support according to request/reseponse properties. Should get this information from the client-generator directly.
const {properties} = this._getMetadata();
const responseSupportChunk = this._responseConstructor ? (new this._responseConstructor()).hasMetadataProperty("uploadedFileSize") : false;
return responseSupportChunk
&& !!properties["resume"]
&& !!properties["resumeAt"]
&& !!properties["finalChunk"];
}

public toRequestObject(): {} {
const result = super.toRequestObject();
const filePropertyName = this.getFilePropertyName();
public getFilePropertyName(): string {
const metadataProperties = this._getMetadata().properties;
return Object.keys(metadataProperties).find(propertyName => metadataProperties[propertyName].type === "f");
}

if (filePropertyName) {
delete result[filePropertyName];
public getFileData(): File {
const filePropertyName = this.getFilePropertyName();
return filePropertyName ? this[filePropertyName] : null;
}

return result;
}
public getFormData(): FormData {
let result = null;
const filePropertyName = this.getFilePropertyName();

if (filePropertyName) {
const file = this[filePropertyName];

if (file) {
result = new FormData();
result.append("fileName", file.name);
result.append(filePropertyName, file);
}
}

return result;
}

public toRequestObject(): {} {
const result = super.toRequestObject();
const filePropertyName = this.getFilePropertyName();

if (filePropertyName) {
delete result[filePropertyName];
}

return result;
}
}

0 comments on commit 43dd5e2

Please sign in to comment.