Add tests for client (#33)
* fix(fs/local): force closing fds and add backoffs, unit tests * test(client/web): add unit tests
This commit is contained in:
parent
1ff1e2024e
commit
ea3400aca6
12 changed files with 301 additions and 70 deletions
|
@ -1,13 +1,20 @@
|
||||||
import { List, Map } from "immutable";
|
import { List, Map } from "immutable";
|
||||||
import { mock, instance } from "ts-mockito";
|
import { mock, instance, anyString, when } from "ts-mockito";
|
||||||
|
|
||||||
import { initWithWorker } from "../core_state";
|
import { ICoreState, initWithWorker, mockState } from "../core_state";
|
||||||
import { makePromise, makeNumberResponse } from "../../test/helpers";
|
import {
|
||||||
import { Updater } from "../browser";
|
makePromise,
|
||||||
|
makeNumberResponse,
|
||||||
|
mockUpdate,
|
||||||
|
} from "../../test/helpers";
|
||||||
|
import { Updater, Browser } from "../browser";
|
||||||
import { MockUsersClient } from "../../client/users_mock";
|
import { MockUsersClient } from "../../client/users_mock";
|
||||||
import { FilesClient } from "../../client/files_mock";
|
import { UsersClient } from "../../client/users";
|
||||||
import { MetadataResp } from "../../client";
|
import { FilesClient } from "../../client/files";
|
||||||
import { MockWorker } from "../../worker/interface";
|
import { FilesClient as MockFilesClient } from "../../client/files_mock";
|
||||||
|
import { MetadataResp, UploadInfo } from "../../client";
|
||||||
|
import { MockWorker, UploadEntry } from "../../worker/interface";
|
||||||
|
import { UploadMgr } from "../../worker/upload_mgr";
|
||||||
|
|
||||||
describe("Browser", () => {
|
describe("Browser", () => {
|
||||||
const mockWorkerClass = mock(MockWorker);
|
const mockWorkerClass = mock(MockWorker);
|
||||||
|
@ -41,7 +48,7 @@ describe("Browser", () => {
|
||||||
];
|
];
|
||||||
|
|
||||||
const usersClient = new MockUsersClient("");
|
const usersClient = new MockUsersClient("");
|
||||||
const filesClient = new FilesClient("");
|
const filesClient = new MockFilesClient("");
|
||||||
for (let i = 0; i < tests.length; i++) {
|
for (let i = 0; i < tests.length; i++) {
|
||||||
const tc = tests[i];
|
const tc = tests[i];
|
||||||
|
|
||||||
|
@ -102,7 +109,7 @@ describe("Browser", () => {
|
||||||
];
|
];
|
||||||
|
|
||||||
const usersClient = new MockUsersClient("");
|
const usersClient = new MockUsersClient("");
|
||||||
const filesClient = new FilesClient("");
|
const filesClient = new MockFilesClient("");
|
||||||
for (let i = 0; i < tests.length; i++) {
|
for (let i = 0; i < tests.length; i++) {
|
||||||
const tc = tests[i];
|
const tc = tests[i];
|
||||||
|
|
||||||
|
@ -163,7 +170,7 @@ describe("Browser", () => {
|
||||||
];
|
];
|
||||||
|
|
||||||
const usersClient = new MockUsersClient("");
|
const usersClient = new MockUsersClient("");
|
||||||
const filesClient = new FilesClient("");
|
const filesClient = new MockFilesClient("");
|
||||||
for (let i = 0; i < tests.length; i++) {
|
for (let i = 0; i < tests.length; i++) {
|
||||||
const tc = tests[i];
|
const tc = tests[i];
|
||||||
|
|
||||||
|
@ -190,4 +197,72 @@ describe("Browser", () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
xtest("Browser: deleteUploading", async () => {
|
||||||
|
interface TestCase {
|
||||||
|
deleteFile: string;
|
||||||
|
preState: ICoreState;
|
||||||
|
postState: ICoreState;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tcs: any = [
|
||||||
|
{
|
||||||
|
deleteFile: "./path/file",
|
||||||
|
preState: {
|
||||||
|
browser: {
|
||||||
|
uploadings: List<UploadInfo>([
|
||||||
|
{
|
||||||
|
realFilePath: "./path/file",
|
||||||
|
size: 1,
|
||||||
|
uploaded: 0,
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
update: mockUpdate,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
postState: {
|
||||||
|
browser: {
|
||||||
|
uploadings: List<UploadInfo>(),
|
||||||
|
update: mockUpdate,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const setState = (patch: any, state: ICoreState): ICoreState => {
|
||||||
|
state.panel.browser = patch.browser;
|
||||||
|
return state;
|
||||||
|
};
|
||||||
|
const mockFilesClientClass = mock(FilesClient);
|
||||||
|
when(mockFilesClientClass.deleteUploading(anyString())).thenResolve({
|
||||||
|
status: 200,
|
||||||
|
statusText: "",
|
||||||
|
data: "",
|
||||||
|
});
|
||||||
|
// TODO: the return should dpends on test case
|
||||||
|
when(mockFilesClientClass.listUploadings()).thenResolve({
|
||||||
|
status: 200,
|
||||||
|
statusText: "",
|
||||||
|
data: { uploadInfos: Array<UploadInfo>() },
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockUsersClientClass = mock(UsersClient);
|
||||||
|
|
||||||
|
const mockFilesClient = instance(mockFilesClientClass);
|
||||||
|
const mockUsersClient = instance(mockUsersClientClass);
|
||||||
|
tcs.forEach((tc: TestCase) => {
|
||||||
|
const preState = setState(tc.preState, mockState());
|
||||||
|
const postState = setState(tc.postState, mockState());
|
||||||
|
// const existingFileName = preState.panel.browser.uploadings.get(0).realFilePath;
|
||||||
|
const infos:Map<string, UploadEntry> = Map();
|
||||||
|
UploadMgr._setInfos(infos);
|
||||||
|
|
||||||
|
const component = new Browser(preState.panel.browser);
|
||||||
|
Updater.init(preState.panel.browser);
|
||||||
|
Updater.setClients(mockUsersClient, mockFilesClient);
|
||||||
|
|
||||||
|
component.deleteUploading(tc.deleteFile);
|
||||||
|
expect(Updater.props).toEqual(postState.panel.browser);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
49
src/client/web/src/components/__test__/panes.test.tsx
Normal file
49
src/client/web/src/components/__test__/panes.test.tsx
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import { Set } from "immutable";
|
||||||
|
|
||||||
|
import { ICoreState, mockState } from "../core_state";
|
||||||
|
import { Panes, Updater } from "../panes";
|
||||||
|
import { mockUpdate } from "../../test/helpers";
|
||||||
|
|
||||||
|
describe("Panes", () => {
|
||||||
|
test("Panes: closePane", async () => {
|
||||||
|
interface TestCase {
|
||||||
|
preState: ICoreState;
|
||||||
|
postState: ICoreState;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tcs: any = [
|
||||||
|
{
|
||||||
|
preState: {
|
||||||
|
panes: {
|
||||||
|
displaying: "settings",
|
||||||
|
paneNames: Set<string>(["settings", "login"]),
|
||||||
|
update: mockUpdate,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
postState: {
|
||||||
|
panes: {
|
||||||
|
displaying: "",
|
||||||
|
paneNames: Set<string>(["settings", "login"]),
|
||||||
|
update: mockUpdate,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const setState = (patch: any, state: ICoreState): ICoreState => {
|
||||||
|
state.panel.panes = patch.panes;
|
||||||
|
return state;
|
||||||
|
};
|
||||||
|
|
||||||
|
tcs.forEach((tc: TestCase) => {
|
||||||
|
const preState = setState(tc.preState, mockState());
|
||||||
|
const postState = setState(tc.postState, mockState());
|
||||||
|
|
||||||
|
const component = new Panes(preState.panel.panes);
|
||||||
|
Updater.init(preState.panel.panes);
|
||||||
|
|
||||||
|
component.closePane();
|
||||||
|
expect(Updater.props).toEqual(postState.panel.panes);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
52
src/client/web/src/components/__test__/root_frame.test.tsx
Normal file
52
src/client/web/src/components/__test__/root_frame.test.tsx
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import { Set } from "immutable";
|
||||||
|
// import { mock, instance } from "ts-mockito";
|
||||||
|
|
||||||
|
import { ICoreState, mockState } from "../core_state";
|
||||||
|
import { RootFrame } from "../root_frame";
|
||||||
|
import { Updater } from "../panes";
|
||||||
|
|
||||||
|
describe("RootFrame", () => {
|
||||||
|
test("component: showSettings", async () => {
|
||||||
|
interface TestCase {
|
||||||
|
preState: ICoreState;
|
||||||
|
postState: ICoreState;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockUpdate = (apply: (prevState: ICoreState) => ICoreState): void => {};
|
||||||
|
const tcs: any = [
|
||||||
|
{
|
||||||
|
preState: {
|
||||||
|
displaying: "",
|
||||||
|
panes: {
|
||||||
|
displaying: "",
|
||||||
|
paneNames: Set<string>(["settings", "login"]),
|
||||||
|
},
|
||||||
|
update: mockUpdate,
|
||||||
|
},
|
||||||
|
postState: {
|
||||||
|
displaying: "settings",
|
||||||
|
panes: {
|
||||||
|
displaying: "settings",
|
||||||
|
paneNames: Set<string>(["settings", "login"]),
|
||||||
|
},
|
||||||
|
update: mockUpdate,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const setState = (patch: any, state: ICoreState): ICoreState => {
|
||||||
|
return { ...state, panel: { ...state.panel, ...patch } };
|
||||||
|
};
|
||||||
|
|
||||||
|
tcs.forEach((tc: TestCase) => {
|
||||||
|
const preState = setState(tc.preState, mockState());
|
||||||
|
const postState = setState(tc.postState, mockState());
|
||||||
|
|
||||||
|
const component = new RootFrame(preState.panel);
|
||||||
|
Updater.init(preState.panel.panes);
|
||||||
|
|
||||||
|
component.showSettings();
|
||||||
|
expect(Updater.props).toEqual(postState.panel.panes);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -46,7 +46,7 @@ function getItemPath(dirPath: string, itemName: string): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Updater {
|
export class Updater {
|
||||||
private static props: Props;
|
static props: Props;
|
||||||
private static usersClient: IUsersClient;
|
private static usersClient: IUsersClient;
|
||||||
private static filesClient: IFilesClient;
|
private static filesClient: IFilesClient;
|
||||||
|
|
||||||
|
@ -160,8 +160,12 @@ export class Updater {
|
||||||
|
|
||||||
static addUploadFiles = (fileList: FileList, len: number) => {
|
static addUploadFiles = (fileList: FileList, len: number) => {
|
||||||
for (let i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
|
const filePath = getItemPath(
|
||||||
|
Updater.props.dirPath.join("/"),
|
||||||
|
fileList[i].name
|
||||||
|
);
|
||||||
// do not wait for the promise
|
// do not wait for the promise
|
||||||
UploadMgr.add(fileList[i], fileList[i].name);
|
UploadMgr.add(fileList[i], filePath);
|
||||||
}
|
}
|
||||||
Updater.setUploadings(UploadMgr.list());
|
Updater.setUploadings(UploadMgr.list());
|
||||||
};
|
};
|
||||||
|
@ -215,9 +219,6 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// showPane = () => {
|
|
||||||
// this.setState({ show: !this.state.show });
|
|
||||||
// };
|
|
||||||
onInputChange = (ev: React.ChangeEvent<HTMLInputElement>) => {
|
onInputChange = (ev: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
this.setState({ inputValue: ev.target.value });
|
this.setState({ inputValue: ev.target.value });
|
||||||
};
|
};
|
||||||
|
@ -245,7 +246,15 @@ export class Browser extends React.Component<Props, State, {}> {
|
||||||
};
|
};
|
||||||
|
|
||||||
onMkDir = () => {
|
onMkDir = () => {
|
||||||
Updater.mkDir(this.state.inputValue)
|
if (this.state.inputValue === "") {
|
||||||
|
alert("folder name can not be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
const dirPath = getItemPath(
|
||||||
|
this.props.dirPath.join("/"),
|
||||||
|
this.state.inputValue
|
||||||
|
);
|
||||||
|
Updater.mkDir(dirPath)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.setState({ inputValue: "" });
|
this.setState({ inputValue: "" });
|
||||||
return Updater.setItems(this.props.dirPath);
|
return Updater.setItems(this.props.dirPath);
|
||||||
|
|
|
@ -3,11 +3,19 @@ import { List, Set } from "immutable";
|
||||||
import BgWorker from "../worker/upload.bg.worker";
|
import BgWorker from "../worker/upload.bg.worker";
|
||||||
import { FgWorker } from "../worker/upload.fgworker";
|
import { FgWorker } from "../worker/upload.fgworker";
|
||||||
|
|
||||||
import { Props as PanelProps } from "./panel";
|
import { Props as PanelProps } from "./root_frame";
|
||||||
import { Item } from "./browser";
|
import { Item } from "./browser";
|
||||||
import { UploadInfo } from "../client";
|
import { UploadInfo } from "../client";
|
||||||
import { UploadMgr, IWorker } from "../worker/upload_mgr";
|
import { UploadMgr, IWorker } from "../worker/upload_mgr";
|
||||||
|
|
||||||
|
export class BaseUpdater {
|
||||||
|
public static props: any;
|
||||||
|
public static init = (props: any) => (BaseUpdater.props = { ...props });
|
||||||
|
public static apply = (prevState: ICoreState): ICoreState => {
|
||||||
|
throw Error("apply is not implemented");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export interface IContext {
|
export interface IContext {
|
||||||
update: (targetStatePatch: any) => void;
|
update: (targetStatePatch: any) => void;
|
||||||
}
|
}
|
||||||
|
@ -25,11 +33,8 @@ export function initWithWorker(worker: IWorker): ICoreState {
|
||||||
|
|
||||||
export function init(): ICoreState {
|
export function init(): ICoreState {
|
||||||
const scripts = Array.from(document.querySelectorAll("script"));
|
const scripts = Array.from(document.querySelectorAll("script"));
|
||||||
if (!Worker) {
|
const worker = Worker == null ? new FgWorker() : new BgWorker();
|
||||||
alert("web worker is not supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
const worker = new BgWorker();
|
|
||||||
UploadMgr.init(worker);
|
UploadMgr.init(worker);
|
||||||
return initState();
|
return initState();
|
||||||
}
|
}
|
||||||
|
@ -65,3 +70,31 @@ export function initState(): ICoreState {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function mockState(): ICoreState {
|
||||||
|
return {
|
||||||
|
ctx: undefined,
|
||||||
|
isVertical: false,
|
||||||
|
panel: {
|
||||||
|
displaying: "browser",
|
||||||
|
authPane: {
|
||||||
|
authed: false,
|
||||||
|
},
|
||||||
|
browser: {
|
||||||
|
isVertical: false,
|
||||||
|
dirPath: List<string>(["."]),
|
||||||
|
items: List<Item>([]),
|
||||||
|
uploadings: List<UploadInfo>([]),
|
||||||
|
uploadValue: "",
|
||||||
|
uploadFiles: List<File>([]),
|
||||||
|
},
|
||||||
|
panes: {
|
||||||
|
displaying: "",
|
||||||
|
paneNames: Set<string>(["settings", "login"]),
|
||||||
|
login: {
|
||||||
|
authed: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ export interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Updater {
|
export class Updater {
|
||||||
private static props: Props;
|
static props: Props;
|
||||||
|
|
||||||
static init = (props: Props) => (Updater.props = { ...props });
|
static init = (props: Props) => (Updater.props = { ...props });
|
||||||
|
|
||||||
|
@ -44,17 +44,15 @@ export class Updater {
|
||||||
|
|
||||||
export interface State {}
|
export interface State {}
|
||||||
export class Panes extends React.Component<Props, State, {}> {
|
export class Panes extends React.Component<Props, State, {}> {
|
||||||
private update: (updater: (prevState: ICoreState) => ICoreState) => void;
|
|
||||||
constructor(p: Props) {
|
constructor(p: Props) {
|
||||||
super(p);
|
super(p);
|
||||||
Updater.init(p);
|
Updater.init(p);
|
||||||
this.update = p.update;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
closePane = () => {
|
closePane = () => {
|
||||||
if (this.props.displaying !== "login") {
|
if (this.props.displaying !== "login") {
|
||||||
Updater.displayPane("");
|
Updater.displayPane("");
|
||||||
this.update(Updater.updateState);
|
this.props.update(Updater.updateState);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -66,8 +64,8 @@ export class Panes extends React.Component<Props, State, {}> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const panesMap: Map<string, JSX.Element> = Map({
|
const panesMap: Map<string, JSX.Element> = Map({
|
||||||
settings: <PaneSettings login={this.props.login} update={this.update} />,
|
settings: <PaneSettings login={this.props.login} update={this.props.update} />,
|
||||||
login: <AuthPane authed={this.props.login.authed} update={this.update} />,
|
login: <AuthPane authed={this.props.login.authed} update={this.props.update} />,
|
||||||
});
|
});
|
||||||
|
|
||||||
const panes = panesMap.keySeq().map(
|
const panes = panesMap.keySeq().map(
|
||||||
|
|
|
@ -1,24 +1,22 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
import { ICoreState } from "./core_state";
|
import { ICoreState, BaseUpdater } from "./core_state";
|
||||||
import { Browser, Props as BrowserProps } from "./browser";
|
import { Browser, Props as BrowserProps } from "./browser";
|
||||||
import { AuthPane, Props as AuthPaneProps } from "./pane_login";
|
import { Props as PaneLoginProps } from "./pane_login";
|
||||||
import { Panes, Props as PanesProps, Updater as PanesUpdater } from "./panes";
|
import { Panes, Props as PanesProps, Updater as PanesUpdater } from "./panes";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
displaying: string;
|
displaying: string;
|
||||||
browser: BrowserProps;
|
browser: BrowserProps;
|
||||||
authPane: AuthPaneProps;
|
authPane: PaneLoginProps;
|
||||||
panes: PanesProps;
|
panes: PanesProps;
|
||||||
update?: (updater: (prevState: ICoreState) => ICoreState) => void;
|
update?: (updater: (prevState: ICoreState) => ICoreState) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Updater {
|
export class Updater {
|
||||||
private static props: Props;
|
public static props: Props;
|
||||||
|
public static init = (props: Props) => (BaseUpdater.props = { ...props });
|
||||||
static init = (props: Props) => (Updater.props = { ...props });
|
public static apply = (prevState: ICoreState): ICoreState => {
|
||||||
|
|
||||||
static setPanel = (prevState: ICoreState): ICoreState => {
|
|
||||||
return {
|
return {
|
||||||
...prevState,
|
...prevState,
|
||||||
panel: { ...prevState.panel, ...Updater.props },
|
panel: { ...prevState.panel, ...Updater.props },
|
||||||
|
@ -27,20 +25,19 @@ export class Updater {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface State {}
|
export interface State {}
|
||||||
export class Panel extends React.Component<Props, State, {}> {
|
export class RootFrame extends React.Component<Props, State, {}> {
|
||||||
private update: (updater: (prevState: ICoreState) => ICoreState) => void;
|
|
||||||
constructor(p: Props) {
|
constructor(p: Props) {
|
||||||
super(p);
|
super(p);
|
||||||
Updater.init(p);
|
Updater.init(p);
|
||||||
this.update = p.update;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
showSettings = () => {
|
showSettings = () => {
|
||||||
PanesUpdater.displayPane("settings");
|
PanesUpdater.displayPane("settings");
|
||||||
this.update(PanesUpdater.updateState);
|
this.props.update(PanesUpdater.updateState);
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const update = this.props.update;
|
||||||
return (
|
return (
|
||||||
<div className="theme-white desktop">
|
<div className="theme-white desktop">
|
||||||
<div id="bg" className="bg bg-img font-m">
|
<div id="bg" className="bg bg-img font-m">
|
||||||
|
@ -48,7 +45,7 @@ export class Panel extends React.Component<Props, State, {}> {
|
||||||
displaying={this.props.panes.displaying}
|
displaying={this.props.panes.displaying}
|
||||||
paneNames={this.props.panes.paneNames}
|
paneNames={this.props.panes.paneNames}
|
||||||
login={this.props.authPane}
|
login={this.props.authPane}
|
||||||
update={this.update}
|
update={update}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -78,16 +75,18 @@ export class Panel extends React.Component<Props, State, {}> {
|
||||||
dirPath={this.props.browser.dirPath}
|
dirPath={this.props.browser.dirPath}
|
||||||
items={this.props.browser.items}
|
items={this.props.browser.items}
|
||||||
uploadings={this.props.browser.uploadings}
|
uploadings={this.props.browser.uploadings}
|
||||||
update={this.update}
|
update={update}
|
||||||
uploadFiles={this.props.browser.uploadFiles}
|
uploadFiles={this.props.browser.uploadFiles}
|
||||||
uploadValue={this.props.browser.uploadValue}
|
uploadValue={this.props.browser.uploadValue}
|
||||||
isVertical={this.props.browser.isVertical}
|
isVertical={this.props.browser.isVertical}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="container-center black0-font tail margin-t-xl margin-b-xl">
|
<div className="container-center black0-font tail margin-t-xl margin-b-xl">
|
||||||
<a href="https://github.com/ihexxa/quickshare">Quickshare</a> -
|
<a href="https://github.com/ihexxa/quickshare">Quickshare</a> -
|
||||||
sharing in simple way.
|
sharing in simple way.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
|
@ -1,40 +1,30 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
import { ICoreState, init } from "./core_state";
|
import { ICoreState, init } from "./core_state";
|
||||||
import { Panel } from "./panel";
|
import { RootFrame } from "./root_frame";
|
||||||
|
|
||||||
export interface Props {}
|
export interface Props {}
|
||||||
export interface State extends ICoreState {}
|
export interface State extends ICoreState {}
|
||||||
|
|
||||||
export class UpdaterBase {
|
|
||||||
private static props: any;
|
|
||||||
static init = (props: any) => (UpdaterBase.props = {...props});
|
|
||||||
}
|
|
||||||
|
|
||||||
export class StateMgr extends React.Component<Props, State, {}> {
|
export class StateMgr extends React.Component<Props, State, {}> {
|
||||||
constructor(p: Props) {
|
constructor(p: Props) {
|
||||||
super(p);
|
super(p);
|
||||||
this.state = init();
|
this.state = init();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: any can be eliminated by adding union type of children states
|
update = (apply: (prevState: ICoreState) => ICoreState): void => {
|
||||||
update = (updater: (prevState:ICoreState) => ICoreState): void => {
|
this.setState(apply(this.state));
|
||||||
console.log("before", this.state)
|
|
||||||
this.setState(updater(this.state));
|
|
||||||
console.log("after", this.state)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<RootFrame
|
||||||
<Panel
|
|
||||||
authPane={this.state.panel.authPane}
|
authPane={this.state.panel.authPane}
|
||||||
displaying={this.state.panel.displaying}
|
displaying={this.state.panel.displaying}
|
||||||
update={this.update}
|
update={this.update}
|
||||||
browser={this.state.panel.browser}
|
browser={this.state.panel.browser}
|
||||||
panes={this.state.panel.panes}
|
panes={this.state.panel.panes}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Response } from "../client";
|
import { Response } from "../client";
|
||||||
|
import { ICoreState } from "../components/core_state";
|
||||||
|
|
||||||
export const makePromise = (ret: any): Promise<any> => {
|
export const makePromise = (ret: any): Promise<any> => {
|
||||||
return new Promise<any>((resolve) => {
|
return new Promise<any>((resolve) => {
|
||||||
|
@ -13,3 +14,5 @@ export const makeNumberResponse = (status: number): Promise<Response> => {
|
||||||
data: {},
|
data: {},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const mockUpdate = (apply: (prevState: ICoreState) => ICoreState): void => {};
|
||||||
|
|
|
@ -30,6 +30,8 @@ export class UploadWorker {
|
||||||
stopUploader = () => {
|
stopUploader = () => {
|
||||||
if (this.uploader != null) {
|
if (this.uploader != null) {
|
||||||
this.uploader.stop();
|
this.uploader.stop();
|
||||||
|
this.file = undefined;
|
||||||
|
this.filePath = undefined;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
getFilePath = (): string => {
|
getFilePath = (): string => {
|
||||||
|
@ -49,6 +51,7 @@ export class UploadWorker {
|
||||||
// find the first qualified task
|
// find the first qualified task
|
||||||
const syncReq = req as SyncReq;
|
const syncReq = req as SyncReq;
|
||||||
const infoArray = syncReq.infos;
|
const infoArray = syncReq.infos;
|
||||||
|
|
||||||
for (let i = 0; i < infoArray.length; i++) {
|
for (let i = 0; i < infoArray.length; i++) {
|
||||||
if (
|
if (
|
||||||
infoArray[i].runnable &&
|
infoArray[i].runnable &&
|
||||||
|
@ -59,6 +62,11 @@ export class UploadWorker {
|
||||||
this.startUploader(infoArray[i].file, infoArray[i].filePath);
|
this.startUploader(infoArray[i].file, infoArray[i].filePath);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
} else if (
|
||||||
|
!infoArray[i].runnable &&
|
||||||
|
infoArray[i].filePath == this.filePath
|
||||||
|
) {
|
||||||
|
this.stopUploader();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -8,6 +8,7 @@ const speedDownRatio = 0.5;
|
||||||
const speedUpRatio = 1.05;
|
const speedUpRatio = 1.05;
|
||||||
const createRetryLimit = 2;
|
const createRetryLimit = 2;
|
||||||
const uploadRetryLimit = 1024;
|
const uploadRetryLimit = 1024;
|
||||||
|
const backoffMax = 2000;
|
||||||
|
|
||||||
export interface IFileUploader {
|
export interface IFileUploader {
|
||||||
stop: () => void;
|
stop: () => void;
|
||||||
|
@ -71,6 +72,13 @@ export class FileUploader {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
backOff = async (): Promise<void> => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const delay = Math.floor(Math.random() * backoffMax);
|
||||||
|
setTimeout(resolve, delay);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
start = async (): Promise<boolean> => {
|
start = async (): Promise<boolean> => {
|
||||||
let resp: Response;
|
let resp: Response;
|
||||||
|
|
||||||
|
@ -81,6 +89,7 @@ export class FileUploader {
|
||||||
return await this.upload();
|
return await this.upload();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
await this.backOff();
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -153,6 +162,8 @@ export class FileUploader {
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this.backOff();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.errMsgs.push(e.toString());
|
this.errMsgs.push(e.toString());
|
||||||
|
|
|
@ -99,6 +99,16 @@ func (fs *LocalFS) translate(name string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *LocalFS) Create(path string) error {
|
func (fs *LocalFS) Create(path string) error {
|
||||||
|
fs.opensMtx.Lock()
|
||||||
|
defer fs.opensMtx.Unlock()
|
||||||
|
if len(fs.opens) > fs.opensLimit {
|
||||||
|
err := fs.closeOpens(true, map[string]bool{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("too many opens and fail to clean: %w", err)
|
||||||
|
}
|
||||||
|
return ErrTooManyOpens
|
||||||
|
}
|
||||||
|
|
||||||
fullpath, err := fs.translate(path)
|
fullpath, err := fs.translate(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -109,12 +119,6 @@ func (fs *LocalFS) Create(path string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.opensMtx.Lock()
|
|
||||||
defer fs.opensMtx.Unlock()
|
|
||||||
if len(fs.opens) > fs.opensLimit {
|
|
||||||
return ErrTooManyOpens
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.opens[fullpath] = &fileInfo{
|
fs.opens[fullpath] = &fileInfo{
|
||||||
lastAccess: time.Now(),
|
lastAccess: time.Now(),
|
||||||
fd: fd,
|
fd: fd,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue