quickshare/client/libs/api_upload.js
hekk 61a1c93f0f !1 Merge back to master
Merge pull request !1 from dev branch
2018-05-27 21:32:55 +08:00

202 lines
6.4 KiB
JavaScript

import axios from "axios";
import { config } from "../config";
import { makePostBody } from "./utils";
const wait = 5000; // TODO: should tune according to backend
const retryMax = 100000;
const maxUploadLen = 20 * 1024 * 1024;
// TODO: add to react-intl
const msgUploadFailed = "Fail to upload, upload is stopped.";
const msgUploadFailedAndRetry = "Fail to upload, retrying...";
const msgFileExists = "File exists.";
const msgTooBigChunk = "Too big chunk.";
const msgFileNotFound = "File not found, upload stopped.";
function randomWait() {
return Math.random() * wait;
}
function isKnownErr(res) {
return res != null && res.Code != null && res.Msg != null;
}
export class FileUploader {
constructor(onStart, onProgress, onFinish, onError) {
this.onStart = onStart;
this.onProgress = onProgress;
this.onFinish = onFinish;
this.onError = onError;
this.retry = retryMax;
this.reader = new FileReader();
this.uploadFile = file => {
return this.startUpload(file);
};
this.startUpload = file => {
return axios
.post(
`${config.serverAddr}/startupload`,
makePostBody({
fname: file.name
})
)
.then(response => {
if (
response.data.ShareId == null ||
response.data.Start === null ||
response.data.Length === null
) {
throw response;
} else {
this.onStart(response.data.ShareId, file.name);
return this.upload(
{
shareId: response.data.ShareId,
start: response.data.Start,
length: response.data.Length
},
file
);
}
})
.catch(response => {
// TODO: this is not good because error may not be response
if (isKnownErr(response.data) && response.data.Code === 429) {
setTimeout(this.startUpload, randomWait(), file);
} else if (isKnownErr(response.data) && response.data.Code === 412) {
this.onError(msgFileExists);
} else if (isKnownErr(response.data) && response.data.Code === 404) {
this.onError(msgFileNotFound);
} else if (this.retry > 0) {
this.retry--;
this.onError(msgUploadFailedAndRetry);
console.trace(response);
setTimeout(this.startUpload, randomWait(), file);
} else {
this.onError(msgUploadFailed);
console.trace(response);
}
});
};
this.prepareReader = (shareInfo, end, resolve, reject) => {
this.reader.onerror = err => {
reject(err);
};
this.reader.onloadend = event => {
const formData = new FormData();
formData.append("shareid", shareInfo.shareId);
formData.append("start", shareInfo.start);
formData.append("len", end - shareInfo.start);
formData.append("chunk", new Blob([event.target.result]));
const url = `${config.serverAddr}/upload`;
const headers = {
"Content-Type": "multipart/form-data"
};
try {
axios
.post(url, formData, { headers })
.then(response => resolve(response))
.catch(err => {
reject(err);
});
} catch (err) {
reject(err);
}
};
};
this.upload = (shareInfo, file) => {
const uploaded = shareInfo.start + shareInfo.length;
const end = uploaded < file.size ? uploaded : file.size;
return new Promise((resolve, reject) => {
if (
end == null ||
shareInfo.start == null ||
end - shareInfo.start >= maxUploadLen
) {
throw new Error(msgTooBigChunk);
}
const chunk = file.slice(shareInfo.start, end);
this.prepareReader(shareInfo, end, resolve, reject);
this.reader.readAsArrayBuffer(chunk);
})
.then(response => {
if (
response.data.ShareId == null ||
response.data.Start == null ||
response.data.Length == null ||
response.data.Start !== end
) {
throw response;
} else {
if (end < file.size) {
this.onProgress(shareInfo.shareId, end / file.size);
return this.upload(
{
shareId: shareInfo.shareId,
start: shareInfo.start + shareInfo.length,
length: shareInfo.length
},
file
);
} else {
return this.finishUpload(shareInfo);
}
}
})
.catch(response => {
// possible error: response.data.Start == null || response.data.Start !== end
if (isKnownErr(response.data) && response.data.Code === 429) {
setTimeout(this.upload, randomWait(), shareInfo, file);
} else if (isKnownErr(response.data) && response.data.Code === 404) {
this.onError(msgFileNotFound);
} else if (this.retry > 0) {
this.retry--;
setTimeout(this.upload, randomWait(), shareInfo, file);
this.onError(msgUploadFailedAndRetry);
console.trace(response);
} else {
this.onError(msgUploadFailed);
console.trace(response);
}
});
};
this.finishUpload = shareInfo => {
return axios
.post(`${config.serverAddr}/finishupload?shareid=${shareInfo.shareId}`)
.then(response => {
// TODO: should check Code instead of Url
if (response.data.ShareId != null && response.data.Start == null) {
this.onFinish();
return response.data.ShareId;
} else {
throw response;
}
})
.catch(response => {
if (isKnownErr(response.data) && response.data.Code === 429) {
setTimeout(this.finishUpload, randomWait(), shareInfo);
} else if (isKnownErr(response.data) && response.data.Code === 404) {
this.onError(msgFileNotFound);
} else if (this.retry > 0) {
this.retry--;
setTimeout(this.finishUpload, randomWait(), shareInfo);
this.onError(msgUploadFailedAndRetry);
console.trace(response);
} else {
this.onError(msgUploadFailed);
console.trace(response);
}
});
};
}
}