fix(fe/worker): refactor fe uploading functions to improve robustness
This commit is contained in:
parent
aca1ae6787
commit
ff5e6db140
6 changed files with 118 additions and 74 deletions
|
@ -114,7 +114,7 @@ export const msgs: Map<string, string> = Map({
|
||||||
"upload.add.fail": "Some files conflict with uploading files, please check.",
|
"upload.add.fail": "Some files conflict with uploading files, please check.",
|
||||||
"server.fail": "The operation failed in the server",
|
"server.fail": "The operation failed in the server",
|
||||||
"err.updater": "updater error",
|
"err.updater": "updater error",
|
||||||
"err.uploadMgr": "upload Manager error",
|
"err.uploadMgr": "Upload Manager error",
|
||||||
"err.server": "The operation failed in the server",
|
"err.server": "The operation failed in the server",
|
||||||
"err.script.cors": "script error with CORS",
|
"err.script.cors": "script error with CORS",
|
||||||
"err.unknown": "unknown error",
|
"err.unknown": "unknown error",
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { UploadStatus, UploadState } from "./interface";
|
||||||
const defaultChunkLen = 1024 * 1024 * 1;
|
const defaultChunkLen = 1024 * 1024 * 1;
|
||||||
const speedDownRatio = 0.5;
|
const speedDownRatio = 0.5;
|
||||||
const speedUpRatio = 1.1;
|
const speedUpRatio = 1.1;
|
||||||
const speedLimit = 1024 * 1024 * 10; // 10MB
|
const chunkLimit = 1024 * 1024 * 50; // 50MB
|
||||||
const createRetryLimit = 512;
|
const createRetryLimit = 512;
|
||||||
const uploadRetryLimit = 1024;
|
const uploadRetryLimit = 1024;
|
||||||
const backoffMax = 2000;
|
const backoffMax = 2000;
|
||||||
|
@ -22,7 +22,7 @@ export class ChunkUploader {
|
||||||
|
|
||||||
private chunkLen: number = defaultChunkLen;
|
private chunkLen: number = defaultChunkLen;
|
||||||
|
|
||||||
constructor() { }
|
constructor() {}
|
||||||
|
|
||||||
setClient = (client: IFilesClient) => {
|
setClient = (client: IFilesClient) => {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
|
@ -70,8 +70,8 @@ export class ChunkUploader {
|
||||||
): Promise<UploadStatus> => {
|
): Promise<UploadStatus> => {
|
||||||
if (this.chunkLen === 0) {
|
if (this.chunkLen === 0) {
|
||||||
this.chunkLen = 1; // reset it to 1B
|
this.chunkLen = 1; // reset it to 1B
|
||||||
} else if (this.chunkLen > speedLimit) {
|
} else if (this.chunkLen > chunkLimit) {
|
||||||
this.chunkLen = speedLimit;
|
this.chunkLen = chunkLimit;
|
||||||
} else if (uploaded > file.size) {
|
} else if (uploaded > file.size) {
|
||||||
return {
|
return {
|
||||||
filePath,
|
filePath,
|
||||||
|
@ -143,7 +143,9 @@ export class ChunkUploader {
|
||||||
filePath,
|
filePath,
|
||||||
uploaded,
|
uploaded,
|
||||||
state: UploadState.Error,
|
state: UploadState.Error,
|
||||||
err: `failed to upload chunk: ${uploadResp.statusText}, ${JSON.stringify(uploadResp.data)}`,
|
err: `failed to upload chunk: ${
|
||||||
|
uploadResp.statusText
|
||||||
|
}, ${JSON.stringify(uploadResp.data)}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ export interface IChunkUploader {
|
||||||
) => Promise<UploadStatus>;
|
) => Promise<UploadStatus>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type eventKind = SyncReqKind | ErrKind | UploadInfoKind;
|
export type eventKind = SyncReqKind | ErrKind | UploadInfoKind | ImIdleKind;
|
||||||
export interface WorkerEvent {
|
export interface WorkerEvent {
|
||||||
kind: eventKind;
|
kind: eventKind;
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ export const syncReqKind: SyncReqKind = "worker.req.sync";
|
||||||
|
|
||||||
export interface SyncReq extends WorkerEvent {
|
export interface SyncReq extends WorkerEvent {
|
||||||
kind: SyncReqKind;
|
kind: SyncReqKind;
|
||||||
file: File,
|
file: File;
|
||||||
filePath: string;
|
filePath: string;
|
||||||
size: number;
|
size: number;
|
||||||
uploaded: number;
|
uploaded: number;
|
||||||
|
@ -69,7 +69,13 @@ export interface UploadInfoResp extends WorkerEvent {
|
||||||
err: string;
|
err: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FileWorkerResp = ErrResp | UploadInfoResp;
|
export type ImIdleKind = "worker.resp.idle";
|
||||||
|
export const imIdleKind: ImIdleKind = "worker.resp.idle";
|
||||||
|
export interface ImIdleResp extends WorkerEvent {
|
||||||
|
kind: ImIdleKind;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FileWorkerResp = ErrResp | UploadInfoResp | ImIdleResp;
|
||||||
|
|
||||||
export class MockWorker {
|
export class MockWorker {
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
|
@ -5,7 +5,9 @@ import {
|
||||||
SyncReq,
|
SyncReq,
|
||||||
errKind,
|
errKind,
|
||||||
ErrResp,
|
ErrResp,
|
||||||
|
ImIdleResp,
|
||||||
uploadInfoKind,
|
uploadInfoKind,
|
||||||
|
imIdleKind,
|
||||||
UploadInfoResp,
|
UploadInfoResp,
|
||||||
FileWorkerResp,
|
FileWorkerResp,
|
||||||
UploadStatus,
|
UploadStatus,
|
||||||
|
@ -13,14 +15,32 @@ import {
|
||||||
IChunkUploader,
|
IChunkUploader,
|
||||||
} from "./interface";
|
} from "./interface";
|
||||||
|
|
||||||
|
const win: Window = self as any;
|
||||||
|
|
||||||
export class UploadWorker {
|
export class UploadWorker {
|
||||||
private uploader: IChunkUploader = new ChunkUploader();
|
private uploader: IChunkUploader = new ChunkUploader();
|
||||||
|
private cycle: number = 100;
|
||||||
|
private working: boolean = false;
|
||||||
|
|
||||||
sendEvent = (resp: FileWorkerResp): void => {
|
sendEvent = (resp: FileWorkerResp): void => {
|
||||||
// TODO: make this abstract
|
// TODO: make this abstract
|
||||||
throw new Error("not implemented");
|
throw new Error("not implemented");
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor() {}
|
constructor() {
|
||||||
|
win.setInterval(this.checkIdle, this.cycle);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkIdle = () => {
|
||||||
|
if (this.working) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resp: ImIdleResp = {
|
||||||
|
kind: imIdleKind,
|
||||||
|
};
|
||||||
|
this.sendEvent(resp);
|
||||||
|
};
|
||||||
|
|
||||||
setUploader = (uploader: IChunkUploader) => {
|
setUploader = (uploader: IChunkUploader) => {
|
||||||
this.uploader = uploader;
|
this.uploader = uploader;
|
||||||
|
@ -46,7 +66,8 @@ export class UploadWorker {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMsg = (event: MessageEvent) => {
|
onMsg = async (event: MessageEvent) => {
|
||||||
|
this.working = true;
|
||||||
const req = event.data as FileWorkerReq;
|
const req = event.data as FileWorkerReq;
|
||||||
|
|
||||||
switch (req.kind) {
|
switch (req.kind) {
|
||||||
|
@ -54,19 +75,25 @@ export class UploadWorker {
|
||||||
const syncReq = req as SyncReq;
|
const syncReq = req as SyncReq;
|
||||||
|
|
||||||
if (syncReq.created) {
|
if (syncReq.created) {
|
||||||
this.uploader
|
const status = await this.uploader.upload(
|
||||||
.upload(syncReq.filePath, syncReq.file, syncReq.uploaded)
|
syncReq.filePath,
|
||||||
.then(this.handleUploadStatus);
|
syncReq.file,
|
||||||
|
syncReq.uploaded
|
||||||
|
);
|
||||||
|
await this.handleUploadStatus(status);
|
||||||
} else {
|
} else {
|
||||||
this.uploader
|
const status = await this.uploader.create(
|
||||||
.create(syncReq.filePath, syncReq.file)
|
syncReq.filePath,
|
||||||
.then(this.handleUploadStatus);
|
syncReq.file
|
||||||
|
);
|
||||||
|
await this.handleUploadStatus(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.error(`unknown worker request(${JSON.stringify(req)})`);
|
console.error(`unknown worker request(${JSON.stringify(req)})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.working = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
onError = (ev: ErrorEvent) => {
|
onError = (ev: ErrorEvent) => {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
syncReqKind,
|
syncReqKind,
|
||||||
errKind,
|
errKind,
|
||||||
uploadInfoKind,
|
uploadInfoKind,
|
||||||
|
imIdleKind,
|
||||||
UploadState,
|
UploadState,
|
||||||
} from "./interface";
|
} from "./interface";
|
||||||
import { errUploadMgr } from "../common/errors";
|
import { errUploadMgr } from "../common/errors";
|
||||||
|
@ -35,8 +36,9 @@ export class UploadMgr {
|
||||||
constructor(worker: IWorker) {
|
constructor(worker: IWorker) {
|
||||||
this.worker = worker;
|
this.worker = worker;
|
||||||
this.worker.onmessage = this.respHandler;
|
this.worker.onmessage = this.respHandler;
|
||||||
|
}
|
||||||
|
|
||||||
const syncing = () => {
|
syncing = () => {
|
||||||
if (this.infos.size === 0) {
|
if (this.infos.size === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -73,12 +75,6 @@ export class UploadMgr {
|
||||||
|
|
||||||
this.idx++;
|
this.idx++;
|
||||||
};
|
};
|
||||||
this.intervalID = win.setInterval(syncing, this.cycle);
|
|
||||||
}
|
|
||||||
|
|
||||||
destory = () => {
|
|
||||||
win.clearInterval(this.intervalID);
|
|
||||||
};
|
|
||||||
|
|
||||||
_setInfos = (infos: OrderedMap<string, UploadEntry>) => {
|
_setInfos = (infos: OrderedMap<string, UploadEntry>) => {
|
||||||
this.infos = infos;
|
this.infos = infos;
|
||||||
|
@ -187,6 +183,9 @@ export class UploadMgr {
|
||||||
const resp = event.data as FileWorkerResp;
|
const resp = event.data as FileWorkerResp;
|
||||||
|
|
||||||
switch (resp.kind) {
|
switch (resp.kind) {
|
||||||
|
case imIdleKind:
|
||||||
|
this.syncing();
|
||||||
|
break;
|
||||||
case errKind:
|
case errKind:
|
||||||
const errResp = resp as ErrResp;
|
const errResp = resp as ErrResp;
|
||||||
const errEntry = this.infos.get(errResp.filePath);
|
const errEntry = this.infos.get(errResp.filePath);
|
||||||
|
|
|
@ -18,9 +18,9 @@ import (
|
||||||
"github.com/ihexxa/gocfg"
|
"github.com/ihexxa/gocfg"
|
||||||
"github.com/ihexxa/multipart"
|
"github.com/ihexxa/multipart"
|
||||||
|
|
||||||
|
"github.com/ihexxa/quickshare/src/db/userstore"
|
||||||
"github.com/ihexxa/quickshare/src/depidx"
|
"github.com/ihexxa/quickshare/src/depidx"
|
||||||
q "github.com/ihexxa/quickshare/src/handlers"
|
q "github.com/ihexxa/quickshare/src/handlers"
|
||||||
"github.com/ihexxa/quickshare/src/db/userstore"
|
|
||||||
"github.com/ihexxa/quickshare/src/worker/localworker"
|
"github.com/ihexxa/quickshare/src/worker/localworker"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -793,11 +793,21 @@ func (h *FileHandlers) DelUploading(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, err = h.deps.FS().Stat(tmpFilePath)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
// no op
|
||||||
|
} else {
|
||||||
|
c.JSON(q.ErrResp(c, 500, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
err = h.deps.FS().Remove(tmpFilePath)
|
err = h.deps.FS().Remove(tmpFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(q.ErrResp(c, 500, err))
|
c.JSON(q.ErrResp(c, 500, err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err = h.uploadMgr.DelInfo(userID, tmpFilePath)
|
err = h.uploadMgr.DelInfo(userID, tmpFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue