From af8452e259f03f0330d80ed2caf30f2f9c0833db Mon Sep 17 00:00:00 2001 From: streamich Date: Fri, 16 Jun 2023 20:10:13 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20implement=20truncation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/fsa-to-node/FsaNodeFs.ts | 95 +++++++++------------ src/fsa-to-node/__tests__/FsaNodeFs.test.ts | 23 +++++ src/node/types/callback.ts | 4 +- src/volume.ts | 4 + 4 files changed, 70 insertions(+), 56 deletions(-) diff --git a/src/fsa-to-node/FsaNodeFs.ts b/src/fsa-to-node/FsaNodeFs.ts index 55b11163d..56a3037a8 100644 --- a/src/fsa-to-node/FsaNodeFs.ts +++ b/src/fsa-to-node/FsaNodeFs.ts @@ -29,9 +29,9 @@ import type * as misc from '../node/types/misc'; import type * as opts from '../node/types/options'; import type * as fsa from '../fsa/types'; -// const notImplemented: (...args: unknown[]) => unknown = () => { -// throw new Error('Not implemented'); -// }; +const notImplemented: (...args: any[]) => any = () => { + throw new Error('Not implemented'); +}; /** * Constructs a Node.js `fs` API from a File System Access API @@ -316,25 +316,36 @@ export class FsaNodeFs implements FsCallbackApi { throw new Error('Not implemented'); } - ftruncate(fd: number, callback: misc.TCallback); - ftruncate(fd: number, len: number, callback: misc.TCallback); - ftruncate(fd: number, a: misc.TCallback | number, b?: misc.TCallback) { - throw new Error('Not implemented'); - } + public readonly ftruncate: FsCallbackApi['ftruncate'] = (fd: number, a: misc.TCallback | number, b?: misc.TCallback): void => { + const len: number = typeof a === 'number' ? a : 0; + const callback: misc.TCallback = validateCallback(typeof a === 'number' ? b : a); + this.getFileByFd(fd) + .then(file => file.file.createWritable({keepExistingData: true})) + .then(writable => writable.truncate(len).then(() => writable.close())) + .then(() => callback(null), error => callback(error)); + }; - truncate(id: misc.TFileId, callback: misc.TCallback); - truncate(id: misc.TFileId, len: number, callback: misc.TCallback); - truncate(id: misc.TFileId, a: misc.TCallback | number, b?: misc.TCallback) { - throw new Error('Not implemented'); - } + public readonly truncate: FsCallbackApi['truncate'] = (path: misc.PathLike, a: misc.TCallback | number, b?: misc.TCallback) => { + const len: number = typeof a === 'number' ? a : 0; + const callback: misc.TCallback = validateCallback(typeof a === 'number' ? b : a); + this.open(path, 'r+', (error, fd) => { + if (error) callback(error); + else { + this.ftruncate(fd!, len, error => { + if (error) this.close(fd!, () => callback(error)); + else this.close(fd!, callback); + }); + } + }); + }; - futimes(fd: number, atime: misc.TTime, mtime: misc.TTime, callback: misc.TCallback): void { - throw new Error('Not implemented'); - } + public readonly futimes: FsCallbackApi['futimes'] = (fd: number, atime: misc.TTime, mtime: misc.TTime, callback: misc.TCallback): void => { + callback(null); + }; - utimes(path: misc.PathLike, atime: misc.TTime, mtime: misc.TTime, callback: misc.TCallback): void { - throw new Error('Not implemented'); - } + public readonly utimes: FsCallbackApi['utimes'] = (path: misc.PathLike, atime: misc.TTime, mtime: misc.TTime, callback: misc.TCallback): void => { + callback(null); + }; public readonly mkdir: FsCallbackApi['mkdir'] = ( path: misc.PathLike, @@ -451,56 +462,32 @@ export class FsaNodeFs implements FsCallbackApi { }; fchmod(fd: number, mode: misc.TMode, callback: misc.TCallback): void { - throw new Error('Not implemented'); + callback(null); } chmod(path: misc.PathLike, mode: misc.TMode, callback: misc.TCallback): void { - throw new Error('Not implemented'); + callback(null); } lchmod(path: misc.PathLike, mode: misc.TMode, callback: misc.TCallback): void { - throw new Error('Not implemented'); + callback(null); } fchown(fd: number, uid: number, gid: number, callback: misc.TCallback): void { - throw new Error('Not implemented'); + callback(null); } chown(path: misc.PathLike, uid: number, gid: number, callback: misc.TCallback): void { - throw new Error('Not implemented'); + callback(null); } lchown(path: misc.PathLike, uid: number, gid: number, callback: misc.TCallback): void { - throw new Error('Not implemented'); - } - - watchFile(path: misc.PathLike, listener: (curr: misc.IStats, prev: misc.IStats) => void): misc.IStatWatcher; - watchFile( - path: misc.PathLike, - options: opts.IWatchFileOptions, - listener: (curr: misc.IStats, prev: misc.IStats) => void, - ): misc.IStatWatcher; - watchFile(path: misc.PathLike, a, b?): misc.IStatWatcher { - throw new Error('Not implemented'); + callback(null); } - unwatchFile(path: misc.PathLike, listener?: (curr: misc.IStats, prev: misc.IStats) => void): void { - throw new Error('Not implemented'); - } - - createReadStream(path: misc.PathLike, options?: opts.IReadStreamOptions | string): misc.IReadStream { - throw new Error('Not implemented'); - } - - createWriteStream(path: misc.PathLike, options?: opts.IWriteStreamOptions | string): misc.IWriteStream { - throw new Error('Not implemented'); - } - - watch( - path: misc.PathLike, - options?: opts.IWatchOptions | string, - listener?: (eventType: string, filename: string) => void, - ): misc.IFSWatcher { - throw new Error('Not implemented'); - } + public readonly watchFile: FsCallbackApi['watchFile'] = notImplemented; + public readonly unwatchFile: FsCallbackApi['unwatchFile'] = notImplemented; + public readonly createReadStream: FsCallbackApi['createReadStream'] = notImplemented; + public readonly createWriteStream: FsCallbackApi['createWriteStream'] = notImplemented; + public readonly watch: FsCallbackApi['watch'] = notImplemented; } diff --git a/src/fsa-to-node/__tests__/FsaNodeFs.test.ts b/src/fsa-to-node/__tests__/FsaNodeFs.test.ts index 565a020f8..0c09a6a54 100644 --- a/src/fsa-to-node/__tests__/FsaNodeFs.test.ts +++ b/src/fsa-to-node/__tests__/FsaNodeFs.test.ts @@ -239,3 +239,26 @@ describe('.readFile()', () => { } }); }); + +describe('.truncate()', () => { + test('can truncate a file', async () => { + const { fs, mfs } = setup({ folder: { file: 'test' }, 'empty-folder': null }); + const res = await new Promise((resolve, reject) => { + fs.truncate('/folder/file', 2, (err, res) => err ? reject(err) : resolve(res)); + }); + expect(res).toBe(undefined); + expect(mfs.readFileSync('/mountpoint/folder/file', 'utf8')).toBe('te'); + }); +}); + +describe('.ftruncate()', () => { + test('can truncate a file', async () => { + const { fs, mfs } = setup({ folder: { file: 'test' }, 'empty-folder': null }); + const handle = await fs.promises.open('/folder/file'); + const res = await new Promise((resolve, reject) => { + fs.ftruncate(handle.fd, 3, (err, res) => err ? reject(err) : resolve(res)); + }); + expect(res).toBe(undefined); + expect(mfs.readFileSync('/mountpoint/folder/file', 'utf8')).toBe('tes'); + }); +}); diff --git a/src/node/types/callback.ts b/src/node/types/callback.ts index 59591150a..28e87100f 100644 --- a/src/node/types/callback.ts +++ b/src/node/types/callback.ts @@ -80,8 +80,8 @@ export interface FsCallbackApi { fdatasync(fd: number, callback: misc.TCallback): void; ftruncate(fd: number, callback: misc.TCallback); ftruncate(fd: number, len: number, callback: misc.TCallback); - truncate(id: misc.TFileId, callback: misc.TCallback); - truncate(id: misc.TFileId, len: number, callback: misc.TCallback); + truncate(id: misc.PathLike, callback: misc.TCallback); + truncate(id: misc.PathLike, len: number, callback: misc.TCallback); futimes(fd: number, atime: misc.TTime, mtime: misc.TTime, callback: misc.TCallback): void; utimes(path: misc.PathLike, atime: misc.TTime, mtime: misc.TTime, callback: misc.TCallback): void; mkdir(path: misc.PathLike, callback: misc.TCallback); diff --git a/src/volume.ts b/src/volume.ts index 6a6df23bc..dbeca60d3 100644 --- a/src/volume.ts +++ b/src/volume.ts @@ -1634,6 +1634,10 @@ export class Volume { } } + /** + * `id` should be a file descriptor or a path. `id` as file descriptor will + * not be supported soon. + */ truncateSync(id: TFileId, len?: number) { if (isFd(id)) return this.ftruncateSync(id as number, len);