From 8190bfd07bb21a27c5515151a4c19036473a08c4 Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Sun, 18 Jun 2023 01:00:07 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20writev()=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/fsa-to-node/FsaNodeFs.ts | 34 ++++++++++++ src/fsa-to-node/FsaNodeFsOpenFile.ts | 4 +- src/fsa-to-node/__tests__/FsaNodeFs.test.ts | 24 +++++++++ src/node/types/callback.ts | 58 +++++++++++---------- src/node/types/misc.ts | 1 - src/volume.ts | 10 ++++ 6 files changed, 101 insertions(+), 30 deletions(-) diff --git a/src/fsa-to-node/FsaNodeFs.ts b/src/fsa-to-node/FsaNodeFs.ts index 501d04fcc..ad06512eb 100644 --- a/src/fsa-to-node/FsaNodeFs.ts +++ b/src/fsa-to-node/FsaNodeFs.ts @@ -46,6 +46,7 @@ import type * as misc from '../node/types/misc'; import type * as opts from '../node/types/options'; import type * as fsa from '../fsa/types'; import type { FsCommonObjects } from '../node/types/FsCommonObjects'; +import type {WritevCallback} from '../node/types/callback'; const notSupported: (...args: any[]) => any = () => { throw new Error('Method not supported by the File System Access API.'); @@ -264,6 +265,39 @@ export class FsaNodeFs implements FsCallbackApi, FsSynchronousApi, FsCommonObjec ); }; + public readonly writev: FsCallbackApi['writev'] = ( + fd: number, + buffers: ArrayBufferView[], + a: number | null | WritevCallback, + b?: WritevCallback + ): void => { + validateFd(fd); + let position: number | null = null; + let callback: WritevCallback; + if (typeof a === 'function') { + callback = a; + } else { + position = Number(a); + callback = b; + } + validateCallback(callback); + (async () => { + const openFile = await this.getFileByFd(fd, 'writev'); + const length = buffers.length; + let bytesWritten = 0; + for (let i = 0; i < length; i++) { + const data = buffers[i]; + await openFile.write(data, position); + bytesWritten += data.byteLength; + position = null; + } + return bytesWritten; + })().then( + bytesWritten => callback(null, bytesWritten, buffers), + error => callback(error), + ); + }; + public readonly writeFile: FsCallbackApi['writeFile'] = ( id: misc.TFileId, data: misc.TData, diff --git a/src/fsa-to-node/FsaNodeFsOpenFile.ts b/src/fsa-to-node/FsaNodeFsOpenFile.ts index c3403de7b..f70b340cc 100644 --- a/src/fsa-to-node/FsaNodeFsOpenFile.ts +++ b/src/fsa-to-node/FsaNodeFsOpenFile.ts @@ -28,7 +28,7 @@ export class FsaNodeFsOpenFile { public async close(): Promise {} - public async write(data: Uint8Array, seek: number | null): Promise { + public async write(data: ArrayBufferView, seek: number | null): Promise { if (typeof seek !== 'number') seek = this.seek; const writer = await this.file.createWritable({ keepExistingData: this.keepExistingData }); await writer.write({ @@ -38,6 +38,6 @@ export class FsaNodeFsOpenFile { }); await writer.close(); this.keepExistingData = true; - this.seek += data.length; + this.seek += data.byteLength; } } diff --git a/src/fsa-to-node/__tests__/FsaNodeFs.test.ts b/src/fsa-to-node/__tests__/FsaNodeFs.test.ts index 9f9da28e6..8dd1e0e3f 100644 --- a/src/fsa-to-node/__tests__/FsaNodeFs.test.ts +++ b/src/fsa-to-node/__tests__/FsaNodeFs.test.ts @@ -357,6 +357,30 @@ describe('.write()', () => { }); }); +describe('.writev()', () => { + test('can write to a file two buffers', async () => { + const { fs, mfs } = setup({}); + const fd = await new Promise((resolve, reject) => + fs.open('/test.txt', 'w', (err, fd) => { + if (err) reject(err); + else resolve(fd!); + }), + ); + const [bytesWritten, data] = await new Promise<[number, any]>((resolve, reject) => { + const buffers = [ + Buffer.from('a'), + Buffer.from('b'), + ]; + fs.writev(fd, buffers, (err, bytesWritten, data) => { + if (err) reject(err); + else resolve([bytesWritten!, data]); + }); + }); + expect(bytesWritten).toBe(2); + expect(mfs.readFileSync('/mountpoint/test.txt', 'utf8')).toBe('ab'); + }); +}); + describe('.exists()', () => { test('can works for folders and files', async () => { const { fs, mfs } = setup({ folder: { file: 'test' }, 'empty-folder': null, 'f.html': 'test' }); diff --git a/src/node/types/callback.ts b/src/node/types/callback.ts index 7d5bc1690..38f75ec15 100644 --- a/src/node/types/callback.ts +++ b/src/node/types/callback.ts @@ -15,33 +15,6 @@ export interface FsCallbackApi { ): void; readFile(id: misc.TFileId, callback: misc.TCallback); readFile(id: misc.TFileId, options: opts.IReadFileOptions | string, callback: misc.TCallback); - write(fd: number, buffer: Buffer | ArrayBufferView | DataView, callback: (...args) => void); - write(fd: number, buffer: Buffer | ArrayBufferView | DataView, offset: number, callback: (...args) => void); - write( - fd: number, - buffer: Buffer | ArrayBufferView | DataView, - offset: number, - length: number, - callback: (...args) => void, - ); - write( - fd: number, - buffer: Buffer | ArrayBufferView | DataView, - offset: number, - length: number, - position: number, - callback: (...args) => void, - ); - write(fd: number, str: string, callback: (...args) => void); - write(fd: number, str: string, position: number, callback: (...args) => void); - write(fd: number, str: string, position: number, encoding: BufferEncoding, callback: (...args) => void); - writeFile(id: misc.TFileId, data: misc.TData, callback: misc.TCallback); - writeFile( - id: misc.TFileId, - data: misc.TData, - options: opts.IWriteFileOptions | string, - callback: misc.TCallback, - ); copyFile(src: misc.PathLike, dest: misc.PathLike, callback: misc.TCallback); copyFile(src: misc.PathLike, dest: misc.PathLike, flags: misc.TFlagsCopy, callback: misc.TCallback); link(existingPath: misc.PathLike, newPath: misc.PathLike, callback: misc.TCallback): void; @@ -117,4 +90,35 @@ export interface FsCallbackApi { options?: opts.IWatchOptions | string, listener?: (eventType: string, filename: string) => void, ): misc.IFSWatcher; + write(fd: number, buffer: Buffer | ArrayBufferView | DataView, callback: (...args) => void); + write(fd: number, buffer: Buffer | ArrayBufferView | DataView, offset: number, callback: (...args) => void); + write( + fd: number, + buffer: Buffer | ArrayBufferView | DataView, + offset: number, + length: number, + callback: (...args) => void, + ); + write( + fd: number, + buffer: Buffer | ArrayBufferView | DataView, + offset: number, + length: number, + position: number, + callback: (...args) => void, + ); + write(fd: number, str: string, callback: (...args) => void); + write(fd: number, str: string, position: number, callback: (...args) => void); + write(fd: number, str: string, position: number, encoding: BufferEncoding, callback: (...args) => void); + writeFile(id: misc.TFileId, data: misc.TData, callback: misc.TCallback); + writeFile( + id: misc.TFileId, + data: misc.TData, + options: opts.IWriteFileOptions | string, + callback: misc.TCallback, + ); + writev(fd: number, buffers: ArrayBufferView[], callback: WritevCallback): void; + writev(fd: number, buffers: ArrayBufferView[], position: number | null, callback: WritevCallback): void; } + +export type WritevCallback = (err: Error | null, bytesWritten?: number, buffers?: ArrayBufferView[]) => void; diff --git a/src/node/types/misc.ts b/src/node/types/misc.ts index 5533d6cd8..5d7a6c56e 100644 --- a/src/node/types/misc.ts +++ b/src/node/types/misc.ts @@ -8,7 +8,6 @@ import type { IReadStreamOptions, IStatOptions, IWriteFileOptions, - IWriteStreamOptions, } from './options'; import type { Readable, Writable } from 'stream'; diff --git a/src/volume.ts b/src/volume.ts index ec4a6f675..22c41b7de 100644 --- a/src/volume.ts +++ b/src/volume.ts @@ -50,6 +50,7 @@ import { bufferToEncoding, } from './node/util'; import type { PathLike, symlink } from 'fs'; +import {WritevCallback} from './node/types/callback'; const resolveCrossPlatform = pathModule.resolve; const { @@ -973,6 +974,12 @@ export class Volume { }); } + writev(fd: number, buffers: ArrayBufferView[], callback: WritevCallback): void; + writev(fd: number, buffers: ArrayBufferView[], position: number | null, callback: WritevCallback): void; + writev(fd: number, buffers: ArrayBufferView[], a, b?): void { + throw new Error('not implemented'); + } + private writeFileBase(id: TFileId, buf: Buffer, flagsNum: number, modeNum: number) { // console.log('writeFileBase', id, buf, flagsNum, modeNum); // const node = this.getNodeByIdOrCreate(id, flagsNum, modeNum); @@ -2198,6 +2205,7 @@ function closeOnOpen(fd) { export interface IWriteStream extends Writable { bytesWritten: number; path: string; + pending: boolean; new (path: PathLike, options: IWriteStreamOptions); open(); close(); @@ -2222,6 +2230,7 @@ function FsWriteStream(vol, path, options) { this.autoClose = options.autoClose === undefined ? true : !!options.autoClose; this.pos = undefined; this.bytesWritten = 0; + this.pending = true; if (this.start !== undefined) { if (typeof this.start !== 'number') { @@ -2261,6 +2270,7 @@ FsWriteStream.prototype.open = function () { } this.fd = fd; + this.pending = false; this.emit('open', fd); }.bind(this), );