diff --git a/demo/fsa/main.ts b/demo/fsa/main.ts index 32452ec87..8796073f3 100644 --- a/demo/fsa/main.ts +++ b/demo/fsa/main.ts @@ -2,55 +2,63 @@ (window as any).Buffer = require('buffer').Buffer; import type * as fsa from '../../src/fsa/types'; -import {fsaToNode} from '../../src/fsa-to-node'; -import {SyncMessenger} from '../../src/fsa-to-node/worker/SyncMessenger'; -import {encode, decode} from 'json-joy/es6/json-pack/msgpack/util'; +import {FsaNodeFs, FsaNodeSyncAdapterWorker} from '../../src/fsa-to-node'; const demo = async (dir: fsa.IFileSystemDirectoryHandle) => { - const fs = fsaToNode(dir); + console.log('demo', dir); + const adapter = await FsaNodeSyncAdapterWorker.start(dir); + const fs = new FsaNodeFs(dir, adapter); + + await fs.promises.writeFile('/test.txt', 'Hello world!'); const list = await fs.promises.readdir(''); console.log(list); - await fs.promises.writeFile('test.txt', 'Hello world!'); - await fs.promises.mkdir('storage/a/b/c', {recursive: true}); - await fs.promises.rm('storage/a/b', {recursive: true}); + + const stats = fs.statSync('/test.txt'); + console.log('stats', stats, stats.isDirectory(), stats.isFile()); + // await fs.promises.mkdir('storage/a/b/c', {recursive: true}); + // await fs.promises.rm('storage/a/b', {recursive: true}); - const stream = fs.createWriteStream('stream.txt'); - stream.write('abc'); - stream.write('def'); - stream.end('ghi'); + // const stream = fs.createWriteStream('stream.txt'); + // stream.write('abc'); + // stream.write('def'); + // stream.end('ghi'); - const worker = new Worker('https://localhost:9876/worker.js'); - worker.onerror = (e) => { - console.log("error", e); - }; + // const worker = new Worker('https://localhost:9876/worker.js'); + // worker.onerror = (e) => { + // console.log("error", e); + // }; - let sab: SharedArrayBuffer | undefined = undefined; - let channel: SyncMessenger | undefined = undefined; + // let sab: SharedArrayBuffer | undefined = undefined; + // let channel: SyncMessenger | undefined = undefined; - worker.onmessage = (e) => { - const data = e.data; - if (data && typeof data === 'object') { - console.log('<', data); - switch (data.type) { - case 'init': { - sab = data.sab; - channel = new SyncMessenger(sab!); - worker.postMessage({type: 'set-root', dir, id: 0}); - break; - } - case 'root-set': { - console.log('READY'); - const request = encode({type: 'readdir', path: ''}); - console.log('call sync', request); - const response = channel!.callSync(request); - const responseDecoded = decode(response as any); - console.log('response', responseDecoded); - break; - } - } - } - }; + // worker.onmessage = (e) => { + // const data = e.data; + // if (data && typeof data === 'object') { + // console.log('<', data); + // switch (data.type) { + // case 'init': { + // sab = data.sab; + // channel = new SyncMessenger(sab!); + // worker.postMessage({type: 'set-root', dir, id: 0}); + // break; + // } + // case 'root-set': { + // console.log('READY'); + + // const request = encode({type: 'readdir', path: ''}); + // console.log('call sync', request); + // const response = channel!.callSync(request); + // const responseDecoded = decode(response as any); + // console.log('response', responseDecoded); + // console.log('READY'); + + + // break; + // } + // } + // } + // }; diff --git a/demo/fsa/worker.ts b/demo/fsa/worker.ts index 21d009606..6bb22d9b2 100644 --- a/demo/fsa/worker.ts +++ b/demo/fsa/worker.ts @@ -1,30 +1,6 @@ -import {SyncMessenger} from "../../src/fsa-to-node/worker/SyncMessenger"; -import {encode, decode} from 'json-joy/es6/json-pack/msgpack/util'; -import {IFileSystemDirectoryHandle} from "../../src/fsa/types"; +import {FsaNodeSyncWorker} from "../../src/fsa-to-node/worker/FsaNodeSyncWorker"; -const sab: SharedArrayBuffer = new SharedArrayBuffer(1024 * 32); -const messenger = new SyncMessenger(sab); - -onmessage = (e) => { - const data = e.data; - console.log('>', data); - if (data && typeof data === 'object') { - switch (data.type) { - case 'set-root': { - postMessage({type: 'root-set', id: data.id}); - const dir = data.dir as IFileSystemDirectoryHandle; - messenger.serveAsync(async (request) => { - const message = decode(request as any); - const list: string[] = []; - for await (const key of dir.keys()) { - list.push(key); - } - return encode(list); - }); - break; - } - } - } -}; - -postMessage({type: 'init', sab}); +if (typeof window === 'undefined') { + const worker = new FsaNodeSyncWorker(); + worker.start(); +} diff --git a/src/fsa-to-node/FsaNodeCore.ts b/src/fsa-to-node/FsaNodeCore.ts new file mode 100644 index 000000000..7b1ee8a86 --- /dev/null +++ b/src/fsa-to-node/FsaNodeCore.ts @@ -0,0 +1,121 @@ +import { + createError, + isFd, + pathToFilename, +} from '../node/util'; +import { pathToLocation} from './util'; +import { ERRSTR} from '../node/constants'; +import { FsaToNodeConstants } from './constants'; +import { FsaNodeFsOpenFile } from './FsaNodeFsOpenFile'; +import { FLAG } from '../consts/FLAG'; +import type * as fsa from '../fsa/types'; +import type * as misc from '../node/types/misc'; +import type {FsaNodeSyncAdapter} from './types'; + +export class FsaNodeCore { + protected static fd: number = 0x7fffffff; + protected readonly fds = new Map(); + + public constructor( + protected readonly root: fsa.IFileSystemDirectoryHandle, + protected syncAdapter?: FsaNodeSyncAdapter, + ) {} + + /** + * A list of reusable (opened and closed) file descriptors, that should be + * used first before creating a new file descriptor. + */ + releasedFds: number[] = []; + + protected newFdNumber(): number { + const releasedFd = this.releasedFds.pop(); + return typeof releasedFd === 'number' ? releasedFd : FsaNodeCore.fd--; + } + + /** + * @param path Path from root to the new folder. + * @param create Whether to create the folders if they don't exist. + */ + protected async getDir(path: string[], create: boolean, funcName?: string): Promise { + let curr: fsa.IFileSystemDirectoryHandle = this.root; + + const options: fsa.GetDirectoryHandleOptions = { create }; + + try { + for (const name of path) { + curr = await curr.getDirectoryHandle(name, options); + } + } catch (error) { + if (error && typeof error === 'object' && error.name === 'TypeMismatchError') + throw createError('ENOTDIR', funcName, path.join(FsaToNodeConstants.Separator)); + throw error; + } + return curr; + } + + protected async getFile( + path: string[], + name: string, + funcName?: string, + create?: boolean, + ): Promise { + const dir = await this.getDir(path, false, funcName); + const file = await dir.getFileHandle(name, { create }); + return file; + } + + protected async getFileOrDir( + path: string[], + name: string, + funcName?: string, + create?: boolean, + ): Promise { + const dir = await this.getDir(path, false, funcName); + try { + const file = await dir.getFileHandle(name); + return file; + } catch (error) { + if (error && typeof error === 'object') { + switch (error.name) { + case 'TypeMismatchError': + return await dir.getDirectoryHandle(name); + case 'NotFoundError': + throw createError('ENOENT', funcName, path.join(FsaToNodeConstants.Separator)); + } + } + throw error; + } + } + + protected async getFileByFd(fd: number, funcName?: string): Promise { + if (!isFd(fd)) throw TypeError(ERRSTR.FD); + const file = this.fds.get(fd); + if (!file) throw createError('EBADF', funcName); + return file; + } + + protected async getFileById(id: misc.TFileId, funcName?: string, create?: boolean): Promise { + if (typeof id === 'number') return (await this.getFileByFd(id, funcName)).file; + const filename = pathToFilename(id); + const [folder, name] = pathToLocation(filename); + return await this.getFile(folder, name, funcName, create); + } + + protected async getFileByIdOrCreate(id: misc.TFileId, funcName?: string): Promise { + if (typeof id === 'number') return (await this.getFileByFd(id, funcName)).file; + const filename = pathToFilename(id); + const [folder, name] = pathToLocation(filename); + const dir = await this.getDir(folder, false, funcName); + return await dir.getFileHandle(name, { create: true }); + } + + protected async __open(filename: string, flags: number, mode: number): Promise { + const [folder, name] = pathToLocation(filename); + const createIfMissing = !!(flags & FLAG.O_CREAT); + const fsaFile = await this.getFile(folder, name, 'open', createIfMissing); + const fd = this.newFdNumber(); + const file = new FsaNodeFsOpenFile(fd, mode, flags, fsaFile); + this.fds.set(fd, file); + return file; + } +} diff --git a/src/fsa-to-node/FsaNodeFs.ts b/src/fsa-to-node/FsaNodeFs.ts index 4730f8524..61b51eba6 100644 --- a/src/fsa-to-node/FsaNodeFs.ts +++ b/src/fsa-to-node/FsaNodeFs.ts @@ -13,6 +13,7 @@ import { writeFileDefaults, getWriteFileOptions, getOptions, + getStatOptions, } from '../node/options'; import { bufferToEncoding, @@ -21,7 +22,6 @@ import { flagsToNumber, genRndStr6, getWriteArgs, - isFd, isWin, modeToNumber, nullCheck, @@ -33,7 +33,6 @@ import { pathToLocation, testDirectoryIsWritable } from './util'; import { ERRSTR, MODE } from '../node/constants'; import { strToEncoding } from '../encoding'; import { FsaToNodeConstants } from './constants'; -import { FsaNodeFsOpenFile } from './FsaNodeFsOpenFile'; import { FsaNodeDirent } from './FsaNodeDirent'; import { FLAG } from '../consts/FLAG'; import { AMODE } from '../consts/AMODE'; @@ -48,6 +47,7 @@ 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'; +import {FsaNodeCore} from './FsaNodeCore'; const notSupported: (...args: any[]) => any = () => { throw new Error('Method not supported by the File System Access API.'); @@ -59,109 +59,7 @@ const noop: (...args: any[]) => any = () => {}; * Constructs a Node.js `fs` API from a File System Access API * [`FileSystemDirectoryHandle` object](https://developer.mozilla.org/en-US/docs/Web/API/FileSystemDirectoryHandle). */ -export class FsaNodeFs implements FsCallbackApi, FsSynchronousApi, FsCommonObjects { - protected static fd: number = 0x7fffffff; - protected readonly fds = new Map(); - - public constructor(protected readonly root: fsa.IFileSystemDirectoryHandle) {} - - /** - * A list of reusable (opened and closed) file descriptors, that should be - * used first before creating a new file descriptor. - */ - releasedFds: number[] = []; - - private newFdNumber(): number { - const releasedFd = this.releasedFds.pop(); - return typeof releasedFd === 'number' ? releasedFd : FsaNodeFs.fd--; - } - - /** - * @param path Path from root to the new folder. - * @param create Whether to create the folders if they don't exist. - */ - private async getDir(path: string[], create: boolean, funcName?: string): Promise { - let curr: fsa.IFileSystemDirectoryHandle = this.root; - - const options: fsa.GetDirectoryHandleOptions = { create }; - - try { - for (const name of path) { - curr = await curr.getDirectoryHandle(name, options); - } - } catch (error) { - if (error && typeof error === 'object' && error.name === 'TypeMismatchError') - throw createError('ENOTDIR', funcName, path.join(FsaToNodeConstants.Separator)); - throw error; - } - return curr; - } - - private async getFile( - path: string[], - name: string, - funcName?: string, - create?: boolean, - ): Promise { - const dir = await this.getDir(path, false, funcName); - const file = await dir.getFileHandle(name, { create }); - return file; - } - - private async getFileOrDir( - path: string[], - name: string, - funcName?: string, - create?: boolean, - ): Promise { - const dir = await this.getDir(path, false, funcName); - try { - const file = await dir.getFileHandle(name); - return file; - } catch (error) { - if (error && typeof error === 'object') { - switch (error.name) { - case 'TypeMismatchError': - return await dir.getDirectoryHandle(name); - case 'NotFoundError': - throw createError('ENOENT', funcName, path.join(FsaToNodeConstants.Separator)); - } - } - throw error; - } - } - - private async getFileByFd(fd: number, funcName?: string): Promise { - if (!isFd(fd)) throw TypeError(ERRSTR.FD); - const file = this.fds.get(fd); - if (!file) throw createError('EBADF', funcName); - return file; - } - - private async getFileById(id: misc.TFileId, funcName?: string, create?: boolean): Promise { - if (typeof id === 'number') return (await this.getFileByFd(id, funcName)).file; - const filename = pathToFilename(id); - const [folder, name] = pathToLocation(filename); - return await this.getFile(folder, name, funcName, create); - } - - private async getFileByIdOrCreate(id: misc.TFileId, funcName?: string): Promise { - if (typeof id === 'number') return (await this.getFileByFd(id, funcName)).file; - const filename = pathToFilename(id); - const [folder, name] = pathToLocation(filename); - const dir = await this.getDir(folder, false, funcName); - return await dir.getFileHandle(name, { create: true }); - } - - private async __open(filename: string, flags: number, mode: number): Promise { - const [folder, name] = pathToLocation(filename); - const createIfMissing = !!(flags & FLAG.O_CREAT); - const fsaFile = await this.getFile(folder, name, 'open', createIfMissing); - const fd = this.newFdNumber(); - const file = new FsaNodeFsOpenFile(fd, mode, flags, fsaFile); - this.fds.set(fd, file); - return file; - } +export class FsaNodeFs extends FsaNodeCore implements FsCallbackApi, FsSynchronousApi, FsCommonObjects { // ------------------------------------------------------------ FsCallbackApi @@ -438,7 +336,7 @@ export class FsaNodeFs implements FsCallbackApi, FsSynchronousApi, FsCommonObjec const fileData = await file.getFile(); size = fileData.size; } - const stats = new FsaNodeStats(bigint, bigint ? BigInt(size) : size, handle); + const stats = new FsaNodeStats(bigint, bigint ? BigInt(size) : size, handle.kind); return stats; } @@ -859,6 +757,17 @@ export class FsaNodeFs implements FsCallbackApi, FsSynchronousApi, FsCommonObjec // --------------------------------------------------------- FsSynchronousApi + public readonly statSync: FsSynchronousApi['statSync'] = (path: misc.PathLike, options?: opts.IStatOptions): misc.IStats => { + const { bigint = true, throwIfNoEntry = true } = getStatOptions(options); + const adapter = this.syncAdapter; + if (!adapter) throw new Error('No sync adapter'); + const filename = pathToFilename(path); + const location = pathToLocation(filename); + const res = adapter.stat(location); + const stats = new FsaNodeStats(bigint, res.size ?? 0, res.kind); + return stats; + }; + public readonly accessSync: FsSynchronousApi['accessSync'] = noop; public readonly appendFileSync: FsSynchronousApi['appendFileSync'] = notSupported; public readonly chmodSync: FsSynchronousApi['chmodSync'] = noop; @@ -889,7 +798,6 @@ export class FsaNodeFs implements FsCallbackApi, FsSynchronousApi, FsCommonObjec public readonly renameSync: FsSynchronousApi['renameSync'] = notSupported; public readonly rmdirSync: FsSynchronousApi['rmdirSync'] = notSupported; public readonly rmSync: FsSynchronousApi['rmSync'] = notSupported; - public readonly statSync: FsSynchronousApi['statSync'] = notSupported; public readonly symlinkSync: FsSynchronousApi['symlinkSync'] = notSupported; public readonly truncateSync: FsSynchronousApi['truncateSync'] = notSupported; public readonly unlinkSync: FsSynchronousApi['unlinkSync'] = notSupported; diff --git a/src/fsa-to-node/FsaNodeStats.ts b/src/fsa-to-node/FsaNodeStats.ts index eafa933ff..3407dba00 100644 --- a/src/fsa-to-node/FsaNodeStats.ts +++ b/src/fsa-to-node/FsaNodeStats.ts @@ -1,5 +1,3 @@ -import { NodeFileSystemDirectoryHandle, NodeFileSystemFileHandle } from '../node-to-fsa'; -import type { IFileSystemHandle } from '../fsa/types'; import type * as misc from '../node/types/misc'; const time: number = 0; @@ -26,7 +24,7 @@ export class FsaNodeStats implements misc.IStats { public readonly mode: T; public readonly nlink: T; - public constructor(isBigInt: boolean, size: T, protected readonly handle: IFileSystemHandle) { + public constructor(isBigInt: boolean, size: T, protected readonly kind: 'file' | 'directory') { const dummy = (isBigInt ? timex : time) as any as T; this.uid = dummy; this.gid = dummy; @@ -49,11 +47,11 @@ export class FsaNodeStats implements misc.IStats { } public isDirectory(): boolean { - return this.handle instanceof NodeFileSystemDirectoryHandle; + return this.kind === 'directory'; } public isFile(): boolean { - return this.handle instanceof NodeFileSystemFileHandle; + return this.kind === 'file'; } public isBlockDevice(): boolean { diff --git a/src/fsa-to-node/index.ts b/src/fsa-to-node/index.ts index 9c9612c00..a270c79a0 100644 --- a/src/fsa-to-node/index.ts +++ b/src/fsa-to-node/index.ts @@ -1,7 +1,8 @@ import { FsaNodeFs } from './FsaNodeFs'; +import { FsaNodeSyncAdapterWorker } from './worker/FsaNodeSyncAdapterWorker'; import type { IFileSystemDirectoryHandle } from '../fsa/types'; -export { FsaNodeFs }; +export { FsaNodeFs, FsaNodeSyncAdapterWorker }; export const fsaToNode = (root: IFileSystemDirectoryHandle) => { return new FsaNodeFs(root); diff --git a/src/fsa-to-node/types.ts b/src/fsa-to-node/types.ts index 259bd72e7..14a006d72 100644 --- a/src/fsa-to-node/types.ts +++ b/src/fsa-to-node/types.ts @@ -1 +1,13 @@ export type FsLocation = [folder: string[], file: string]; + +/** + * Adapter which implements synchronous calls to the FSA API. + */ +export interface FsaNodeSyncAdapter { + stat(location: FsLocation): FsaNodeSyncAdapterStats; +} + +export interface FsaNodeSyncAdapterStats { + kind: 'file' | 'directory'; + size?: number; +} diff --git a/src/fsa-to-node/worker/FsaNodeSyncAdapterWorker.ts b/src/fsa-to-node/worker/FsaNodeSyncAdapterWorker.ts new file mode 100644 index 000000000..b7fabf396 --- /dev/null +++ b/src/fsa-to-node/worker/FsaNodeSyncAdapterWorker.ts @@ -0,0 +1,71 @@ +import {Defer} from 'thingies/es6/Defer'; +import {FsaNodeWorkerMessageCode} from './constants'; +import {encode, decode} from 'json-joy/es6/json-pack/msgpack/util'; +import {SyncMessenger} from "./SyncMessenger"; +import type * as fsa from '../../fsa/types'; +import type {FsLocation, FsaNodeSyncAdapter, FsaNodeSyncAdapterStats} from "../types"; +import type {FsaNodeWorkerMsg, FsaNodeWorkerMsgInit, FsaNodeWorkerMsgRootSet, FsaNodeWorkerMsgSetRoot} from "./types"; + +let rootId = 0; + +export class FsaNodeSyncAdapterWorker implements FsaNodeSyncAdapter { + public static async start(dir: fsa.IFileSystemDirectoryHandle): Promise { + const worker = new Worker('https://localhost:9876/worker.js'); + const future = new Defer(); + let id = rootId++; + let messenger: SyncMessenger | undefined = undefined; + worker.onmessage = (e) => { + const data = e.data; + if (!Array.isArray(data)) return; + console.log('<', data); + const msg = data as FsaNodeWorkerMsg; + const code = msg[0] as FsaNodeWorkerMessageCode; + switch (code) { + case FsaNodeWorkerMessageCode.Init: { + const [, sab] = msg as FsaNodeWorkerMsgInit; + messenger = new SyncMessenger(sab); + const setRootMessage: FsaNodeWorkerMsgSetRoot = [FsaNodeWorkerMessageCode.SetRoot, id, dir]; + worker.postMessage(setRootMessage); + break; + } + case FsaNodeWorkerMessageCode.RootSet: { + const [, rootId] = msg as FsaNodeWorkerMsgRootSet; + if (id !== rootId) return; + const adapter = new FsaNodeSyncAdapterWorker(messenger!, id, dir); + future.resolve(adapter); + break; + } + } + }; + return await future.promise; + } + + public constructor(protected readonly messenger: SyncMessenger, protected readonly id: number, protected readonly root: fsa.IFileSystemDirectoryHandle) { + + } + + public call(msg: FsaNodeWorkerMsg): unknown { + const request = encode(msg); + const response = this.messenger.callSync(request); + const resposeMsg = decode(response as any); + switch (resposeMsg[0]) { + case FsaNodeWorkerMessageCode.Response: { + const [, responseData] = resposeMsg; + return responseData; + break; + } + case FsaNodeWorkerMessageCode.ResponseError: { + const [, error] = resposeMsg; + throw error; + break; + } + default: { + throw new Error('Invalid response message code'); + } + } + } + + public stat(location: FsLocation): FsaNodeSyncAdapterStats { + return this.call([FsaNodeWorkerMessageCode.Stat, location]) as FsaNodeSyncAdapterStats; + } +} diff --git a/src/fsa-to-node/worker/FsaNodeSyncWorker.ts b/src/fsa-to-node/worker/FsaNodeSyncWorker.ts new file mode 100644 index 000000000..53c9486c9 --- /dev/null +++ b/src/fsa-to-node/worker/FsaNodeSyncWorker.ts @@ -0,0 +1,64 @@ +import {AsyncCallback, SyncMessenger} from "./SyncMessenger"; +import {encode, decode} from 'json-joy/es6/json-pack/msgpack/util'; +import {FsaNodeWorkerMessageCode} from "./constants"; +import type * as fsa from '../../fsa/types'; +import type {FsaNodeWorkerError, FsaNodeWorkerMsg, FsaNodeWorkerMsgInit, FsaNodeWorkerMsgRootSet} from "./types"; +import type {FsaNodeSyncAdapterStats} from "../types"; + +export class FsaNodeSyncWorker { + protected readonly sab: SharedArrayBuffer = new SharedArrayBuffer(1024 * 32); + protected readonly messenger = new SyncMessenger(this.sab); + protected readonly roots = new Map(); + + protected readonly onCall: AsyncCallback = (request: Uint8Array): Promise => { + throw new Error('Not implemented'); + }; + + public start() { + onmessage = (e) => { + if (!Array.isArray(e.data)) return; + console.log('>', e.data); + this.onPostMessage(e.data as FsaNodeWorkerMsg); + }; + const initMsg: FsaNodeWorkerMsgInit = [FsaNodeWorkerMessageCode.Init, this.sab]; + postMessage(initMsg); + } + + protected onPostMessage = (msg: FsaNodeWorkerMsg): void => { + switch (msg[0]) { + case FsaNodeWorkerMessageCode.SetRoot: { + const [, id, dir] = msg; + this.roots.set(id, dir); + const response: FsaNodeWorkerMsgRootSet = [FsaNodeWorkerMessageCode.RootSet, id]; + postMessage(response); + this.messenger.serveAsync(this.onRequest); + break; + } + } + }; + + protected readonly onRequest: AsyncCallback = async (request: Uint8Array): Promise => { + try { + const message = decode(request as any) as FsaNodeWorkerMsg; + if (!Array.isArray(message)) throw new Error('Invalid message format'); + const code = message[0]; + const handler = this.handlers[code]; + if (!handler) throw new Error('Invalid message code'); + const response = await handler(message); + return encode([FsaNodeWorkerMessageCode.Response, response]); + } catch (err) { + const message = err && typeof err === 'object' && err.message ? err.message : 'Unknown error'; + const error: FsaNodeWorkerError = {message}; + if (err && typeof err === 'object' && err.code) error.code = err.code; + return encode([FsaNodeWorkerMessageCode.ResponseError, error]); + } + }; + + protected handlers: Record Promise> = { + [FsaNodeWorkerMessageCode.Stat]: async (msg: FsaNodeWorkerMsg): Promise => { + return { + kind: 'directory', + }; + }, + }; +} diff --git a/src/fsa-to-node/worker/SyncMessenger.ts b/src/fsa-to-node/worker/SyncMessenger.ts index c94f4c834..26244b490 100644 --- a/src/fsa-to-node/worker/SyncMessenger.ts +++ b/src/fsa-to-node/worker/SyncMessenger.ts @@ -1,4 +1,4 @@ -type AsyncCallback = (request: Uint8Array) => Promise; +export type AsyncCallback = (request: Uint8Array) => Promise; const microSleepSync = () => { /** @todo Replace this by synchronous XHR call. */ @@ -11,10 +11,10 @@ const sleepUntilSync = (condition: () => boolean) => { /** * `SyncMessenger` allows to execute asynchronous code synchronously. The - * asynchronous code is executed in a Worker, while the main thread is blocked - * until the asynchronous code is finished. + * asynchronous code is executed in a Worker thread, while the main thread is + * blocked until the asynchronous code is finished. * - * First, four 4-byte works is header, where the first word is used for Atomics + * First, four 4-byte works is the header, where the first word is used for Atomics * notifications. The second word is used for spin-locking the main thread until * the asynchronous code is finished. The third word is used to specify payload * length. The fourth word is currently unused. @@ -37,29 +37,32 @@ export class SyncMessenger { public callSync(data: Uint8Array): Uint8Array { const requestLength = data.length; + const headerSize = this.headerSize; this.int32[1] = 0; this.int32[2] = requestLength; - this.uint8.set(data, this.headerSize); + this.uint8.set(data, headerSize); Atomics.notify(this.int32, 0); sleepUntilSync(() => this.int32[1] === 1); const responseLength = this.int32[2]; - const response = this.uint8.slice(this.headerSize, this.headerSize + responseLength); + const response = this.uint8.slice(headerSize, headerSize + responseLength); return response; } public serveAsync(callback: AsyncCallback): void { + const headerSize = this.headerSize; (async () => { try { const res = Atomics.wait(this.int32, 0, 0); if (res !== 'ok') throw new Error(`Unexpected Atomics.wait result: ${res}`); const requestLength = this.int32[2]; - const request = this.uint8.slice(this.headerSize, this.headerSize + requestLength); + const request = this.uint8.slice(headerSize, headerSize + requestLength); const response = await callback(request); const responseLength = response.length; this.int32[2] = responseLength; - this.uint8.set(response, this.headerSize); + this.uint8.set(response, headerSize); this.int32[1] = 1; } catch {} + this.serveAsync(callback); })().catch(() => {}); } } diff --git a/src/fsa-to-node/worker/constants.ts b/src/fsa-to-node/worker/constants.ts new file mode 100644 index 000000000..60a16c31f --- /dev/null +++ b/src/fsa-to-node/worker/constants.ts @@ -0,0 +1,8 @@ +export const enum FsaNodeWorkerMessageCode { + Init = 0, + SetRoot, + RootSet, + Response, + ResponseError, + Stat, +} diff --git a/src/fsa-to-node/worker/types.ts b/src/fsa-to-node/worker/types.ts new file mode 100644 index 000000000..aeaebe51d --- /dev/null +++ b/src/fsa-to-node/worker/types.ts @@ -0,0 +1,23 @@ +import type {IFileSystemDirectoryHandle} from "../../fsa/types"; +import type {FsLocation} from "../types"; +import type {FsaNodeWorkerMessageCode} from "./constants"; + +export type FsaNodeWorkerMsgInit = [type: FsaNodeWorkerMessageCode.Init, sab: SharedArrayBuffer]; +export type FsaNodeWorkerMsgSetRoot = [type: FsaNodeWorkerMessageCode.SetRoot, id: number, dir: IFileSystemDirectoryHandle]; +export type FsaNodeWorkerMsgRootSet = [type: FsaNodeWorkerMessageCode.RootSet, id: number]; +export type FsaNodeWorkerMsgResponse = [type: FsaNodeWorkerMessageCode.Response, data: unknown]; +export type FsaNodeWorkerMsgResponseError = [type: FsaNodeWorkerMessageCode.ResponseError, data: unknown]; +export type FsaNodeWorkerMsgStat = [type: FsaNodeWorkerMessageCode.Stat, location: FsLocation]; + +export interface FsaNodeWorkerError { + message: string; + code?: string; +} + +export type FsaNodeWorkerMsg = + | FsaNodeWorkerMsgInit + | FsaNodeWorkerMsgSetRoot + | FsaNodeWorkerMsgRootSet + | FsaNodeWorkerMsgResponse + | FsaNodeWorkerMsgResponseError + | FsaNodeWorkerMsgStat;