diff --git a/.gitignore b/.gitignore index e364de0..1377fd7 100644 --- a/.gitignore +++ b/.gitignore @@ -107,3 +107,4 @@ dist lib/ *.swp *.swo +/temp \ No newline at end of file diff --git a/src/filter.ts b/src/filter.ts index 153523e..5314e8d 100644 --- a/src/filter.ts +++ b/src/filter.ts @@ -204,7 +204,7 @@ export class TrzszFilter { if (items instanceof DataTransferItemList) { this.uploadFilesList = await parseDataTransferItemList(items as DataTransferItemList); } else if (isArrayOfType(items, "string") && !isRunningInBrowser) { - this.uploadFilesList = nodefs.checkPathsReadable(items as string[], true); + this.uploadFilesList = await nodefs.checkPathsReadable(items as string[], true); } else { throw new Error("The upload items type is not supported"); } @@ -342,7 +342,7 @@ export class TrzszFilter { await this.trzszTransfer.sendAction(false, remoteIsWindows); return; } - nodefs.checkPathWritable(savePath); + await nodefs.checkPathWritable(savePath); saveParam = { path: savePath, maps: new Map() }; openSaveFile = nodefs.openSaveFile; } @@ -368,7 +368,7 @@ export class TrzszFilter { sendFiles = directory ? await browser.selectSendDirectories() : await browser.selectSendFiles(); } else { const filePaths = await this.chooseSendFiles(directory); - sendFiles = nodefs.checkPathsReadable(filePaths, directory); + sendFiles = await nodefs.checkPathsReadable(filePaths, directory); } if (!sendFiles || !sendFiles.length) { diff --git a/src/nodefs.ts b/src/nodefs.ts index 6d1bea0..6d63c1c 100644 --- a/src/nodefs.ts +++ b/src/nodefs.ts @@ -5,7 +5,6 @@ */ /* eslint-disable require-jsdoc */ - const fs = requireSafely("fs"); const path = requireSafely("path"); import { TrzszError, TrzszFileReader, TrzszFileWriter } from "./comm"; @@ -18,20 +17,57 @@ function requireSafely(name) { } } -export function checkPathWritable(filePath: string) { +function promisify (fs: any, funcs: string[]) { + for (const func of funcs) { + fs[func + 'Async'] = (...args) => { + return new Promise((resolve, reject) => { + fs[func](...args, (err, data) => { + if (err) { + return reject(err); + } else { + resolve(data || true); + } + }) + }) + } + } +} + +promisify( + fs, + [ + 'stat', + 'access', + 'mkdir', + 'readdir', + 'read', + 'close', + 'open', + 'realpath', + 'write' + ] +); + +function fsExistsAsync (fp: string) { + return fs.accessAsync(fp) + .then(() => true) + .catch(() => false) +} + +export async function checkPathWritable(filePath: string) { if (!filePath) { return false; } - if (!fs.existsSync(filePath)) { + if (!await fsExistsAsync(filePath)) { throw new TrzszError(`No such directory: ${filePath}`); } - const stats = fs.statSync(filePath); + const stats = await fs.statAsync(filePath); if (!stats.isDirectory()) { throw new TrzszError(`Not a directory: ${filePath}`); } try { - fs.accessSync(filePath, fs.constants.W_OK); + await fs.accessAsync(filePath, fs.constants.W_OK); } catch (err) { throw new TrzszError(`No permission to write: ${filePath}`); } @@ -77,25 +113,25 @@ class NodefsFileReader implements TrzszFileReader { throw new TrzszError(`File closed: ${this.absPath}`, null, true); } if (this.fd === null) { - this.fd = fs.openSync(this.absPath, "r"); + this.fd = await fs.openAsync(this.absPath, "r"); } const uint8 = new Uint8Array(buf); - const n = fs.readSync(this.fd, uint8, 0, uint8.length, null); + const n = await fs.readAsync(this.fd, uint8, 0, uint8.length, null); return uint8.subarray(0, n); } - public closeFile() { + public async closeFile() { if (!this.closed) { this.closed = true; if (this.fd !== null) { - fs.closeSync(this.fd); + await fs.closeAsync(this.fd); this.fd = null; } } } } -function checkPathReadable( +async function checkPathReadable( pathId: number, absPath: string, stats: any, @@ -108,7 +144,7 @@ function checkPathReadable( throw new TrzszError(`Not a regular file: ${absPath}`); } try { - fs.accessSync(absPath, fs.constants.R_OK); + await fs.accessAsync(absPath, fs.constants.R_OK); } catch (err) { throw new TrzszError(`No permission to read: ${absPath}`); } @@ -116,38 +152,40 @@ function checkPathReadable( return; } - const realPath = fs.realpathSync(absPath); + const realPath = await fs.realpathAsync(absPath); if (visitedDir.has(realPath)) { throw new TrzszError(`Duplicate link: ${absPath}`); } visitedDir.add(realPath); fileList.push(new NodefsFileReader(pathId, absPath, relPath, true, 0)); - - fs.readdirSync(absPath).forEach((file) => { + const arr = await fs.readdirAsync(absPath) + for (const file of arr) { const filePath = path.join(absPath, file); - checkPathReadable(pathId, filePath, fs.statSync(filePath), fileList, [...relPath, file], visitedDir); - }); + const stat = await fs.statAsync(filePath) + await checkPathReadable(pathId, filePath, stat, fileList, [...relPath, file], visitedDir); + } } -export function checkPathsReadable( +export async function checkPathsReadable( filePaths: string[] | undefined, directory: boolean = false -): TrzszFileReader[] | undefined { +): Promise { if (!filePaths || !filePaths.length) { return undefined; } const fileList: NodefsFileReader[] = []; - for (const [idx, filePath] of filePaths.entries()) { + const entries = filePaths.entries() + for (const [idx, filePath] of entries) { const absPath = path.resolve(filePath); - if (!fs.existsSync(absPath)) { + if (!await fsExistsAsync(absPath)) { throw new TrzszError(`No such file: ${absPath}`); } - const stats = fs.statSync(absPath); + const stats = await fs.statAsync(absPath); if (!directory && stats.isDirectory()) { throw new TrzszError(`Is a directory: ${absPath}`); } const visitedDir = new Set(); - checkPathReadable(idx, absPath, stats, fileList, [path.basename(absPath)], visitedDir); + await checkPathReadable(idx, absPath, stats, fileList, [path.basename(absPath)], visitedDir); } return fileList; } @@ -179,27 +217,27 @@ class NodefsFileWriter implements TrzszFileWriter { } public async writeFile(buf: Uint8Array) { - fs.writeSync(this.fd, buf); + await fs.writeAsync(this.fd, buf); } - public closeFile() { + public async closeFile() { if (!this.closed) { this.closed = true; if (this.fd !== null) { - fs.closeSync(this.fd); + await fs.closeAsync(this.fd); this.fd = null; } } } } -function getNewName(savePath: string, fileName: string) { - if (!fs.existsSync(path.join(savePath, fileName))) { +async function getNewName(savePath: string, fileName: string) { + if (!fsExistsAsync(path.join(savePath, fileName))) { return fileName; } for (let i = 0; i < 1000; i++) { const saveName = `${fileName}.${i}`; - if (!fs.existsSync(path.join(savePath, saveName))) { + if (!await fsExistsAsync(path.join(savePath, saveName))) { return saveName; } } @@ -208,7 +246,7 @@ function getNewName(savePath: string, fileName: string) { function doCreateFile(absPath: string) { try { - return fs.openSync(absPath, "w"); + return fs.openAsync(absPath, "w"); } catch (err) { if (err.errno === -13 || err.errno === -4048) { throw new TrzszError(`No permission to write: ${absPath}`); @@ -219,19 +257,19 @@ function doCreateFile(absPath: string) { } } -function doCreateDirectory(absPath: string) { - if (!fs.existsSync(absPath)) { - fs.mkdirSync(absPath, { recursive: true, mode: 0o755 }); +async function doCreateDirectory(absPath: string) { + if (!await fsExistsAsync(absPath)) { + await fs.mkdirAsync(absPath, { recursive: true, mode: 0o755 }); } - const stats = fs.statSync(absPath); + const stats = await fs.statAsync(absPath); if (!stats.isDirectory()) { throw new TrzszError(`Not a directory: ${absPath}`); } } -function createFile(savePath, fileName: string, overwrite: boolean) { - const localName = overwrite ? fileName : getNewName(savePath, fileName); - const fd = doCreateFile(path.join(savePath, localName)); +async function createFile(savePath, 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); } @@ -258,7 +296,7 @@ export async function openSaveFile(saveParam: any, fileName: string, directory: if (saveParam.maps.has(file.path_id)) { localName = saveParam.maps.get(file.path_id); } else { - localName = getNewName(saveParam.path, file.path_name[0]); + localName = await getNewName(saveParam.path, file.path_name[0]); saveParam.maps.set(file.path_id, localName); } } @@ -266,17 +304,17 @@ export async function openSaveFile(saveParam: any, fileName: string, directory: let fullPath: string; if (file.path_name.length > 1) { const p = path.join(saveParam.path, localName, ...file.path_name.slice(1, file.path_name.length - 1)); - doCreateDirectory(p); + await doCreateDirectory(p); fullPath = path.join(p, fileName); } else { fullPath = path.join(saveParam.path, localName); } if (file.is_dir === true) { - doCreateDirectory(fullPath); + await doCreateDirectory(fullPath); return new NodefsFileWriter(fileName, localName, null, true); } - const fd = doCreateFile(fullPath); + const fd = await doCreateFile(fullPath); return new NodefsFileWriter(fileName, localName, fd); } diff --git a/src/trz.ts b/src/trz.ts index febd8aa..b643b60 100644 --- a/src/trz.ts +++ b/src/trz.ts @@ -108,7 +108,7 @@ async function main() { try { args.path = path.resolve(args.path); - nodefs.checkPathWritable(args.path); + await nodefs.checkPathWritable(args.path); const [tmuxMode, realStdoutWriter, tmuxPaneWidth] = await checkTmux(); diff --git a/src/tsz.ts b/src/tsz.ts index a11505b..b996bc4 100644 --- a/src/tsz.ts +++ b/src/tsz.ts @@ -110,7 +110,7 @@ async function main() { const args = parseArgs(); try { - const fileList = checkPathsReadable(args.file, args.directory); + const fileList = await checkPathsReadable(args.file, args.directory); if (!fileList) { return; } diff --git a/test/nodefs.test.ts b/test/nodefs.test.ts index 3733f28..c9c95ec 100644 --- a/test/nodefs.test.ts +++ b/test/nodefs.test.ts @@ -9,6 +9,7 @@ const fs = require("fs"); const path = require("path"); import { strToUint8 } from "../src/comm"; import { checkPathWritable, checkPathsReadable, openSaveFile } from "../src/nodefs"; +import { TrzszFileReader } from "../src/comm"; let tmpDir: string; let tmpFile: string; @@ -50,13 +51,13 @@ test("require fs and path", () => { require("../src/nodefs"); }); -test("check paths readable", () => { +test("check paths readable", async () => { expect(checkPathsReadable(undefined)).toBe(undefined); expect(checkPathsReadable([])).toBe(undefined); fs.chmodSync(tmpFile, 0o444); - expect(checkPathsReadable([tmpFile]).length).toBe(1); - expect(checkPathsReadable([linkPath]).length).toBe(1); + expect(((await checkPathsReadable([tmpFile])) as TrzszFileReader[]).length).toBe(1); + expect(((await checkPathsReadable([linkPath])) as TrzszFileReader[]).length).toBe(1); expect(() => checkPathsReadable([notExistFile])).toThrowError("No such file"); expect(() => checkPathsReadable([tmpFile, notExistFile])).toThrowError("No such file"); @@ -74,12 +75,12 @@ test("check paths readable", () => { } }); -test("check path writable", () => { - expect(checkPathWritable(undefined)).toBe(false); - expect(checkPathWritable(null)).toBe(false); +test("check path writable", async () => { + expect(await checkPathWritable(undefined)).toBe(false); + expect(await checkPathWritable(null)).toBe(false); fs.chmodSync(tmpDir, 0o777); - expect(checkPathWritable(tmpDir)).toBe(true); + expect(await checkPathWritable(tmpDir)).toBe(true); expect(() => checkPathWritable(notExistFile)).toThrowError("No such directory"); expect(() => checkPathWritable(tmpFile)).toThrowError("Not a directory"); @@ -97,7 +98,7 @@ test("open send files success", async () => { fs.writeSync(fd, "test file content"); fs.closeSync(fd); - const tfr = checkPathsReadable([testPath])[0]; + const tfr = await checkPathsReadable([testPath])[0]; expect(tfr.getPathId()).toBe(0); expect(tfr.getRelPath()).toStrictEqual(["send.txt"]); @@ -113,7 +114,7 @@ test("open send files success", async () => { expect(await tfr.readFile(buf)).toStrictEqual(strToUint8("")); tfr.closeFile(); - await expect(tfr.readFile(buf)).rejects.toThrowError("File closed"); + await expect(await tfr.readFile(buf)).rejects.toThrowError("File closed"); }); test("open send files error", async () => { @@ -147,7 +148,7 @@ test("open save file success", async () => { const existsSync = fs.existsSync; fs.existsSync = jest.fn(); fs.existsSync.mockReturnValue(true); - await expect(openSaveFile(saveParam, "save.txt", false, false)).rejects.toThrowError("Fail to assign new file name"); + await expect(await openSaveFile(saveParam, "save.txt", false, false)).rejects.toThrowError("Fail to assign new file name"); fs.existsSync = existsSync; }); @@ -160,14 +161,14 @@ test("open save file error", async () => { } fs.mkdirSync(path.join(tmpDir, "isdir")); - await expect(openSaveFile(saveParam, "isdir", false, true)).rejects.toThrowError("Is a directory"); + await expect(await openSaveFile(saveParam, "isdir", false, true)).rejects.toThrowError("Is a directory"); const openSync = fs.openSync; fs.openSync = jest.fn(); fs.openSync.mockImplementation(() => { throw new Error("other error"); }); - await expect(openSaveFile(saveParam, "other.txt", false, false)).rejects.toThrowError("other error"); + await expect(await openSaveFile(saveParam, "other.txt", false, false)).rejects.toThrowError("other error"); fs.openSync = openSync; }); @@ -179,7 +180,7 @@ test("open directory success", async () => { fs.writeSync(fd, "test file content"); fs.closeSync(fd); - const fileList = checkPathsReadable([testDir], true); + const fileList = await checkPathsReadable([testDir], true) as TrzszFileReader[]; expect(fileList[0].getPathId()).toBe(0); expect(fileList[0].getRelPath()).toStrictEqual(["testdir"]); expect(fileList[0].isDir()).toBe(true);