fix(fe/worker): refactor fe uploading functions to improve robustness

This commit is contained in:
hexxa 2022-01-05 14:31:37 +08:00 committed by Hexxa
parent aca1ae6787
commit ff5e6db140
6 changed files with 118 additions and 74 deletions

View file

@ -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",

View file

@ -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)}`,
}; };
} }

View file

@ -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() {}

View file

@ -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) => {

View file

@ -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);

View file

@ -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 {