Skip to content

Commit

Permalink
feat: 🎸 start synchronous file handle implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
streamich committed Jun 20, 2023
1 parent 361812d commit f9b0f73
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 6 deletions.
11 changes: 8 additions & 3 deletions src/node-to-fsa/NodeFileSystemFileHandle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@ import { NodeFileSystemWritableFileStream } from './NodeFileSystemWritableFileSt
import type { NodeFsaContext, NodeFsaFs } from './types';

export class NodeFileSystemFileHandle extends NodeFileSystemHandle {
protected readonly ctx: NodeFsaContext;

constructor(
protected readonly fs: NodeFsaFs,
public readonly __path: string,
protected readonly ctx: Partial<NodeFsaContext> = createCtx(ctx),
ctx: Partial<NodeFsaContext> = {},
) {
ctx = createCtx(ctx);
super('file', basename(__path, ctx.separator!));
this.ctx = ctx as NodeFsaContext;
}

/**
Expand Down Expand Up @@ -43,8 +47,9 @@ export class NodeFileSystemFileHandle extends NodeFileSystemHandle {
/**
* @see https://developer.mozilla.org/en-US/docs/Web/API/FileSystemFileHandle/createSyncAccessHandle
*/
public async createSyncAccessHandle(): Promise<NodeFileSystemSyncAccessHandle> {
throw new Error('Not implemented');
public get createSyncAccessHandle(): undefined | (() => Promise<NodeFileSystemSyncAccessHandle>) {
if (!this.ctx.syncHandleAllowed) return undefined;
return async () => new NodeFileSystemSyncAccessHandle(this.fs, this.__path, this.ctx);
}

/**
Expand Down
54 changes: 53 additions & 1 deletion src/node-to-fsa/NodeFileSystemSyncAccessHandle.ts
Original file line number Diff line number Diff line change
@@ -1 +1,53 @@
export class NodeFileSystemSyncAccessHandle {}
import type {NodeFsaContext, NodeFsaFs} from "./types";

/**
* @see https://developer.mozilla.org/en-US/docs/Web/API/FileSystemSyncAccessHandle
*/
export class NodeFileSystemSyncAccessHandle {
protected readonly fd: number;

constructor(
protected readonly fs: NodeFsaFs,
protected readonly path: string,
protected readonly ctx: NodeFsaContext,
) {
this.fd = fs.openSync(path, 'r+');
}

/**
* @see https://developer.mozilla.org/en-US/docs/Web/API/FileSystemSyncAccessHandle/close
*/
public async close(): Promise<void> {
this.fs.closeSync(this.fd);
}

/**
* @see https://developer.mozilla.org/en-US/docs/Web/API/FileSystemSyncAccessHandle/flush
*/
public async flush(): Promise<void> {
this.fs.fsyncSync(this.fd);
}

/**
* @see https://developer.mozilla.org/en-US/docs/Web/API/FileSystemSyncAccessHandle/getSize
*/
public async getSize(): Promise<number> {
return this.fs.statSync(this.path).size;
}

/**
* @see https://developer.mozilla.org/en-US/docs/Web/API/FileSystemSyncAccessHandle/read
*/
public async read(buffer: ArrayBuffer | ArrayBufferView, options: FileSystemReadWriteOptions = {}): Promise<number> {
const buf: Buffer | ArrayBufferView = buffer instanceof ArrayBuffer ? Buffer.from(buffer) : buffer;
const size = this.fs.readSync(this.fd, buf, 0, buffer.byteLength, options.at || 0);
return size;
}
}

export interface FileSystemReadWriteOptions {
/**
* A number representing the offset in bytes that the file should be read from.
*/
at?: number;
}
98 changes: 98 additions & 0 deletions src/node-to-fsa/__tests__/NodeFileSystemSyncAccessHandle.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { DirectoryJSON, memfs } from '../..';
import { NodeFileSystemDirectoryHandle } from '../NodeFileSystemDirectoryHandle';
import {NodeFileSystemSyncAccessHandle} from '../NodeFileSystemSyncAccessHandle';
import { maybe } from './util';

const setup = (json: DirectoryJSON = {}) => {
const fs = memfs(json, '/');
const dir = new NodeFileSystemDirectoryHandle(fs as any, '/', {syncHandleAllowed: true});
return { dir, fs };
};

maybe('NodeFileSystemSyncAccessHandle', () => {
describe('.close()', () => {
test('can close the file', async () => {
const { dir } = setup({
'file.txt': 'Hello, world!',
});
const entry = await dir.getFileHandle('file.txt');
const sync = await entry.createSyncAccessHandle!();
expect(sync).toBeInstanceOf(NodeFileSystemSyncAccessHandle);
await sync.close();
// ...
});
});

describe('.flush()', () => {
test('can flush', async () => {
const { dir } = setup({
'file.txt': 'Hello, world!',
});
const entry = await dir.getFileHandle('file.txt');
const sync = await entry.createSyncAccessHandle!();
await sync.flush();
});
});

describe('.getSize()', () => {
test('can get file size', async () => {
const { dir } = setup({
'file.txt': 'Hello, world!',
});
const entry = await dir.getFileHandle('file.txt');
const sync = await entry.createSyncAccessHandle!();
const size = await sync.getSize();
expect(size).toBe(13);
});
});

describe('.getSize()', () => {
test('can get file size', async () => {
const { dir } = setup({
'file.txt': 'Hello, world!',
});
const entry = await dir.getFileHandle('file.txt');
const sync = await entry.createSyncAccessHandle!();
const size = await sync.getSize();
expect(size).toBe(13);
});
});

describe('.read()', () => {
test('can read from beginning', async () => {
const { dir } = setup({
'file.txt': '0123456789',
});
const entry = await dir.getFileHandle('file.txt');
const sync = await entry.createSyncAccessHandle!();
const buf = new Uint8Array(5);
const size = await sync.read(buf);
expect(size).toBe(5);
expect(Buffer.from(buf).toString()).toBe('01234');
});

test('can read at offset', async () => {
const { dir } = setup({
'file.txt': '0123456789',
});
const entry = await dir.getFileHandle('file.txt');
const sync = await entry.createSyncAccessHandle!();
const buf = new Uint8Array(3);
const size = await sync.read(buf, {at: 3});
expect(size).toBe(3);
expect(Buffer.from(buf).toString()).toBe('345');
});

test('can read into buffer larger than file', async () => {
const { dir } = setup({
'file.txt': '0123456789',
});
const entry = await dir.getFileHandle('file.txt');
const sync = await entry.createSyncAccessHandle!();
const buf = new Uint8Array(25);
const size = await sync.read(buf);
expect(size).toBe(10);
expect(Buffer.from(buf).slice(0, 10).toString()).toBe('0123456789');
});
});
});
4 changes: 3 additions & 1 deletion src/node-to-fsa/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import type { IFs } from '..';
/**
* Required Node.js `fs` module functions for File System Access API.
*/
export type NodeFsaFs = Pick<IFs, 'promises'>;
export type NodeFsaFs = Pick<IFs, 'promises' | 'openSync' | 'fsyncSync' | 'statSync' | 'closeSync' | 'readSync'>;

export interface NodeFsaContext {
separator: '/' | '\\';
/** Whether synchronous file handles are allowed. */
syncHandleAllowed: boolean;
}

export interface NodeFileSystemHandlePermissionDescriptor {
Expand Down
3 changes: 2 additions & 1 deletion src/node-to-fsa/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import type { NodeFsaContext } from './types';
*/
export const ctx = (partial: Partial<NodeFsaContext> = {}): NodeFsaContext => {
return {
...partial,
separator: '/',
syncHandleAllowed: false,
...partial,
};
};

Expand Down

0 comments on commit f9b0f73

Please sign in to comment.