From e147d5850870cddf1c64bca747e6708676317817 Mon Sep 17 00:00:00 2001 From: streamich Date: Sat, 17 Jun 2023 20:02:35 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20lstat()=20and=20fs?= =?UTF-8?q?tat()=20methods?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/fsa-to-node/FsaNodeFs.ts | 47 +++++++++++++-------- src/fsa-to-node/__tests__/FsaNodeFs.test.ts | 47 ++++++++++++++++++++- 2 files changed, 75 insertions(+), 19 deletions(-) diff --git a/src/fsa-to-node/FsaNodeFs.ts b/src/fsa-to-node/FsaNodeFs.ts index c080f98e6..396c6ade9 100644 --- a/src/fsa-to-node/FsaNodeFs.ts +++ b/src/fsa-to-node/FsaNodeFs.ts @@ -300,15 +300,13 @@ export class FsaNodeFs implements FsCallbackApi, FsCommonObjects { throw new Error('Not implemented'); } - lstat(path: misc.PathLike, callback: misc.TCallback): void; - lstat(path: misc.PathLike, options: opts.IStatOptions, callback: misc.TCallback): void; - lstat( + public readonly lstat: FsCallbackApi['lstat'] = ( path: misc.PathLike, a: misc.TCallback | opts.IStatOptions, b?: misc.TCallback, - ): void { - throw new Error('Not implemented'); - } + ): void => { + this.stat(path, a, b); + }; public readonly stat: FsCallbackApi['stat'] = ( path: misc.PathLike, @@ -320,24 +318,37 @@ export class FsaNodeFs implements FsCallbackApi, FsCommonObjects { const [folder, name] = pathToLocation(filename); (async () => { const handle = await this.getFileOrDir(folder, name, 'stat'); - let size: number = 0; - if (handle.kind === 'file') { - const file = handle; - const fileData = await file.getFile(); - size = fileData.size; - } - const stats = new FsaNodeStats(bigint, bigint ? BigInt(size) : size, handle); - return stats; + return await this.getHandleStats(bigint, handle); })().then( stats => callback(null, stats), error => callback(error), ); }; - fstat(fd: number, callback: misc.TCallback): void; - fstat(fd: number, options: opts.IFStatOptions, callback: misc.TCallback): void; - fstat(fd: number, a: misc.TCallback | opts.IFStatOptions, b?: misc.TCallback): void { - throw new Error('Not implemented'); + public readonly fstat: FsCallbackApi['fstat'] = ( + fd: number, + a: misc.TCallback | opts.IStatOptions, + b?: misc.TCallback, + ): void => { + const [{ bigint = false, throwIfNoEntry = true }, callback] = getStatOptsAndCb(a, b); + (async () => { + const openFile = await this.getFileByFd(fd, 'fstat'); + return await this.getHandleStats(bigint, openFile.file); + })().then( + stats => callback(null, stats), + error => callback(error), + ); + }; + + private async getHandleStats(bigint: boolean, handle: fsa.IFileSystemHandle): Promise { + let size: number = 0; + if (handle.kind === 'file') { + const file = handle; + const fileData = await file.getFile(); + size = fileData.size; + } + const stats = new FsaNodeStats(bigint, bigint ? BigInt(size) : size, handle); + return stats; } public readonly rename: FsCallbackApi['rename'] = ( diff --git a/src/fsa-to-node/__tests__/FsaNodeFs.test.ts b/src/fsa-to-node/__tests__/FsaNodeFs.test.ts index 316e0bda5..74b8f8fce 100644 --- a/src/fsa-to-node/__tests__/FsaNodeFs.test.ts +++ b/src/fsa-to-node/__tests__/FsaNodeFs.test.ts @@ -1,7 +1,7 @@ import { IFsWithVolume, NestedDirectoryJSON, memfs } from '../..'; import { AMODE } from '../../consts/AMODE'; import { nodeToFsa } from '../../node-to-fsa'; -import { IDirent } from '../../node/types/misc'; +import { IDirent, IStats } from '../../node/types/misc'; import { FsaNodeFs } from '../FsaNodeFs'; const setup = (json: NestedDirectoryJSON | null = null, mode: 'read' | 'readwrite' = 'readwrite') => { @@ -494,3 +494,48 @@ describe('.stat()', () => { } }); }); + +describe('.lstat()', () => { + test('can stat a file', async () => { + const { fs, mfs } = setup({ folder: { file: 'test' }, 'empty-folder': null, 'f.html': 'test' }); + const stats = await fs.promises.lstat('/folder/file'); + expect(stats.isFile()).toBe(true); + }); + + test('can retrieve file size', async () => { + const { fs, mfs } = setup({ folder: { file: 'test' }, 'empty-folder': null, 'f.html': 'test' }); + const stats = await fs.promises.lstat('/folder/file'); + expect(stats.size).toBe(4); + }); + + test('can stat a folder', async () => { + const { fs, mfs } = setup({ folder: { file: 'test' }, 'empty-folder': null, 'f.html': 'test' }); + const stats = await fs.promises.lstat('/folder'); + expect(stats.isFile()).toBe(false); + expect(stats.isDirectory()).toBe(true); + }); + + test('throws on non-existing path', async () => { + const { fs } = setup({ folder: { file: 'test' }, 'empty-folder': null, 'f.html': 'test' }); + try { + await fs.promises.lstat('/folder/abc'); + throw new Error('should not be here'); + } catch (error) { + expect(error.code).toBe('ENOENT'); + } + }); +}); + +describe('.fstat()', () => { + test('can stat a file', async () => { + const { fs } = setup({ folder: { file: 'test' }, 'empty-folder': null, 'f.html': 'test' }); + const handle = await fs.promises.open('/folder/file', 'r'); + const stats = await new Promise((resolve, reject) => { + fs.fstat(handle.fd, (error, stats) => { + if (error) reject(error); + else resolve(stats!); + }); + }); + expect(stats.isFile()).toBe(true); + }); +});