fix(uploader): add error info and fix issues in uploader
This commit is contained in:
parent
e462c349a5
commit
3b1508af51
6 changed files with 138 additions and 92 deletions
|
@ -8,7 +8,7 @@ server:
|
||||||
debug: true
|
debug: true
|
||||||
host: "127.0.0.1"
|
host: "127.0.0.1"
|
||||||
port: 8686
|
port: 8686
|
||||||
readTimeout: 2000
|
readTimeout: 4000
|
||||||
writerTimeout: 86400000 # 1 day
|
writerTimeout: 86400000 # 1 day
|
||||||
maxHeaderBytes: 512
|
maxHeaderBytes: 512
|
||||||
publicPath: "public"
|
publicPath: "public"
|
||||||
|
|
|
@ -725,3 +725,9 @@ div.hr {
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.alert-red {
|
||||||
|
border: dashed 1px #e74c3c;
|
||||||
|
color: #e74c3c;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
|
@ -33,7 +33,7 @@ export interface BrowserProps {
|
||||||
dirPath: List<string>;
|
dirPath: List<string>;
|
||||||
isSharing: boolean;
|
isSharing: boolean;
|
||||||
items: List<MetadataResp>;
|
items: List<MetadataResp>;
|
||||||
uploadings: List<UploadInfo>;
|
uploadings: List<UploadEntry>;
|
||||||
sharings: List<string>;
|
sharings: List<string>;
|
||||||
|
|
||||||
uploadFiles: List<File>;
|
uploadFiles: List<File>;
|
||||||
|
@ -328,8 +328,7 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const nameCellClass = `item-name item-name-${
|
const nameCellClass = `item-name item-name-${this.props.browser.isVertical ? "vertical" : "horizontal"
|
||||||
this.props.browser.isVertical ? "vertical" : "horizontal"
|
|
||||||
} pointer`;
|
} pointer`;
|
||||||
const sizeCellClass = this.props.browser.isVertical
|
const sizeCellClass = this.props.browser.isVertical
|
||||||
? `hidden margin-s`
|
? `hidden margin-s`
|
||||||
|
@ -422,8 +421,7 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
<span className="padding-m">
|
<span className="padding-m">
|
||||||
<button
|
<button
|
||||||
onClick={() => this.select(item.name)}
|
onClick={() => this.select(item.name)}
|
||||||
className={`${
|
className={`${isSelected ? "blue0-bg white-font" : "grey2-bg grey3-font"
|
||||||
isSelected ? "blue0-bg white-font" : "grey2-bg grey3-font"
|
|
||||||
}`}
|
}`}
|
||||||
style={{ width: "8rem", display: "inline-block" }}
|
style={{ width: "8rem", display: "inline-block" }}
|
||||||
>
|
>
|
||||||
|
@ -470,8 +468,7 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => this.select(item.name)}
|
onClick={() => this.select(item.name)}
|
||||||
className={`${
|
className={`${isSelected ? "blue0-bg white-font" : "grey2-bg grey3-font"
|
||||||
isSelected ? "blue0-bg white-font" : "grey2-bg grey3-font"
|
|
||||||
}`}
|
}`}
|
||||||
style={{ width: "8rem", display: "inline-block" }}
|
style={{ width: "8rem", display: "inline-block" }}
|
||||||
>
|
>
|
||||||
|
@ -593,13 +590,13 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
const uploadingList = this.props.browser.uploadings.map(
|
const uploadingList = this.props.browser.uploadings.map(
|
||||||
(uploading: UploadInfo) => {
|
(uploading: UploadEntry) => {
|
||||||
const pathParts = uploading.realFilePath.split("/");
|
const pathParts = uploading.filePath.split("/");
|
||||||
const fileName = pathParts[pathParts.length - 1];
|
const fileName = pathParts[pathParts.length - 1];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div key={uploading.filePath}>
|
||||||
<Flexbox
|
<Flexbox
|
||||||
key={uploading.realFilePath}
|
|
||||||
children={List([
|
children={List([
|
||||||
<span className="padding-m">
|
<span className="padding-m">
|
||||||
<Flexbox
|
<Flexbox
|
||||||
|
@ -622,13 +619,13 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
|
|
||||||
<div className="item-op padding-m">
|
<div className="item-op padding-m">
|
||||||
<button
|
<button
|
||||||
onClick={() => this.stopUploading(uploading.realFilePath)}
|
onClick={() => this.stopUploading(uploading.filePath)}
|
||||||
className="grey3-bg grey4-font margin-r-m"
|
className="grey3-bg grey4-font margin-r-m"
|
||||||
>
|
>
|
||||||
{this.props.msg.pkg.get("browser.stop")}
|
{this.props.msg.pkg.get("browser.stop")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => this.deleteUpload(uploading.realFilePath)}
|
onClick={() => this.deleteUpload(uploading.filePath)}
|
||||||
className="grey3-bg grey4-font"
|
className="grey3-bg grey4-font"
|
||||||
>
|
>
|
||||||
{this.props.msg.pkg.get("browser.delete")}
|
{this.props.msg.pkg.get("browser.delete")}
|
||||||
|
@ -637,6 +634,14 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
])}
|
])}
|
||||||
childrenStyles={List([{}, { justifyContent: "flex-end" }])}
|
childrenStyles={List([{}, { justifyContent: "flex-end" }])}
|
||||||
/>
|
/>
|
||||||
|
{uploading.err.trim() === "" ? null : (
|
||||||
|
<div className="alert-red margin-s">
|
||||||
|
<span className="padding-m">
|
||||||
|
{uploading.err.trim()}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -668,7 +673,7 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="container">
|
<div className="container padding-b-m">
|
||||||
<Flexbox
|
<Flexbox
|
||||||
children={List([
|
children={List([
|
||||||
<span className="padding-m">
|
<span className="padding-m">
|
||||||
|
@ -721,8 +726,7 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
type="text"
|
type="text"
|
||||||
readOnly
|
readOnly
|
||||||
className="margin-r-m"
|
className="margin-r-m"
|
||||||
value={`${
|
value={`${document.location.href.split("?")[0]
|
||||||
document.location.href.split("?")[0]
|
|
||||||
}?dir=${encodeURIComponent(dirPath)}`}
|
}?dir=${encodeURIComponent(dirPath)}`}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { List, Set, Map } from "immutable";
|
||||||
|
|
||||||
import BgWorker from "../worker/upload.bg.worker";
|
import BgWorker from "../worker/upload.bg.worker";
|
||||||
import { FgWorker } from "../worker/upload.fg.worker";
|
import { FgWorker } from "../worker/upload.fg.worker";
|
||||||
|
import { UploadEntry } from "../worker/interface";
|
||||||
|
|
||||||
import { BrowserProps } from "./browser";
|
import { BrowserProps } from "./browser";
|
||||||
import { PanesProps } from "./panes";
|
import { PanesProps } from "./panes";
|
||||||
|
@ -9,8 +10,7 @@ import { LoginProps } from "./pane_login";
|
||||||
import { AdminProps } from "./pane_admin";
|
import { AdminProps } from "./pane_admin";
|
||||||
|
|
||||||
import { MsgPackage } from "../i18n/msger";
|
import { MsgPackage } from "../i18n/msger";
|
||||||
import { Item } from "./browser";
|
import { User, MetadataResp } from "../client";
|
||||||
import { UploadInfo, User, MetadataResp } from "../client";
|
|
||||||
import { initUploadMgr, IWorker } from "../worker/upload_mgr";
|
import { initUploadMgr, IWorker } from "../worker/upload_mgr";
|
||||||
|
|
||||||
export interface MsgProps {
|
export interface MsgProps {
|
||||||
|
@ -56,7 +56,7 @@ export function initState(): ICoreState {
|
||||||
items: List<MetadataResp>([]),
|
items: List<MetadataResp>([]),
|
||||||
sharings: List<string>([]),
|
sharings: List<string>([]),
|
||||||
isSharing: false,
|
isSharing: false,
|
||||||
uploadings: List<UploadInfo>([]),
|
uploadings: List<UploadEntry>([]),
|
||||||
uploadValue: "",
|
uploadValue: "",
|
||||||
uploadFiles: List<File>([]),
|
uploadFiles: List<File>([]),
|
||||||
tab: "",
|
tab: "",
|
||||||
|
|
|
@ -39,7 +39,7 @@ export class Updater {
|
||||||
|
|
||||||
initUploads = () => {
|
initUploads = () => {
|
||||||
this.props.browser.uploadings.forEach((entry) => {
|
this.props.browser.uploadings.forEach((entry) => {
|
||||||
Up().addStopped(entry.realFilePath, entry.uploaded, entry.size);
|
Up().addStopped(entry.filePath, entry.uploaded, entry.size);
|
||||||
});
|
});
|
||||||
// this.setUploadings(Up().list());
|
// this.setUploadings(Up().list());
|
||||||
};
|
};
|
||||||
|
@ -63,13 +63,9 @@ export class Updater {
|
||||||
};
|
};
|
||||||
|
|
||||||
setUploadings = (infos: Map<string, UploadEntry>) => {
|
setUploadings = (infos: Map<string, UploadEntry>) => {
|
||||||
this.props.browser.uploadings = List<UploadInfo>(
|
this.props.browser.uploadings = List<UploadEntry>(
|
||||||
infos.valueSeq().map((v: UploadEntry): UploadInfo => {
|
infos.valueSeq().map((entry: UploadEntry): UploadEntry => {
|
||||||
return {
|
return entry;
|
||||||
realFilePath: v.filePath,
|
|
||||||
size: v.size,
|
|
||||||
uploaded: v.uploaded,
|
|
||||||
};
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -105,13 +101,40 @@ export class Updater {
|
||||||
};
|
};
|
||||||
|
|
||||||
refreshUploadings = async (): Promise<boolean> => {
|
refreshUploadings = async (): Promise<boolean> => {
|
||||||
|
// this function get information from server and merge them with local information
|
||||||
|
// because some information (error) can only be detected from local
|
||||||
const luResp = await this.filesClient.listUploadings();
|
const luResp = await this.filesClient.listUploadings();
|
||||||
|
if (luResp.status !== 200) {
|
||||||
|
// TODO: i18n
|
||||||
|
console.error(luResp.data);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
this.props.browser.uploadings =
|
let remoteUploads = Map<string, UploadInfo>([]);
|
||||||
luResp.status === 200
|
luResp.data.uploadInfos.forEach((info: UploadInfo) => {
|
||||||
? List<UploadInfo>(luResp.data.uploadInfos)
|
remoteUploads = remoteUploads.set(info.realFilePath, info);
|
||||||
: this.props.browser.uploadings;
|
})
|
||||||
return luResp.status === 200;
|
|
||||||
|
let updatedUploads = List<UploadEntry>([]);
|
||||||
|
this.props.browser.uploadings.forEach((localInfo: UploadEntry) => {
|
||||||
|
const remoteInfo = remoteUploads.get(localInfo.filePath);
|
||||||
|
if (remoteInfo == null) {
|
||||||
|
// TODO: i18n
|
||||||
|
localInfo.err = "not found in server";
|
||||||
|
} else {
|
||||||
|
updatedUploads.push({
|
||||||
|
file: localInfo.file,
|
||||||
|
filePath: localInfo.filePath,
|
||||||
|
size: remoteInfo.size,
|
||||||
|
uploaded: remoteInfo.uploaded,
|
||||||
|
state: localInfo.state,
|
||||||
|
err: localInfo.err,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.props.browser.uploadings = updatedUploads;
|
||||||
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
stopUploading = (filePath: string) => {
|
stopUploading = (filePath: string) => {
|
||||||
|
|
|
@ -6,7 +6,8 @@ import { UploadStatus, UploadState } from "./interface";
|
||||||
// TODO: move chunk copying to worker
|
// TODO: move chunk copying to worker
|
||||||
const defaultChunkLen = 1024 * 1024 * 1;
|
const defaultChunkLen = 1024 * 1024 * 1;
|
||||||
const speedDownRatio = 0.5;
|
const speedDownRatio = 0.5;
|
||||||
const speedUpRatio = 1.05;
|
const speedUpRatio = 1.1;
|
||||||
|
const speedLimit = 1024 * 1024 * 10; // 10MB
|
||||||
const createRetryLimit = 2;
|
const createRetryLimit = 2;
|
||||||
const uploadRetryLimit = 1024;
|
const uploadRetryLimit = 1024;
|
||||||
const backoffMax = 2000;
|
const backoffMax = 2000;
|
||||||
|
@ -17,12 +18,11 @@ export interface ReaderResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ChunkUploader {
|
export class ChunkUploader {
|
||||||
private reader = new FileReader();
|
|
||||||
private client: IFilesClient = new FilesClient("");
|
private client: IFilesClient = new FilesClient("");
|
||||||
|
|
||||||
private chunkLen: number = defaultChunkLen;
|
private chunkLen: number = defaultChunkLen;
|
||||||
|
|
||||||
constructor() {}
|
constructor() { }
|
||||||
|
|
||||||
setClient = (client: IFilesClient) => {
|
setClient = (client: IFilesClient) => {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
|
@ -70,6 +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) {
|
||||||
|
this.chunkLen = speedLimit;
|
||||||
} else if (uploaded > file.size) {
|
} else if (uploaded > file.size) {
|
||||||
return {
|
return {
|
||||||
filePath,
|
filePath,
|
||||||
|
@ -79,13 +81,14 @@ export class ChunkUploader {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
const readerPromise = new Promise<ReaderResult>(
|
const readerPromise = new Promise<ReaderResult>(
|
||||||
(resolve: (result: ReaderResult) => void) => {
|
(resolve: (result: ReaderResult) => void) => {
|
||||||
this.reader.onerror = (_: ProgressEvent<FileReader>) => {
|
reader.onerror = (_: ProgressEvent<FileReader>) => {
|
||||||
resolve({ err: this.reader.error });
|
resolve({ err: reader.error });
|
||||||
};
|
};
|
||||||
|
|
||||||
this.reader.onloadend = (ev: ProgressEvent<FileReader>) => {
|
reader.onloadend = (ev: ProgressEvent<FileReader>) => {
|
||||||
const dataURL = ev.target.result as string; // readAsDataURL
|
const dataURL = ev.target.result as string; // readAsDataURL
|
||||||
const base64Chunk = dataURL.slice(dataURL.indexOf(",") + 1);
|
const base64Chunk = dataURL.slice(dataURL.indexOf(",") + 1);
|
||||||
resolve({ chunk: base64Chunk });
|
resolve({ chunk: base64Chunk });
|
||||||
|
@ -93,12 +96,22 @@ export class ChunkUploader {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
try {
|
||||||
const chunkRightPos =
|
const chunkRightPos =
|
||||||
uploaded + this.chunkLen > file.size
|
uploaded + this.chunkLen > file.size
|
||||||
? file.size
|
? file.size
|
||||||
: uploaded + this.chunkLen;
|
: uploaded + this.chunkLen;
|
||||||
const blob = file.slice(uploaded, chunkRightPos);
|
const blob = file.slice(uploaded, chunkRightPos);
|
||||||
this.reader.readAsDataURL(blob);
|
reader.readAsDataURL(blob);
|
||||||
|
break;
|
||||||
|
} catch (e) {
|
||||||
|
// The object is already busy reading Blobs
|
||||||
|
console.error(e);
|
||||||
|
await this.backOff();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const result = await readerPromise;
|
const result = await readerPromise;
|
||||||
if (result.err != null) {
|
if (result.err != null) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue