Skip to content

Commit

Permalink
support stop and delete with go client
Browse files Browse the repository at this point in the history
  • Loading branch information
lonnywong committed Nov 4, 2023
1 parent db94360 commit 58f65c5
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 24 deletions.
4 changes: 4 additions & 0 deletions src/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,10 @@ class BrowserFileWriter implements TrzszFileWriter {
this.closed = true;
}
}

public async deleteFile() {
return "";
}
}

export async function selectSaveDirectory(): Promise<FileSystemDirectoryHandle | undefined> {
Expand Down
13 changes: 13 additions & 0 deletions src/comm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,13 @@ export class TrzszError extends Error {
return this.type === "fail" || this.type === "FAIL";
}

public isStopAndDelete() {
if (this.type !== "fail") {
return false;
}
return this.message === "Stopped and deleted";
}

public static getErrorMessage(err: Error) {
if (err instanceof TrzszError && !err.isTraceBack()) {
return err.message;
Expand Down Expand Up @@ -132,6 +139,7 @@ export interface TrzszFileWriter extends TrzszFile {
getLocalName: () => string;
isDir: () => boolean;
writeFile: (buf: Uint8Array) => Promise<void>;
deleteFile: () => Promise<string>;
}

export type OpenSaveFile = (
Expand Down Expand Up @@ -283,6 +291,11 @@ async function setTmuxStatusInterval(interval: string) {
await exec(`tmux setw status-interval ${interval}`);
}

export async function tmuxRefreshClient() {
const exec = require("util").promisify(require("child_process").exec);
await exec("tmux refresh-client");
}

export function getTerminalColumns() {
return process.stdout.columns;
}
Expand Down
85 changes: 62 additions & 23 deletions src/nodefs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,19 @@ function requireSafely(name: string) {
}
}

function promisify(fs: any, funcs: string[]) {
function promisify1(fs: any, funcs: string[]) {
for (const func of funcs) {
fs[func + "Async"] = (...args: any) => {
return new Promise((resolve) => {
fs[func](...args, (err: Error) => resolve(!err));
});
};
}
}

promisify1(fs, ["rm", "rmdir", "unlink", "access"]);

function promisify2(fs: any, funcs: string[]) {
for (const func of funcs) {
fs[func + "Async"] = (...args: any) => {
return new Promise((resolve, reject) => {
Expand All @@ -33,11 +45,7 @@ function promisify(fs: any, funcs: string[]) {
}
}

promisify(fs, ["stat", "access", "mkdir", "readdir", "close", "open", "realpath", "write"]);

async function fsExists(path: string): Promise<boolean> {
return new Promise((resolve) => fs.access(path, (err: Error) => resolve(!err)));
}
promisify2(fs, ["stat", "mkdir", "readdir", "close", "open", "realpath", "write"]);

async function fsRead(
fd: number,
Expand All @@ -62,16 +70,14 @@ export async function checkPathWritable(filePath: string) {
return false;
}

if (!(await fsExists(filePath))) {
if (!(await fs.accessAsync(filePath))) {
throw new TrzszError(`No such directory: ${filePath}`);
}
const stats = await fs.statAsync(filePath);
if (!stats.isDirectory()) {
throw new TrzszError(`Not a directory: ${filePath}`);
}
try {
await fs.accessAsync(filePath, fs.constants.W_OK);
} catch (err) {
if (!(await fs.accessAsync(filePath, fs.constants.W_OK))) {
throw new TrzszError(`No permission to write: ${filePath}`);
}

Expand Down Expand Up @@ -145,9 +151,7 @@ async function checkPathReadable(
if (!stats.isFile()) {
throw new TrzszError(`Not a regular file: ${absPath}`);
}
try {
await fs.accessAsync(absPath, fs.constants.R_OK);
} catch (err) {
if (!(await fs.accessAsync(absPath, fs.constants.R_OK))) {
throw new TrzszError(`No permission to read: ${absPath}`);
}
fileList.push(new NodefsFileReader(pathId, absPath, relPath, false, stats.size));
Expand Down Expand Up @@ -179,7 +183,7 @@ export async function checkPathsReadable(
const entries = filePaths.entries();
for (const [idx, filePath] of entries) {
const absPath = path.resolve(filePath);
if (!(await fsExists(absPath))) {
if (!(await fs.accessAsync(absPath))) {
throw new TrzszError(`No such file: ${absPath}`);
}
const stats = await fs.statAsync(absPath);
Expand All @@ -193,13 +197,15 @@ export async function checkPathsReadable(
}

class NodefsFileWriter implements TrzszFileWriter {
private absPath: string;
private fileName: string;
private localName: string;
private fd: number | null;
private dir: boolean;
private closed: boolean = false;

constructor(fileName: string, localName: string, fd: number | null, dir: boolean = false) {
constructor(absPath: string, fileName: string, localName: string, fd: number | null, dir: boolean = false) {
this.absPath = absPath;
this.fileName = fileName;
this.localName = localName;
this.fd = fd;
Expand Down Expand Up @@ -231,15 +237,42 @@ class NodefsFileWriter implements TrzszFileWriter {
}
}
}

public async deleteFile() {
if (!this.absPath || !(await fs.accessAsync(this.absPath))) {
return "";
}
try {
await this.closeFile();
if (typeof fs.rm === "function") {
if (await fs.rmAsync(this.absPath, { recursive: true })) {
return this.absPath;
}
} else {
if (this.isDir) {
if (await fs.rmdirAsync(this.absPath, { recursive: true })) {
return this.absPath;
}
} else {
if (await fs.unlinkAsync(this.absPath)) {
return this.absPath;
}
}
}
} catch (err) {
console.log(`delete [${this.absPath}] failed`, err);
}
return "";
}
}

async function getNewName(savePath: string, fileName: string) {
if (!(await fsExists(path.join(savePath, fileName)))) {
if (!(await fs.accessAsync(path.join(savePath, fileName)))) {
return fileName;
}
for (let i = 0; i < 1000; i++) {
const saveName = `${fileName}.${i}`;
if (!(await fsExists(path.join(savePath, saveName)))) {
if (!(await fs.accessAsync(path.join(savePath, saveName)))) {
return saveName;
}
}
Expand All @@ -260,19 +293,22 @@ async function doCreateFile(absPath: string) {
}

async function doCreateDirectory(absPath: string) {
if (!(await fsExists(absPath))) {
if (!(await fs.accessAsync(absPath))) {
await fs.mkdirAsync(absPath, { recursive: true, mode: 0o755 });
return true;
}
const stats = await fs.statAsync(absPath);
if (!stats.isDirectory()) {
throw new TrzszError(`Not a directory: ${absPath}`);
}
return false;
}

async function createFile(savePath: string, fileName: string, overwrite: boolean) {
const localName = overwrite ? fileName : await getNewName(savePath, fileName);
const fd = await doCreateFile(path.join(savePath, localName));
return new NodefsFileWriter(fileName, localName, fd);
const absPath = path.join(savePath, localName);
const fd = await doCreateFile(absPath);
return new NodefsFileWriter(absPath, fileName, localName, fd);
}

export async function openSaveFile(saveParam: any, fileName: string, directory: boolean, overwrite: boolean) {
Expand Down Expand Up @@ -313,10 +349,13 @@ export async function openSaveFile(saveParam: any, fileName: string, directory:
}

if (file.is_dir === true) {
await doCreateDirectory(fullPath);
return new NodefsFileWriter(fileName, localName, null, true);
let absPath = "";
if (await doCreateDirectory(fullPath)) {
absPath = fullPath;
}
return new NodefsFileWriter(absPath, fileName, localName, null, true);
}

const fd = await doCreateFile(fullPath);
return new NodefsFileWriter(fileName, localName, fd);
return new NodefsFileWriter(fullPath, fileName, localName, fd);
}
26 changes: 25 additions & 1 deletion src/transfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
TrzszFileWriter,
ProgressCallback,
stripTmuxStatusLine,
tmuxRefreshClient,
} from "./comm";

/* eslint-disable require-jsdoc */
Expand All @@ -32,6 +33,7 @@ export class TrzszTransfer {
private remoteIsWindows: boolean = false;
private lastInputTime: number = 0;
private openedFiles: TrzszFile[] = [];
private createdFiles: TrzszFileWriter[] = [];
private tmuxOutputJunk: boolean = false;
private cleanTimeoutInMilliseconds: number = 100;
private transferConfig: any = {};
Expand Down Expand Up @@ -161,7 +163,7 @@ export class TrzszTransfer {
return uint8ToStr(decodeBuffer(buf), "utf8");
}

private async checkString(expect: string) {
protected async checkString(expect: string) {
const result = await this.recvString("SUCC");
if (result !== expect) {
throw new TrzszError(`String check [${result}] <> [${expect}]`, null, true);
Expand Down Expand Up @@ -307,6 +309,20 @@ export class TrzszTransfer {
}
process.stdout.write(msg);
process.stdout.write("\r\n");
if (this.transferConfig.tmux_output_junk) {
await tmuxRefreshClient();
}
}

private async deleteCreatedFiles() {
const deletedFiles: string[] = [];
for (const file of this.createdFiles) {
const path = await file.deleteFile();
if (path) {
deletedFiles.push(path);
}
}
return deletedFiles;
}

public async clientError(err: Error) {
Expand Down Expand Up @@ -339,6 +355,13 @@ export class TrzszTransfer {
const errMsg = TrzszError.getErrorMessage(err);
let trace = true;
if (err instanceof TrzszError) {
if (err.isStopAndDelete()) {
const deletedFiles = await this.deleteCreatedFiles();
if (deletedFiles && deletedFiles.length) {
await this.serverExit([err.message + ":"].concat(deletedFiles).join("\r\n- "));
return;
}
}
trace = err.isTraceBack();
if (err.isRemoteExit() || err.isRemoteFail()) {
await this.serverExit(errMsg);
Expand Down Expand Up @@ -486,6 +509,7 @@ export class TrzszTransfer {
) {
const fileName = await this.recvString("NAME");
const file = await openSaveFile(saveParam, fileName, directory, overwrite);
this.createdFiles.push(file);
await this.sendString("SUCC", file.getLocalName());
if (progressCallback) {
progressCallback.onName(file.getFileName());
Expand Down

0 comments on commit 58f65c5

Please sign in to comment.