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 { mock, instance } from "ts-mockito";
|
||||
import { mock, instance, anyString, when } from "ts-mockito";
|
||||
|
||||
import { initWithWorker } from "../core_state";
|
||||
import { makePromise, makeNumberResponse } from "../../test/helpers";
|
||||
import { Updater } from "../browser";
|
||||
import { ICoreState, initWithWorker, mockState } from "../core_state";
|
||||
import {
|
||||
makePromise,
|
||||
makeNumberResponse,
|
||||
mockUpdate,
|
||||
} from "../../test/helpers";
|
||||
import { Updater, Browser } from "../browser";
|
||||
import { MockUsersClient } from "../../client/users_mock";
|
||||
import { FilesClient } from "../../client/files_mock";
|
||||
import { MetadataResp } from "../../client";
|
||||
import { MockWorker } from "../../worker/interface";
|
||||
import { UsersClient } from "../../client/users";
|
||||
import { FilesClient } from "../../client/files";
|
||||
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", () => {
|
||||
const mockWorkerClass = mock(MockWorker);
|
||||
|
@ -41,7 +48,7 @@ describe("Browser", () => {
|
|||
];
|
||||
|
||||
const usersClient = new MockUsersClient("");
|
||||
const filesClient = new FilesClient("");
|
||||
const filesClient = new MockFilesClient("");
|
||||
for (let i = 0; i < tests.length; i++) {
|
||||
const tc = tests[i];
|
||||
|
||||
|
@ -102,7 +109,7 @@ describe("Browser", () => {
|
|||
];
|
||||
|
||||
const usersClient = new MockUsersClient("");
|
||||
const filesClient = new FilesClient("");
|
||||
const filesClient = new MockFilesClient("");
|
||||
for (let i = 0; i < tests.length; i++) {
|
||||
const tc = tests[i];
|
||||
|
||||
|
@ -163,7 +170,7 @@ describe("Browser", () => {
|
|||
];
|
||||
|
||||
const usersClient = new MockUsersClient("");
|
||||
const filesClient = new FilesClient("");
|
||||
const filesClient = new MockFilesClient("");
|
||||
for (let i = 0; i < tests.length; 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 {
|
||||
private static props: Props;
|
||||
static props: Props;
|
||||
private static usersClient: IUsersClient;
|
||||
private static filesClient: IFilesClient;
|
||||
|
||||
|
@ -160,8 +160,12 @@ export class Updater {
|
|||
|
||||
static addUploadFiles = (fileList: FileList, len: number) => {
|
||||
for (let i = 0; i < len; i++) {
|
||||
const filePath = getItemPath(
|
||||
Updater.props.dirPath.join("/"),
|
||||
fileList[i].name
|
||||
);
|
||||
// do not wait for the promise
|
||||
UploadMgr.add(fileList[i], fileList[i].name);
|
||||
UploadMgr.add(fileList[i], filePath);
|
||||
}
|
||||
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>) => {
|
||||
this.setState({ inputValue: ev.target.value });
|
||||
};
|
||||
|
@ -245,7 +246,15 @@ export class Browser extends React.Component<Props, State, {}> {
|
|||
};
|
||||
|
||||
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(() => {
|
||||
this.setState({ inputValue: "" });
|
||||
return Updater.setItems(this.props.dirPath);
|
||||
|
@ -433,7 +442,7 @@ export class Browser extends React.Component<Props, State, {}> {
|
|||
<button
|
||||
onClick={() => this.select(item.name)}
|
||||
className={`white-font ${isSelected ? "blue0-bg" : ""}`}
|
||||
style={{width: "8rem", display: "inline-block"}}
|
||||
style={{ width: "8rem", display: "inline-block" }}
|
||||
>
|
||||
{isSelected ? "Deselect" : "Select"}
|
||||
</button>
|
||||
|
@ -467,7 +476,7 @@ export class Browser extends React.Component<Props, State, {}> {
|
|||
type="button"
|
||||
onClick={() => this.select(item.name)}
|
||||
className={`white-font ${isSelected ? "blue0-bg" : ""}`}
|
||||
style={{width: "8rem", display: "inline-block"}}
|
||||
style={{ width: "8rem", display: "inline-block" }}
|
||||
>
|
||||
{isSelected ? "Deselect" : "Select"}
|
||||
</button>
|
||||
|
|
|
@ -3,11 +3,19 @@ import { List, Set } from "immutable";
|
|||
import BgWorker from "../worker/upload.bg.worker";
|
||||
import { FgWorker } from "../worker/upload.fgworker";
|
||||
|
||||
import { Props as PanelProps } from "./panel";
|
||||
import { Props as PanelProps } from "./root_frame";
|
||||
import { Item } from "./browser";
|
||||
import { UploadInfo } from "../client";
|
||||
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 {
|
||||
update: (targetStatePatch: any) => void;
|
||||
}
|
||||
|
@ -25,11 +33,8 @@ export function initWithWorker(worker: IWorker): ICoreState {
|
|||
|
||||
export function init(): ICoreState {
|
||||
const scripts = Array.from(document.querySelectorAll("script"));
|
||||
if (!Worker) {
|
||||
alert("web worker is not supported");
|
||||
}
|
||||
const worker = Worker == null ? new FgWorker() : new BgWorker();
|
||||
|
||||
const worker = new BgWorker();
|
||||
UploadMgr.init(worker);
|
||||
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 {
|
||||
private static props: Props;
|
||||
static props: Props;
|
||||
|
||||
static init = (props: Props) => (Updater.props = { ...props });
|
||||
|
||||
|
@ -44,17 +44,15 @@ export class Updater {
|
|||
|
||||
export interface State {}
|
||||
export class Panes extends React.Component<Props, State, {}> {
|
||||
private update: (updater: (prevState: ICoreState) => ICoreState) => void;
|
||||
constructor(p: Props) {
|
||||
super(p);
|
||||
Updater.init(p);
|
||||
this.update = p.update;
|
||||
}
|
||||
|
||||
closePane = () => {
|
||||
if (this.props.displaying !== "login") {
|
||||
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({
|
||||
settings: <PaneSettings login={this.props.login} update={this.update} />,
|
||||
login: <AuthPane authed={this.props.login.authed} update={this.update} />,
|
||||
settings: <PaneSettings login={this.props.login} update={this.props.update} />,
|
||||
login: <AuthPane authed={this.props.login.authed} update={this.props.update} />,
|
||||
});
|
||||
|
||||
const panes = panesMap.keySeq().map(
|
||||
|
|
|
@ -1,24 +1,22 @@
|
|||
import * as React from "react";
|
||||
|
||||
import { ICoreState } from "./core_state";
|
||||
import { ICoreState, BaseUpdater } from "./core_state";
|
||||
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";
|
||||
|
||||
export interface Props {
|
||||
displaying: string;
|
||||
browser: BrowserProps;
|
||||
authPane: AuthPaneProps;
|
||||
authPane: PaneLoginProps;
|
||||
panes: PanesProps;
|
||||
update?: (updater: (prevState: ICoreState) => ICoreState) => void;
|
||||
}
|
||||
|
||||
export class Updater {
|
||||
private static props: Props;
|
||||
|
||||
static init = (props: Props) => (Updater.props = { ...props });
|
||||
|
||||
static setPanel = (prevState: ICoreState): ICoreState => {
|
||||
public static props: Props;
|
||||
public static init = (props: Props) => (BaseUpdater.props = { ...props });
|
||||
public static apply = (prevState: ICoreState): ICoreState => {
|
||||
return {
|
||||
...prevState,
|
||||
panel: { ...prevState.panel, ...Updater.props },
|
||||
|
@ -27,20 +25,19 @@ export class Updater {
|
|||
}
|
||||
|
||||
export interface State {}
|
||||
export class Panel extends React.Component<Props, State, {}> {
|
||||
private update: (updater: (prevState: ICoreState) => ICoreState) => void;
|
||||
export class RootFrame extends React.Component<Props, State, {}> {
|
||||
constructor(p: Props) {
|
||||
super(p);
|
||||
Updater.init(p);
|
||||
this.update = p.update;
|
||||
}
|
||||
|
||||
showSettings = () => {
|
||||
PanesUpdater.displayPane("settings");
|
||||
this.update(PanesUpdater.updateState);
|
||||
this.props.update(PanesUpdater.updateState);
|
||||
};
|
||||
|
||||
render() {
|
||||
const update = this.props.update;
|
||||
return (
|
||||
<div className="theme-white desktop">
|
||||
<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}
|
||||
paneNames={this.props.panes.paneNames}
|
||||
login={this.props.authPane}
|
||||
update={this.update}
|
||||
update={update}
|
||||
/>
|
||||
|
||||
<div
|
||||
|
@ -78,16 +75,18 @@ export class Panel extends React.Component<Props, State, {}> {
|
|||
dirPath={this.props.browser.dirPath}
|
||||
items={this.props.browser.items}
|
||||
uploadings={this.props.browser.uploadings}
|
||||
update={this.update}
|
||||
update={update}
|
||||
uploadFiles={this.props.browser.uploadFiles}
|
||||
uploadValue={this.props.browser.uploadValue}
|
||||
isVertical={this.props.browser.isVertical}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="container-center black0-font tail margin-t-xl margin-b-xl">
|
||||
<a href="https://github.com/ihexxa/quickshare">Quickshare</a> -
|
||||
sharing in simple way.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
|
@ -1,40 +1,30 @@
|
|||
import * as React from "react";
|
||||
|
||||
import { ICoreState, init } from "./core_state";
|
||||
import { Panel } from "./panel";
|
||||
import { RootFrame } from "./root_frame";
|
||||
|
||||
export interface Props {}
|
||||
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, {}> {
|
||||
constructor(p: Props) {
|
||||
super(p);
|
||||
this.state = init();
|
||||
}
|
||||
|
||||
// TODO: any can be eliminated by adding union type of children states
|
||||
update = (updater: (prevState:ICoreState) => ICoreState): void => {
|
||||
console.log("before", this.state)
|
||||
this.setState(updater(this.state));
|
||||
console.log("after", this.state)
|
||||
update = (apply: (prevState: ICoreState) => ICoreState): void => {
|
||||
this.setState(apply(this.state));
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Panel
|
||||
authPane = {this.state.panel.authPane}
|
||||
displaying={this.state.panel.displaying}
|
||||
update={this.update}
|
||||
browser={this.state.panel.browser}
|
||||
panes={this.state.panel.panes}
|
||||
/>
|
||||
</div>
|
||||
<RootFrame
|
||||
authPane={this.state.panel.authPane}
|
||||
displaying={this.state.panel.displaying}
|
||||
update={this.update}
|
||||
browser={this.state.panel.browser}
|
||||
panes={this.state.panel.panes}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { Response } from "../client";
|
||||
import { ICoreState } from "../components/core_state";
|
||||
|
||||
export const makePromise = (ret: any): Promise<any> => {
|
||||
return new Promise<any>((resolve) => {
|
||||
|
@ -13,3 +14,5 @@ export const makeNumberResponse = (status: number): Promise<Response> => {
|
|||
data: {},
|
||||
});
|
||||
};
|
||||
|
||||
export const mockUpdate = (apply: (prevState: ICoreState) => ICoreState): void => {};
|
||||
|
|
|
@ -14,7 +14,7 @@ export class UploadWorker {
|
|||
private file: File = undefined;
|
||||
private filePath: string = undefined;
|
||||
private uploader: FileUploader = undefined;
|
||||
sendEvent = (resp: FileWorkerResp):void => {
|
||||
sendEvent = (resp: FileWorkerResp): void => {
|
||||
// TODO: make this abstract
|
||||
throw new Error("not implemented");
|
||||
};
|
||||
|
@ -30,6 +30,8 @@ export class UploadWorker {
|
|||
stopUploader = () => {
|
||||
if (this.uploader != null) {
|
||||
this.uploader.stop();
|
||||
this.file = undefined;
|
||||
this.filePath = undefined;
|
||||
}
|
||||
};
|
||||
getFilePath = (): string => {
|
||||
|
@ -49,6 +51,7 @@ export class UploadWorker {
|
|||
// find the first qualified task
|
||||
const syncReq = req as SyncReq;
|
||||
const infoArray = syncReq.infos;
|
||||
|
||||
for (let i = 0; i < infoArray.length; i++) {
|
||||
if (
|
||||
infoArray[i].runnable &&
|
||||
|
@ -59,6 +62,11 @@ export class UploadWorker {
|
|||
this.startUploader(infoArray[i].file, infoArray[i].filePath);
|
||||
}
|
||||
break;
|
||||
} else if (
|
||||
!infoArray[i].runnable &&
|
||||
infoArray[i].filePath == this.filePath
|
||||
) {
|
||||
this.stopUploader();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -8,6 +8,7 @@ const speedDownRatio = 0.5;
|
|||
const speedUpRatio = 1.05;
|
||||
const createRetryLimit = 2;
|
||||
const uploadRetryLimit = 1024;
|
||||
const backoffMax = 2000;
|
||||
|
||||
export interface IFileUploader {
|
||||
stop: () => void;
|
||||
|
@ -71,6 +72,13 @@ export class FileUploader {
|
|||
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> => {
|
||||
let resp: Response;
|
||||
|
||||
|
@ -81,6 +89,7 @@ export class FileUploader {
|
|||
return await this.upload();
|
||||
}
|
||||
} catch (e) {
|
||||
await this.backOff();
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
@ -153,6 +162,8 @@ export class FileUploader {
|
|||
);
|
||||
break;
|
||||
}
|
||||
|
||||
await this.backOff();
|
||||
}
|
||||
} catch (e) {
|
||||
this.errMsgs.push(e.toString());
|
||||
|
|
|
@ -99,6 +99,16 @@ func (fs *LocalFS) translate(name string) (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)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -109,12 +119,6 @@ func (fs *LocalFS) Create(path string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
fs.opensMtx.Lock()
|
||||
defer fs.opensMtx.Unlock()
|
||||
if len(fs.opens) > fs.opensLimit {
|
||||
return ErrTooManyOpens
|
||||
}
|
||||
|
||||
fs.opens[fullpath] = &fileInfo{
|
||||
lastAccess: time.Now(),
|
||||
fd: fd,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue