From e15a889fae260f7007cfd6e5453c426b116a7710 Mon Sep 17 00:00:00 2001 From: Tomoki Miyauchi Date: Sat, 17 Aug 2024 16:54:14 +0900 Subject: [PATCH] feat: switch to use external file system module --- deno.json | 9 +- deno.lock | 28 +- src/deno/adaptor.ts | 199 +------- src/file_system/algorithm.ts | 297 ----------- .../file_system_directory_handle.ts | 473 ------------------ src/file_system/file_system_file_handle.ts | 311 ------------ src/file_system/file_system_handle.ts | 47 -- .../file_system_sync_access_handle.ts | 245 --------- .../file_system_writable_file_stream.ts | 315 ------------ src/file_system/mod.ts | 5 - src/file_system/symbol.ts | 4 - src/file_system/type.ts | 179 ------- src/file_system/webidl/async.ts | 78 --- src/file_system_access.ts | 8 +- src/show_directory_picker.ts | 32 +- src/show_open_file_picker.ts | 34 +- src/type.ts | 13 +- 17 files changed, 89 insertions(+), 2188 deletions(-) delete mode 100644 src/file_system/algorithm.ts delete mode 100644 src/file_system/file_system_directory_handle.ts delete mode 100644 src/file_system/file_system_file_handle.ts delete mode 100644 src/file_system/file_system_handle.ts delete mode 100644 src/file_system/file_system_sync_access_handle.ts delete mode 100644 src/file_system/file_system_writable_file_stream.ts delete mode 100644 src/file_system/mod.ts delete mode 100644 src/file_system/symbol.ts delete mode 100644 src/file_system/type.ts delete mode 100644 src/file_system/webidl/async.ts diff --git a/deno.json b/deno.json index 22619cb..73df9ac 100644 --- a/deno.json +++ b/deno.json @@ -8,12 +8,13 @@ }, "imports": { "@denosaurs/plug": "jsr:@denosaurs/plug@^1.0.6", - "@miyauci/file-system": "./src/file_system/mod.ts", - "@miyauci/file-system-access": "./src/mod.ts", - "@miyauci/file-system-access/deno": "./src/deno/mod.ts", + "@miyauci/fs": "jsr:@miyauci/fs@1.0.0-beta.1", + "@miyauci/infra": "jsr:@miyauci/infra@1.0.0-beta.2", "@std/bytes": "jsr:@std/bytes@^1.0.2", "@std/media-types": "jsr:@std/media-types@^1.0.2", - "@std/path": "jsr:@std/path@^1.0.1" + "@std/path": "jsr:@std/path@^1.0.1", + "@miyauci/file-system-access": "./src/mod.ts", + "@miyauci/file-system-access/deno": "./src/deno/mod.ts" }, "exports": { ".": "./src/mod.ts", diff --git a/deno.lock b/deno.lock index 86eea41..3540f17 100644 --- a/deno.lock +++ b/deno.lock @@ -4,6 +4,8 @@ "specifiers": { "jsr:@denosaurs/plug": "jsr:@denosaurs/plug@1.0.6", "jsr:@denosaurs/plug@^1.0.6": "jsr:@denosaurs/plug@1.0.6", + "jsr:@miyauci/fs@1.0.0-beta.1": "jsr:@miyauci/fs@1.0.0-beta.1", + "jsr:@miyauci/infra@1.0.0-beta.2": "jsr:@miyauci/infra@1.0.0-beta.2", "jsr:@std/assert@^0.221.0": "jsr:@std/assert@0.221.0", "jsr:@std/bytes@^1.0.2": "jsr:@std/bytes@1.0.2", "jsr:@std/encoding@^0.221.0": "jsr:@std/encoding@0.221.0", @@ -11,7 +13,9 @@ "jsr:@std/fs@^0.221.0": "jsr:@std/fs@0.221.0", "jsr:@std/media-types@^1.0.2": "jsr:@std/media-types@1.0.2", "jsr:@std/path@^0.221.0": "jsr:@std/path@0.221.0", - "jsr:@std/path@^1.0.1": "jsr:@std/path@1.0.1" + "jsr:@std/path@^1.0.1": "jsr:@std/path@1.0.1", + "jsr:@std/path@^1.0.2": "jsr:@std/path@1.0.2", + "npm:@types/node": "npm:@types/node@18.16.19" }, "jsr": { "@denosaurs/plug@1.0.6": { @@ -23,6 +27,18 @@ "jsr:@std/path@^0.221.0" ] }, + "@miyauci/fs@1.0.0-beta.1": { + "integrity": "0c2af5c5ced0946c216e7fbebcd02a802fd956a0f9aaa7b522612fc44e4c3903", + "dependencies": [ + "jsr:@miyauci/infra@1.0.0-beta.2", + "jsr:@std/bytes@^1.0.2", + "jsr:@std/media-types@^1.0.2", + "jsr:@std/path@^1.0.2" + ] + }, + "@miyauci/infra@1.0.0-beta.2": { + "integrity": "ad42a2378b9bd7ca2565cbbfc75ae0e5d792a0c13913e4c006e928995647c1bd" + }, "@std/assert@0.221.0": { "integrity": "a5f1aa6e7909dbea271754fd4ab3f4e687aeff4873b4cef9a320af813adb489a" }, @@ -53,6 +69,15 @@ }, "@std/path@1.0.1": { "integrity": "e061ff02c28481ca49e3a14981875c345e9fc7e973190672782cd0ac8af70428" + }, + "@std/path@1.0.2": { + "integrity": "a452174603f8c620bd278a380c596437a9eef50c891c64b85812f735245d9ec7" + } + }, + "npm": { + "@types/node@18.16.19": { + "integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==", + "dependencies": {} } } }, @@ -60,6 +85,7 @@ "workspace": { "dependencies": [ "jsr:@denosaurs/plug@^1.0.6", + "jsr:@miyauci/fs@1.0.0-beta.1", "jsr:@std/bytes@^1.0.2", "jsr:@std/media-types@^1.0.2", "jsr:@std/path@^1.0.1" diff --git a/src/deno/adaptor.ts b/src/deno/adaptor.ts index 6ecd512..efbaa46 100644 --- a/src/deno/adaptor.ts +++ b/src/deno/adaptor.ts @@ -1,200 +1,17 @@ -import type { - AccessMode, - FileEntry, - FileLocator, - FileSystemAccessResult, - FileSystemEntry, - FileSystemLocator, - IO, - UnderlyingFileSystem, -} from "@miyauci/file-system"; -import { join } from "@std/path/join"; -import { isDirectoryEntry } from "@miyauci/file-system"; -import { readdirSync, statSync } from "node:fs"; +import { type FileEntry, UA, type UserAgent } from "@miyauci/fs"; +import { FileSystem } from "@miyauci/fs/deno"; import { openDirectoryDialog, openFileDialog } from "./ffi.ts"; import type { Adaptor, OpenDirectoryPicker, OpenFileDialog } from "../type.ts"; +import { typeByExtension } from "@std/media-types"; +import { extname } from "@std/path/extname"; export class DenoAdaptor implements Adaptor { - io: IO = new DenoIO(); - openFileDialog: OpenFileDialog = openFileDialog; openDirectoryDialog: OpenDirectoryPicker = openDirectoryDialog; - fs: UnderlyingFileSystem = new DenoFs(); -} - -class DenoFs implements UnderlyingFileSystem { - create(entry: FileSystemEntry, locator: FileSystemLocator): void { - const fullPath = join(locator.root, ...locator.path, entry.name); - - if (isDirectoryEntry(entry)) { - Deno.mkdirSync(fullPath); - } else { - const file = Deno.openSync(fullPath, { createNew: true, write: true }); - - file.writeSync(entry.binaryData); - file.utimeSync(entry.modificationTimestamp, entry.modificationTimestamp); - - file.close(); - } - } - - remove(entry: FileSystemEntry, locator: FileSystemLocator): void { - const fullPath = join(locator.root, ...locator.path.slice(1), entry.name); - - Deno.remove(fullPath, { recursive: true }); - } - - stream( - entry: FileEntry, - locator: FileSystemLocator, - ): ReadableStream { - const fullPath = join(locator.root, ...locator.path); - - return new ReadableStream({ - async start(controller) { - using file = await Deno.open(fullPath, { read: true }); - - await file.read(entry.binaryData); - - controller.enqueue(entry.binaryData); - controller.close(); - }, - }); - } - - write(locator: FileLocator, data: Uint8Array): void { - const fullPath = join(locator.root, ...locator.path); - - Deno.writeFile(fullPath, data); - } -} - -export class DenoIO implements IO { - time: number = Date.now(); - async queryAccess( - locator: FileLocator, - mode: AccessMode, - ): Promise { - const path = join(locator.root, ...locator.path); - - switch (mode) { - case "read": { - const status = await Deno.permissions.request({ name: "read", path }); - - return { permissionState: status.state, errorName: "" }; - } - - case "readwrite": { - const readStatus = await Deno.permissions.request({ - name: "read", - path, - }); - - if (readStatus.state !== "granted") { - return { - permissionState: readStatus.state, - errorName: "NotAllowedError", - }; - } - - const writeStatus = await Deno.permissions.request({ - name: "write", - path, - }); - - if (writeStatus.state !== "granted") { - return { - permissionState: writeStatus.state, - errorName: "NotAllowedError", - }; - } - - return { - permissionState: "granted", - errorName: "", - }; - } - } - } - - async requestAccess( - locator: FileLocator, - mode: AccessMode, - ): Promise { - const path = join(locator.root, ...locator.path); - - switch (mode) { - case "read": { - const status = await Deno.permissions.request({ name: "read", path }); - - return { permissionState: status.state, errorName: "" }; - } - - case "readwrite": { - const readStatus = await Deno.permissions.request({ - name: "read", - path, - }); - - if (readStatus.state !== "granted") { - return { - permissionState: readStatus.state, - errorName: "NotAllowedError", - }; - } - - const writeStatus = await Deno.permissions.request({ - name: "write", - path, - }); - - if (writeStatus.state !== "granted") { - return { - permissionState: writeStatus.state, - errorName: "NotAllowedError", - }; - } - - return { - permissionState: "granted", - errorName: "", - }; - } - } - } - - children(locator: FileSystemLocator): FileSystemLocator[] { - const path = join(locator.root, ...locator.path); - - const dir = readdirSync(path, { withFileTypes: true }); - - return dir.map((entry) => { - const path = locator.path.concat(entry.name); - - if (entry.isDirectory()) { - return { kind: "directory", root: locator.root, path }; - } - - if (entry.isFile()) { - return { kind: "file", root: locator.root, path }; - } - - throw new Error(); - }); - } - - modificationTimestamp(locator: FileSystemLocator): number { - const path = join(locator.root, ...locator.path); - const file = statSync(path); - - return file.mtime?.getTime() ?? this.time; - } - - binaryData(locator: FileSystemLocator): Uint8Array { - const path = join(locator.root, ...locator.path); - const file = statSync(path); - - return new Uint8Array(file.size); + userAgent: UserAgent = new UA(); + typeByEntry(entry: FileEntry): string | undefined { + return typeByExtension(extname(entry.name)); } + locateEntry = FileSystem.prototype.locateEntry; } diff --git a/src/file_system/algorithm.ts b/src/file_system/algorithm.ts deleted file mode 100644 index e202c5e..0000000 --- a/src/file_system/algorithm.ts +++ /dev/null @@ -1,297 +0,0 @@ -import type { - AccessMode, - DirectoryEntry, - DirectoryLocator, - FileEntry, - FileLocator, - FileSystemEntry, - FileSystemLocator, - IO, - UnderlyingFileSystem, -} from "./type.ts"; - -export function locateEntry( - locator: FileSystemLocator, - io: IO, - fs: UnderlyingFileSystem, -): FileSystemEntry | null { - switch (locator.kind) { - case "directory": - return createDirectoryEntry(locator, io, fs); - - case "file": - return createFileEntry(locator, io, fs); - } -} - -function createFileEntry( - locator: FileLocator, - io: IO, - fs: UnderlyingFileSystem, -): FileEntry { - const name = locator.path[locator.path.length - 1]; - - return new FileEntryImpl(name, io, fs, locator); -} - -class FileEntryImpl implements FileEntry { - constructor( - public name: string, - private io: IO, - private fs: UnderlyingFileSystem, - private locator: FileLocator, - ) {} - sharedLockCount: number = 0; - queryAccess(mode: AccessMode) { - return this.io.queryAccess(this.locator, mode); - } - requestAccess(mode: AccessMode) { - return this.io.requestAccess(this.locator, mode); - } - lock: "open" | "taken-exclusive" | "taken-shared" = "open"; - - #timestamp: number | undefined; - #binaryData: Uint8Array | undefined; - - get modificationTimestamp(): number { - return this.#timestamp ?? - (this.#timestamp = this.io.modificationTimestamp(this.locator)); - } - - set modificationTimestamp(value: number) { - this.#timestamp = value; - } - - get binaryData(): Uint8Array { - return this.#binaryData ?? - (this.#binaryData = this.io.binaryData(this.locator)); - } - - set binaryData(value: Uint8Array) { - this.#binaryData = value; - - this.fs.write(this.locator, value); - } -} - -class DirectoryEntryImpl implements DirectoryEntry { - constructor( - public name: string, - private io: IO, - private fs: UnderlyingFileSystem, - private locator: DirectoryLocator, - ) {} - queryAccess(mode: AccessMode) { - return this.io.queryAccess(this.locator, mode); - } - requestAccess(mode: AccessMode) { - return this.io.requestAccess(this.locator, mode); - } - - #children: FileSystemEntry[] | undefined; - - get children(): FileSystemEntry[] { - if (this.#children) return this.#children; - - const childLocators = this.io.children(this.locator); - const createEntry = this.createEntry.bind(this); - - this.#children = childLocators.map(createEntry); - - return this.#children; - } - - set children(value: (FileEntry | DirectoryEntry)[]) { - this.#children = value; - } - - private createEntry(locator: FileSystemLocator): FileSystemEntry { - if (locator.kind === "file") { - return createFileEntry(locator, this.io, this.fs); - } - - return createDirectoryEntry(locator, this.io, this.fs); - } -} - -function createDirectoryEntry( - locator: DirectoryLocator, - io: IO, - fs: UnderlyingFileSystem, -): DirectoryEntry { - const name = locator.path[locator.path.length - 1]; - - return new DirectoryEntryImpl(name, io, fs, locator); -} - -export function isValidFileName(fileName: string): boolean { - // a string that is not an empty string, is not equal to "." or "..", and does not contain '/' or any other character used as path separator on the underlying platform. - if (!fileName) return false; - - if (fileName === "." || fileName === "..") return false; - - if (fileName.includes("/")) return false; - - return true; -} - -export function isDirectoryEntry( - entry: FileSystemEntry, -): entry is DirectoryEntry { - return "children" in entry; -} - -export function isFileEntry( - entry: FileSystemEntry, -): entry is FileEntry { - return "binaryData" in entry; -} - -export function resolve( - child: FileSystemLocator, - root: FileSystemLocator, -): Promise { - // 1. Let result be a new promise. - const { promise, resolve } = Promise.withResolvers(); - // 2. Enqueue the following steps to the file system queue: - queueMicrotask(() => { - // 1. If child’s locator's root is not root’s locator's root, resolve result with null, and abort these steps. // maybe type miss - // 1. If child’s root is not root’s root, resolve result with null, and abort these steps. - if (child.root !== root.root) return resolve(null); - - // 2. Let childPath be child’s path. - const childPath = child.path; - - // 3. Let rootPath be root’s path. - const rootPath = root.path; - - // 4. If childPath is the same path as rootPath, resolve result with « », and abort these steps. - if (isSamePath(childPath, rootPath)) return resolve([]); - - // 5. If rootPath’s size is greater than childPath’s size, resolve result with null, and abort these steps. - - if (rootPath.length > childPath.length) return resolve([]); - - // 6. For each index of rootPath’s indices: - for (const index of rootPath.keys()) { - // 1. If rootPath.\[[index]] is not childPath.\[[index]], then resolve result with null, and abort these steps. - if (rootPath[index] !== childPath[index]) return resolve(null); - } - - // 7. Let relativePath be « ». - const relativePath: string[] = []; - - // 8. For each index of the range from rootPath’s size to rootPath’s size, exclusive, append childPath.\[[index]] to relativePath. - for (const index of exclusiveRange(rootPath.length, childPath.length)) { - relativePath.push(childPath[index]); - } - - // 9. Resolve result with relativePath. - resolve(relativePath); - }); - - // 3. Return result. - return promise; -} - -/** - * @see https://fs.spec.whatwg.org/#file-system-path-the-same-path-as - */ -export function isSamePath(a: string[], b: string[]): boolean { - // if a’s size is the same as b’s size and for each index of a’s indices a.\[[index]] is b.\[[index]]. - return a.length === b.length && - a.every((aValue, index) => aValue === b[index]); -} - -function exclusiveRange(n: number, m: number): number[] { - // If m equals n, then it creates an empty ordered set. - if (m === n) return []; - - const items: number[] = []; - // creates a new ordered set containing all of the integers from n up to and including m − 1 in consecutively increasing order, as long as m is greater than n. - for (let i = n; i < m; i++) { - items.push(i); - } - - return items; -} - -/** - * @see https://fs.spec.whatwg.org/#file-system-locator-the-same-locator-as - */ -export function isSameLocator( - a: FileSystemLocator, - b: FileSystemLocator, -): boolean { - // if a’s kind is b’s kind, a’s root is b’s root, and a’s path is the same path as b’s path. - return a.kind === b.kind && a.root === b.root && isSamePath(a.path, b.path); -} - -/** - * @see https://fs.spec.whatwg.org/#file-entry-lock-take - */ -export function takeLock( - value: "exclusive" | "shared", - file: FileEntry, -): "success" | "failure" { - // 1. Let lock be the file’s lock. - const lock = file.lock; - - // 2. Let count be the file’s shared lock count. - - // 3. If value is "exclusive": - if (value === "exclusive") { - // 1. If lock is "open": - if (lock === "open") { - // 1. Set lock to "taken-exclusive". - file.lock = "taken-exclusive"; - - // 2. Return "success". - return "success"; - } - } - - // 4. If value is "shared": - if (value === "shared") { - // 1. If lock is "open": - if (lock === "open") { - // 1. Set lock to "taken-shared". - file.lock = "taken-shared"; - - // 2. Set count to 1. - file.sharedLockCount = 1; - - // 3. Return "success". - return "success"; - - // 2. Otherwise, if lock is "taken-shared": - } else if (lock === "taken-shared") { - // 1. Increase count by 1. - file.sharedLockCount++; - - // 2. Return "success". - return "success"; - } - } - - // 5. Return "failure". - return "failure"; -} - -/** - * @see https://fs.spec.whatwg.org/#file-entry-lock-release - */ -export function releaseLock(file: FileEntry): void { - // 1. Let lock be the file’s associated lock. - // 2. Let count be the file’s shared lock count. - - // 3. If lock is "taken-shared": - if (file.lock === "taken-shared") { - // 1. Decrease count by 1. - file.sharedLockCount--; - - // 2. If count is 0, set lock to "open". - if (file.sharedLockCount === 0) file.lock = "open"; - } // 4. Otherwise, set lock to "open". - else file.lock = "open"; -} diff --git a/src/file_system/file_system_directory_handle.ts b/src/file_system/file_system_directory_handle.ts deleted file mode 100644 index 124aa8e..0000000 --- a/src/file_system/file_system_directory_handle.ts +++ /dev/null @@ -1,473 +0,0 @@ -import { FileSystemHandle } from "./file_system_handle.ts"; -import { - isDirectoryEntry, - isFileEntry, - isValidFileName, - locateEntry, - resolve, -} from "./algorithm.ts"; -import type { - DirectoryEntry, - FileEntry, - FileSystemEntry, - FileSystemGetDirectoryOptions, - FileSystemGetFileOptions, - FileSystemLocator, - FileSystemRemoveOptions, - IO, - UnderlyingFileSystem, -} from "./type.ts"; -import { - createChildFileSystemFileHandle, - FileSystemFileHandle, -} from "./file_system_file_handle.ts"; -import { locator } from "./symbol.ts"; -import { asynciterator, type PairAsyncIterable } from "./webidl/async.ts"; - -interface IterationContext { - /** - * @see https://fs.spec.whatwg.org/#filesystemdirectoryhandle-iterator-past-results - */ - pastResults: Set; -} - -function next( - handle: FileSystemDirectoryHandle, - iterator: - & AsyncIterableIterator<[string, FileSystemHandle]> - & IterationContext, -): Promise> { - const fsLocator = handle[locator]; - const io = handle["io"]; - const fs = handle["fs"]; - - // // 1. Let promise be a new promise. - const { promise, reject, resolve } = Promise.withResolvers< - IteratorResult< - [string, FileSystemFileHandle | FileSystemDirectoryHandle] - > - >(); - - // // 2. Enqueue the following steps to the file system queue: - queueMicrotask(async () => { - // // 1. Let directory be the result of locating an entry given handle’s locator. - const directory = locateEntry(fsLocator, io, fs); - - // // 2. Let accessResult be the result of running directory’s query access given "read". - const accessResult = await directory?.queryAccess("read"); - - // // 3. Queue a storage task with handle’s relevant global object to run these steps: - - // // 1. If accessResult’s permission state is not "granted", reject promise with a DOMException of accessResult’s error name and abort these steps.: - if (accessResult && accessResult.permissionState !== "granted") { - return reject(new DOMException(accessResult.errorName)); - } - - // // 2. If directory is null, reject result with a "NotFoundError" DOMException and abort these steps. - if (directory === null) { - return reject(new DOMException("NotFoundError")); - } - - // // 1. Assert: directory is a directory entry. - assertDirectoryEntry(directory); - - // // 3. Let child be a file system entry in directory’s children, such that child’s name is not contained in iterator’s past results, or null if no such entry exists. - const child = directory.children.find((child) => - !iterator.pastResults.has(child.name) - ) ?? - null; - - // // 4. If child is null, resolve promise with undefined and abort these steps. - if (child === null) { - return resolve({ done: true, value: undefined }); - } - - // // 5. Append child’s name to iterator’s past results. - iterator.pastResults.add(child.name); - - let result: FileSystemFileHandle | FileSystemDirectoryHandle; - // 6. If child is a file entry: - if (isFileEntry(child)) { - // 1. Let result be the result of creating a child FileSystemFileHandle with handle’s locator and child’s name in handle’s relevant Realm. - result = createChildFileSystemFileHandle(fsLocator, child.name, { - FileSystemFileHandle, - fs, - io, - }); - } // 7. Otherwise: - else { - // 1. Let result be the result of creating a child FileSystemDirectoryHandle with handle’s locator and child’s name in handle’s relevant Realm. - result = createChildFileSystemDirectoryHandle( - fsLocator, - child.name, - { FileSystemDirectoryHandle, fs, io }, - ); - } - - // 8. Resolve promise with (child’s name, result). - resolve({ done: false, value: [child.name, result] }); - }); - - // 3. Return promise. - return promise; -} - -@asynciterator({ - init(_, iterator): void { - // 1. Set iterator’s past results to an empty set. - iterator.pastResults = new Set(); - }, - next, -}) -export class FileSystemDirectoryHandle extends FileSystemHandle { - override get kind(): "directory" { - return "directory"; - } - - getDirectoryHandle( - name: string, - options?: FileSystemGetDirectoryOptions, - ): Promise { - // 1. Let result be a new promise. - const { promise, reject, resolve } = Promise.withResolvers< - FileSystemDirectoryHandle - >(); - - // 2. Let realm be this's relevant Realm. - - // 3. Let locator be this's locator. - const fsLocator = this[locator]; - - // 4. Let global be this's relevant global object. - - // 5. Enqueue the following steps to the file system queue: - queueMicrotask(async () => { - // 1. If name is not a valid file name, queue a storage task with global to reject result with a TypeError and abort these steps. - if (!isValidFileName(name)) { - return reject(); - } - - // 2. Let entry be the result of locating an entry given locator. - const entry = locateEntry(fsLocator, this.io, this.fs); - - // 3. If options["create"] is true: - // 1. Let accessResult be the result of running entry’s request access given "readwrite". - // 4. Otherwise: - // 1. Let accessResult be the result of running entry’s query access given "read". - const accessResult = options?.create - ? await entry?.requestAccess("readwrite") - : await entry?.queryAccess("read"); - - // 5. Queue a storage task with global to run these steps: - - // 1. If accessResult’s permission state is not "granted", reject result with a DOMException of accessResult’s error name and abort these steps. - if (accessResult && accessResult.permissionState !== "granted") { - return reject(new DOMException(accessResult.errorName)); - } - - // 2. If entry is null, reject result with a "NotFoundError" DOMException and abort these steps. - if (entry === null) { - return reject(new DOMException("NotFoundError")); - } - - // 3. Assert: entry is a directory entry. - assertDirectoryEntry(entry); - - // 4. For each child of entry’s children: - for (const child of entry.children) { - // 1. If child’s name equals name: - if (child.name === name) { - // 1. If child is a file entry: - if (isFileEntry(child)) { - // 1. Reject result with a "TypeMismatchError" DOMException and abort these steps. - return reject(new DOMException("TypeMismatchError")); - } - - // 2. Resolve result with the result of creating a child FileSystemDirectoryHandle with locator and child’s name in realm and abort these steps. - return resolve( - createChildFileSystemDirectoryHandle(fsLocator, name, { - FileSystemDirectoryHandle, - fs: this.fs, - io: this.io, - }), - ); - } - } - - // 5. If options["create"] is false: - if (!options?.create) { - // 1. Reject result with a "NotFoundError" DOMException and abort these steps. - return reject(new DOMException("NotFoundError")); - } - - // 6. Let child be a new directory entry whose query access and request access algorithms are those of entry. - // 7. Set child’s name to name. - // 8. Set child’s children to an empty set. - const child = { - name, - queryAccess: entry.queryAccess.bind(entry), - requestAccess: entry.requestAccess.bind(entry), - children: [], - } satisfies DirectoryEntry; - - // 9. Append child to entry’s children. - entry.children.push(child); - - // 10. If creating child in the underlying file system throws an exception, reject result with that exception and abort these steps. - try { - this.fs.create(child, fsLocator); - } catch (e) { - return reject(e); - } - - // 11. Resolve result with the result of creating a child FileSystemDirectoryHandle with locator and child’s name in realm. - resolve( - createChildFileSystemDirectoryHandle(fsLocator, child.name, { - FileSystemDirectoryHandle, - fs: this.fs, - io: this.io, - }), - ); - }); - - // 6. Return result. - return promise; - } - - getFileHandle( - name: string, - options?: FileSystemGetFileOptions, - ): Promise { - // 1. Let result be a new promise. - const { promise, reject, resolve } = Promise.withResolvers< - FileSystemFileHandle - >(); - - // 2. Let realm be this's relevant Realm. - const realm = { FileSystemFileHandle, fs: this.fs, io: this.io }; - - // 3. Let locator be this's locator. - const fsLocator = this[locator]; - - // 4. Let global be this's relevant global object. - // 5. Enqueue the following steps to the file system queue: - queueMicrotask(async () => { - // 1. If name is not a valid file name, queue a storage task with global to reject result with a TypeError and abort these steps. - if (!isValidFileName(name)) return reject(new TypeError()); - - // 2. Let entry be the result of locating an entry given locator. - const entry = locateEntry(fsLocator, this.io, this.fs); - - // 3. If options["create"] is true: - // 1. Let accessResult be the result of running entry’s request access given "readwrite". - // 4. Otherwise: - // 1. Let accessResult be the result of running entry’s query access given "read". - const accessResult = options?.create - ? await entry?.queryAccess("readwrite") - : await entry?.queryAccess("read"); - - // 5. Queue a storage task with global to run these steps: - - // 1. If accessResult’s permission state is not "granted", reject result with a DOMException of accessResult’s error name and abort these steps. - if (accessResult && accessResult.permissionState !== "granted") { - return reject(new DOMException(accessResult.errorName)); - } - - // 2. If entry is null, reject result with a "NotFoundError" DOMException and abort these steps. - if (entry === null) return reject(new DOMException("NotFoundError")); - - // 3. Assert: entry is a directory entry. - assertDirectoryEntry(entry); - - // 4. For each child of entry’s children: - for (const child of entry.children) { - // 1. If child’s name equals name: - if (child.name === name) { - // 1. If child is a directory entry: - if (isDirectoryEntry(child)) { - // 1. Reject result with a "TypeMismatchError" DOMException and abort these steps. - return reject(new DOMException("TypeMismatchError")); - } - - // 2. Resolve result with the result of creating a child FileSystemFileHandle with locator and child’s name in realm and abort these steps. - return resolve( - createChildFileSystemFileHandle(fsLocator, child.name, realm), - ); - } - } - - // 5. If options["create"] is false: - if (!options?.create) { - // 1. Reject result with a "NotFoundError" DOMException and abort these steps. - return reject(new DOMException("NotFoundError")); - } - - // 6. Let child be a new file entry whose query access and request access algorithms are those of entry. - const child = { - // 7. Set child’s name to name. - name, - // 8. Set child’s binary data to an empty byte sequence. - binaryData: new Uint8Array(0), - queryAccess: entry.queryAccess.bind(entry), - requestAccess: entry.requestAccess.bind(entry), - // 9. Set child’s modification timestamp to the current time. - modificationTimestamp: Date.now(), - sharedLockCount: 0, - lock: "open", - } satisfies FileEntry; - - // 10. Append child to entry’s children. - entry.children.push(child); - - // 11. If creating child in the underlying file system throws an exception, reject result with that exception and abort these steps. - try { - this.fs.create(child, fsLocator); - } catch (e) { - reject(e); - } - - // 12. Resolve result with the result of creating a child FileSystemFileHandle with locator and child’s name in realm. - resolve(createChildFileSystemFileHandle(fsLocator, child.name, realm)); - }); - - // 6. Return result. - return promise; - } - - removeEntry(name: string, options?: FileSystemRemoveOptions): Promise { - // 1. Let result be a new promise. - const { promise, resolve, reject } = Promise.withResolvers(); - - // 2. Let locator be this's locator. - const fsLocator = this[locator]; - - // 3. Let global be this's relevant global object. - - // 4. Enqueue the following steps to the file system queue: - queueMicrotask(async () => { - // 1. If name is not a valid file name, queue a storage task with global to reject result with a TypeError and abort these steps. - if (!isValidFileName(name)) return reject(); - - // 2. Let entry be the result of locating an entry given locator. - const entry = locateEntry(fsLocator, this.io, this.fs); - - // 3. Let accessResult be the result of running entry’s request access given "readwrite". - const accessResult = await entry?.requestAccess("readwrite"); - - // 4. Queue a storage task with global to run these steps: - - // 1. If accessResult’s permission state is not "granted", reject result with a DOMException of accessResult’s error name and abort these steps. - if (accessResult && accessResult.permissionState !== "granted") { - return reject(new DOMException(accessResult.errorName)); - } - - // 2. If entry is null, reject result with a "NotFoundError" DOMException and abort these steps. - if (entry === null) return reject(); - - // 3. Assert: entry is a directory entry. - assertDirectoryEntry(entry); - - // 4. For each child of entry’s children: - for (const child of entry.children) { - // 1. If child’s name equals name: - if (child.name === name) { - // 1. If child is a directory entry: - if (isDirectoryEntry(child)) { - // 1. If child’s children is not empty and options["recursive"] is false: - if (child.children.length && !options?.recursive) { - // 1. Reject result with an "InvalidModificationError" DOMException and abort these steps. - return reject(new DOMException("InvalidModificationError")); - } - } - - // 2. Remove child from entry’s children. - // TODO - - // 3. If removing child in the underlying file system throws an exception, reject result with that exception and abort these steps. - try { - this.fs.remove(child, fsLocator); - } catch (e) { - return reject(e); - } - - // 4. Resolve result with undefined. - return resolve(); - } - } - - // 5. Reject result with a "NotFoundError" DOMException. - reject(new DOMException("NotFoundError")); - }); - - // 5. Return result. - return promise; - } - - resolve( - possibleDescendant: FileSystemHandle, - ): Promise { - // steps are to return the result of resolving possibleDescendant’s locator relative to this's locator. - return resolve(possibleDescendant[locator], this[locator]); - } -} - -export interface FileSystemDirectoryHandle - extends PairAsyncIterable {} - -function assertDirectoryEntry( - _: FileSystemEntry, -): asserts _ is DirectoryEntry {} - -export function createChildFileSystemDirectoryHandle( - parentLocator: FileSystemLocator, - name: string, - realm: { - FileSystemDirectoryHandle: typeof FileSystemDirectoryHandle; - fs: UnderlyingFileSystem; - io: IO; - }, -): FileSystemDirectoryHandle { - // 2. Let childType be "directory". - const childType = "directory"; - - // 3. Let childRoot be a copy of parentLocator’s root. - const childRoot = parentLocator.root; - - // 4. Let childPath be the result of cloning parentLocator’s path and appending name. - const childPath = parentLocator.path.concat(name); - const locator = { - kind: childType, - root: childRoot, - path: childPath, - } satisfies FileSystemLocator; - - // 5. Set handle’s locator to a file system locator whose kind is childType, root is childRoot, and path is childPath. - // 1. Let handle be a new FileSystemDirectoryHandle in realm. - const handle = new realm.FileSystemDirectoryHandle( - locator, - realm.fs, - realm.io, - ); - - // 6. Return handle. - return handle; -} - -export function createFileSystemDirectoryHandle( - root: string, - path: string[], - realm: { - FileSystemDirectoryHandle: typeof FileSystemDirectoryHandle; - fs: UnderlyingFileSystem; - io: IO; - }, -): FileSystemDirectoryHandle { - const locator = { kind: "directory", root, path } satisfies FileSystemLocator; - const handle = new realm.FileSystemDirectoryHandle( - locator, - realm.fs, - realm.io, - ); - - return handle; -} diff --git a/src/file_system/file_system_file_handle.ts b/src/file_system/file_system_file_handle.ts deleted file mode 100644 index 8988e41..0000000 --- a/src/file_system/file_system_file_handle.ts +++ /dev/null @@ -1,311 +0,0 @@ -import { FileSystemHandle } from "./file_system_handle.ts"; -import type { - FileEntry, - FileSystemCreateWritableOptions, - FileSystemEntry, - FileSystemLocator, - IO, - UnderlyingFileSystem, -} from "./type.ts"; -import { locateEntry, takeLock } from "./algorithm.ts"; -import { createFileSystemWritableFileStream } from "./file_system_writable_file_stream.ts"; -import { buffer, locator } from "./symbol.ts"; -import { extname } from "@std/path"; -import { typeByExtension } from "@std/media-types"; -import { - createFileSystemSyncAccessHandle, - type FileSystemSyncAccessHandle, -} from "./file_system_sync_access_handle.ts"; -import type { FileSystemWritableFileStream } from "./file_system_writable_file_stream.ts"; - -export class FileSystemFileHandle extends FileSystemHandle { - override get kind(): "file" { - return "file"; - } - - getFile(): Promise { - // 1. Let result be a new promise. - const { reject, promise, resolve } = Promise.withResolvers(); - - // 2. Let locator be this's locator. - const fsLocator = this[locator]; - - // 3. Let global be this's relevant global object. - - // 4. Enqueue the following steps to the file system queue: - queueMicrotask(async () => { - // 1. Let entry be the result of locating an entry given locator. - const entry = locateEntry(fsLocator, this.io, this.fs); - - // 2. Let accessResult be the result of running entry’s query access given "read". - const accessResult = await entry?.queryAccess("read"); - - // 3. Queue a storage task with global to run these steps: - - // 1. If accessResult’s permission state is not "granted", reject result with a DOMException of accessResult’s error name and abort these steps. - if (accessResult && accessResult.permissionState !== "granted") { - return reject(new DOMException(accessResult.errorName)); - } - - // 2. If entry is null, reject result with a "NotFoundError" DOMException and abort these steps. - if (entry === null) return reject(new DOMException("NotFoundError")); - - // 3. Assert: entry is a file entry. - assertFileEntry(entry); - - // 4. Let f be a new File. - // 5. Set f’s snapshot state to the current state of entry. - // 6. Set f’s underlying byte sequence to a copy of entry’s binary data. - // 7. Set f’s name to entry’s name. - // 8. Set f’s lastModified to entry’s modification timestamp. - // 9. Set f’s type to an implementation-defined value, based on for example entry’s name or its file extension. - const blob = new BlobDataItem({ - locator: fsLocator, - lastModified: entry.modificationTimestamp, - entry, - fs: this.fs, - io: this.io, - binaryData: entry.binaryData, - }); - - const type = typeByExtension(extname(entry.name)); - - const file = new File([blob], entry.name, { - lastModified: entry.modificationTimestamp, - type, - }); - - // 10. Resolve result with f. - resolve(file); - }); - - // 5. Return result. - - return promise; - } - - createWritable( - options?: FileSystemCreateWritableOptions, - ): Promise { - // 1. Let result be a new promise. - const { promise, reject, resolve } = Promise.withResolvers< - FileSystemWritableFileStream - >(); - - // 2. Let locator be this's locator. - const fsLocator = this[locator]; - - // 3. Let realm be this's relevant Realm. - - // 4. Let global be this's relevant global object. - - // 5. Enqueue the following steps to the file system queue: - queueMicrotask(async () => { - // 1. Let entry be the result of locating an entry given locator. - const entry = locateEntry(fsLocator, this.io, this.fs); - - // 2. Let accessResult be the result of running entry’s request access given "readwrite". - const accessResult = await entry?.requestAccess("readwrite"); - - // 3. If accessResult’s permission state is not "granted", queue a storage task with global to reject result with a DOMException of accessResult’s error name and abort these steps. - if (accessResult && accessResult.permissionState !== "granted") { - return reject(new DOMException(accessResult.errorName)); - } - - // 4. If entry is null, queue a storage task with global to reject result with a "NotFoundError" DOMException and abort these steps. - if (entry === null) return reject(new DOMException("NotFoundError")); - - // 5. Assert: entry is a file entry. - assertFileEntry(entry); - - // 6. Let lockResult be the result of taking a lock with "shared" on entry. - const lockResult = takeLock("shared", entry); - - // 7. Queue a storage task with global to run these steps: - - // 1. If lockResult is "failure", reject result with a "NoModificationAllowedError" DOMException and abort these steps. - if (lockResult === "failure") { - return reject(new DOMException("NoModificationAllowedError")); - } - - // 2. Let stream be the result of creating a new FileSystemWritableFileStream for entry in realm. - const stream = createFileSystemWritableFileStream(entry); - - // 3. If options["keepExistingData"] is true: - if (options?.keepExistingData) { - // 1. Set stream’s [[buffer]] to a copy of entry’s binary data. - stream[buffer] = entry.binaryData.slice(0); - } - - // 4. Resolve result with stream. - return resolve(stream); - }); - - // 6. Return result. - return promise; - } - - createSyncAccessHandle(): Promise { - // 1. Let result be a new promise. - const { promise: result, reject, resolve } = Promise.withResolvers< - FileSystemSyncAccessHandle - >(); - - // 2. Let locator be this's locator. - const fsLocator = this[locator]; - - // 3. Let realm be this's relevant Realm. - - // 4. Let global be this's relevant global object. - - // 5. Let isInABucketFileSystem be true if this is in a bucket file system; otherwise false. - - // 6. Enqueue the following steps to the file system queue: - queueMicrotask(async () => { - // 1. Let entry be the result of locating an entry given locator. - const entry = locateEntry(fsLocator, this.io, this.fs); - - // 2. Let accessResult be the result of running entry’s request access given "readwrite". - const accessResult = await entry?.requestAccess("readwrite"); - - // 3. If accessResult’s permission state is not "granted", queue a storage task with global to reject result with a DOMException of accessResult’s error name and abort these steps. - if (accessResult && accessResult.permissionState !== "granted") { - return reject(new DOMException(accessResult.errorName)); - } - - // 4. If isInABucketFileSystem is false, queue a storage task with global to reject result with an "InvalidStateError" DOMException and abort these steps. - - // 5. If entry is null, queue a storage task with global to reject result with a "NotFoundError" DOMException and abort these steps. - if (entry === null) return reject(new DOMException("NotFoundError")); - - // 6. Assert: entry is a file entry. - assertFileEntry(entry); - - // 7. Let lockResult be the result of taking a lock with "exclusive" on entry. - const lockResult = takeLock("exclusive", entry); - - // 8. Queue a storage task with global to run these steps: - - // 1. If lockResult is "failure", reject result with a "NoModificationAllowedError" DOMException and abort these steps. - if (lockResult === "failure") { - return reject(new DOMException("NoModificationAllowedError")); - } - - // 2. Let handle be the result of creating a new FileSystemSyncAccessHandle for entry in realm. - const handle = createFileSystemSyncAccessHandle(entry); - - // 3. Resolve result with handle. - resolve(handle); - }); - - // 7. Return result. - return result; - } -} - -function assertFileEntry(_: FileSystemEntry): asserts _ is FileEntry {} - -interface BlobDataItemOptions { - lastModified: number; - locator: FileSystemLocator; - entry: FileEntry; - fs: UnderlyingFileSystem; - io: IO; - binaryData: Uint8Array; -} - -class BlobDataItem extends Blob { - lastModified: number; - locator: FileSystemLocator; - entry: FileEntry; - fs: UnderlyingFileSystem; - io: IO; - binaryData: Uint8Array; - - constructor(options: BlobDataItemOptions) { - super([options.binaryData]); - - this.lastModified = options.lastModified; - this.locator = options.locator; - this.entry = options.entry; - this.fs = options.fs; - this.io = options.io; - this.binaryData = options.binaryData; - } - - slice(start: number = 0, end?: number): Blob { - const binaryData = this.binaryData.slice(start, end); - - return new BlobDataItem({ - lastModified: this.lastModified, - locator: this.locator, - entry: { ...this.entry, binaryData }, - fs: this.fs, - io: this.io, - binaryData, - }); - } - - stream(): ReadableStream { - const timestamp = this.io.modificationTimestamp(this.locator); - - if (timestamp > this.lastModified) { - throw new DOMException( - "The requested file could not be read, typically due to permission problems that have occurred after a reference to a file was acquired.", - "NotReadableError", - ); - } - - return this.fs.stream(this.entry, this.locator); - } -} - -export function createChildFileSystemFileHandle( - parentLocator: FileSystemLocator, - name: string, - realm: { - FileSystemFileHandle: typeof FileSystemFileHandle; - fs: UnderlyingFileSystem; - io: IO; - }, -): FileSystemFileHandle { - // 2. Let childType be "file". - const childType = "file"; - - // 3. Let childRoot be a copy of parentLocator’s root. - const childRoot = parentLocator.root; - - // 4. Let childPath be the result of cloning parentLocator’s path and appending name. - const childPath = parentLocator.path.concat(name); - const locator = { - kind: childType, - root: childRoot, - path: childPath, - } satisfies FileSystemLocator; - // 5. Set handle’s locator to a file system locator whose kind is childType, root is childRoot, and path is childPath. - // 1. Let handle be a new FileSystemFileHandle in realm. - const handle = new realm.FileSystemFileHandle(locator, realm.fs, realm.io); - - // 6. Return handle. - return handle; -} - -export function createFileSystemFileHandle( - root: string, - path: string[], - fs: UnderlyingFileSystem, - io: IO, -): FileSystemFileHandle { - const locator = { - kind: "file", - root, - path, - } satisfies FileSystemLocator; - - // 1. Let handle be a new FileSystemFileHandle in realm. - // 2. Set handle’s locator to a file system locator whose kind is "file", root is root, and path is path. - const handle = new FileSystemFileHandle(locator, fs, io); - - // 3. Return handle. - return handle; -} diff --git a/src/file_system/file_system_handle.ts b/src/file_system/file_system_handle.ts deleted file mode 100644 index f11b963..0000000 --- a/src/file_system/file_system_handle.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { isSameLocator } from "./algorithm.ts"; -import type { - FileSystemHandleKind, - FileSystemLocator, - IO, - UnderlyingFileSystem, -} from "./type.ts"; -import { locator } from "./symbol.ts"; - -export class FileSystemHandle { - constructor( - loc: FileSystemLocator, - protected fs: UnderlyingFileSystem, - protected io: IO, - ) { - this[locator] = loc; - } - get kind(): FileSystemHandleKind { - // steps are to return this's locator's kind. - return this[locator].kind; - } - - get name(): string { - // steps are to return the last item (a string) of this's locator's path. - return this[locator].path[this[locator].path.length - 1]; - } - - isSameEntry(other: FileSystemHandle): Promise { - // 1. Let realm be this's relevant Realm. - - // 2. Let p be a new promise in realm. - const { promise, resolve } = Promise.withResolvers(); - - // 3. Enqueue the following steps to the file system queue: - queueMicrotask(() => { - // 1. If this's locator is the same locator as other’s locator, resolve p with true. - if (isSameLocator(this[locator], other[locator])) resolve(true); - // 2. Otherwise resolve p with false. - else resolve(false); - }); - - // 4. Return p. - return promise; - } - - [locator]: FileSystemLocator; -} diff --git a/src/file_system/file_system_sync_access_handle.ts b/src/file_system/file_system_sync_access_handle.ts deleted file mode 100644 index d6ed872..0000000 --- a/src/file_system/file_system_sync_access_handle.ts +++ /dev/null @@ -1,245 +0,0 @@ -import { releaseLock } from "./algorithm.ts"; -import type { - AllowSharedBufferSource, - FileEntry, - FileSystemReadWriteOptions, -} from "./type.ts"; -import { $file } from "./symbol.ts"; -import { concat } from "@std/bytes"; - -const state = Symbol("[[state]]"); - -export class FileSystemSyncAccessHandle { - /** - * @see https://fs.spec.whatwg.org/#filesystemsyncaccesshandle-state - */ - [state]: "open" | "close" = "open"; - [$file]!: FileEntry; - filePositionCursor: number = 0; - - read( - buffer: AllowSharedBufferSource, - options?: FileSystemReadWriteOptions, - ): number { - // 1. If this's [[state]] is "closed", throw an "InvalidStateError" DOMException. - if (this[state] === "close") throw new DOMException("InvalidStateError"); - - // 2. Let bufferSize be buffer’s byte length. - const bufferSize = buffer.byteLength; - - // 3. Let fileContents be this's [[file]]'s binary data. - const fileContents = this[$file].binaryData; - - // 4. Let fileSize be fileContents’s length. - const fileSize = fileContents.length; - - // 5. Let readStart be options["at"] if options["at"] exists; otherwise this's file position cursor. - const readStart = typeof options?.at === "number" - ? options.at - : this.filePositionCursor; - - // 6. If the underlying file system does not support reading from a file offset of readStart, throw a TypeError. - - // 7. If readStart is larger than fileSize: - if (readStart > fileSize) { - // 1. Set this's file position cursor to fileSize. - this.filePositionCursor = fileSize; - - // 2. Return 0. - return 0; - } - - // 8. Let readEnd be readStart + (bufferSize − 1). - let readEnd = readStart + (bufferSize - 1); - - // 9. If readEnd is larger than fileSize, set readEnd to fileSize. - if (readEnd > fileSize) readEnd = fileSize; - - // 10. Let bytes be a byte sequence containing the bytes from readStart to readEnd of fileContents. - const bytes = fileContents.slice(readStart, readEnd); - - // 11. Let result be bytes’s length. - const result = bytes.length; - - // 12. If the operations reading from fileContents in the previous steps failed: - - // 1. If there were partial reads and the number of bytes that were read into bytes is known, set result to the number of read bytes. - - // 2. Otherwise set result to 0. - - // 13. Let arrayBuffer be buffer’s underlying buffer. - - // 14. Write bytes into arrayBuffer. - - // 15. Set this's file position cursor to readStart + result. - this.filePositionCursor = readStart + result; - - // 16. Return result. - return result; - } - - write( - buffer: AllowSharedBufferSource, - options?: FileSystemReadWriteOptions, - ): number { - // 1. If this's [[state]] is "closed", throw an "InvalidStateError" DOMException. - if (this[state] === "close") throw new DOMException("InvalidStateError"); - - // 2. Let writePosition be options["at"] if options["at"] exists; otherwise this's file position cursor. - const writePosition = typeof options?.at === "number" - ? options.at - : this.filePositionCursor; - - // 3. If the underlying file system does not support writing to a file offset of writePosition, throw a TypeError. - - // 4. Let fileContents be a copy of this's [[file]]'s binary data. - const fileContents = this[$file].binaryData.slice(0); - - // 5. Let oldSize be fileContents’s length. - const oldSize = fileContents.length; - - // 6. Let bufferSize be buffer’s byte length. - const bufferSize = buffer.byteLength; - - // 7. If writePosition is larger than oldSize, append writePosition − oldSize 0x00 (NUL) bytes to the end of fileContents. - - // 8. Let head be a byte sequence containing the first writePosition bytes of fileContents. - // const head = fileContents.slice(0, writePosition); - - // 9. Let tail be an empty byte sequence. - // let tail = new Uint8Array(); - - // 10. If writePosition + bufferSize is smaller than oldSize: - if (writePosition + bufferSize < oldSize) { - // const lastIndex = oldSize - (writePosition + bufferSize); - // 1. Set tail to a byte sequence containing the last oldSize − (writePosition + bufferSize) bytes of fileContents. - // tail = fileContents.slice(-lastIndex); - } - - // 11. Let newSize be head’s length + bufferSize + tail’s length. - // const newSize = head.length + bufferSize + tail.length; - - // 12. If newSize − oldSize exceeds the available storage quota, throw a "QuotaExceededError" DOMException. - - // 13. Set this's [[file]]'s binary data to the concatenation of head, the contents of buffer and tail. - - // 14. If the operations modifying the this's[[file]]'s binary data in the previous steps failed: - - // 1. If there were partial writes and the number of bytes that were written from buffer is known: - - // 1. Let bytesWritten be the number of bytes that were written from buffer. - - // 2. Set this's file position cursor to writePosition + bytesWritten. - - // 3. Return bytesWritten. - - // 2. Otherwise throw an "InvalidStateError" DOMException. - - // 15. Set this's file position cursor to writePosition + bufferSize. - this.filePositionCursor = writePosition + bufferSize; - - // 16. Return bufferSize. - return bufferSize; - } - - truncate(newSize: number): void { - // 1. If this's [[state]] is "closed", throw an "InvalidStateError" DOMException. - if (this[state] === "close") throw new DOMException("InvalidStateError"); - - // 2. Let fileContents be a copy of this's [[file]]'s binary data. - const fileContents = this[$file].binaryData.slice(); - - // 3. Let oldSize be the length of this's [[file]]'s binary data. - const oldSize = this[$file].binaryData.length; - - // 4. If the underlying file system does not support setting a file’s size to newSize, throw a TypeError. - - // 5. If newSize is larger than oldSize: - if (newSize > oldSize) { - // 1. If newSize − oldSize exceeds the available storage quota, throw a "QuotaExceededError" DOMException. - - try { - // 2. Set this's [[file]]'s to a byte sequence formed by concatenating fileContents with a byte sequence containing newSize − oldSize 0x00 bytes. - this[$file].binaryData = concat([ - fileContents.slice(0, newSize), - new Uint8Array(newSize - oldSize), - ]); - } catch { - // 3. If the operations modifying the this's [[file]]'s binary data in the previous steps failed, throw an "InvalidStateError" DOMException. - throw new DOMException("InvalidStateError"); - } - - // 6. Otherwise, if newSize is smaller than oldSize: - } else if (newSize < oldSize) { - try { - // 1. Set this's [[file]]'s to a byte sequence containing the first newSize bytes in fileContents. - this[$file].binaryData = fileContents.slice(0, newSize); - } catch { - // 2. If the operations modifying the this's [[file]]'s binary data in the previous steps failed, throw an "InvalidStateError" DOMException. - throw new DOMException("InvalidStateError"); - } - } - - // 7. If this's file position cursor is greater than newSize, then set file position cursor to newSize. - if (this.filePositionCursor > newSize) this.filePositionCursor = newSize; - } - - getSize(): number { - // 1. If this's [[state]] is "closed", throw an "InvalidStateError" DOMException. - if (this[state] === "close") throw new DOMException("InvalidStateError"); - - // 2. Return this's [[file]]'s binary data's length. - return this[$file].binaryData.length; - } - - flush(): void { - // 1. If this's [[state]] is "closed", throw an "InvalidStateError" DOMException. - if (this[state] === "close") throw new DOMException("InvalidStateError"); - - // 2. Attempt to transfer all cached modifications of the file’s content to the file system’s underlying storage device. - } - - close(): void { - // 1. If this's [[state]] is "closed", return. - if (this[state] === "close") return; - - // 2. Set this's [[state]] to "closed". - this[state] = "close"; - - // 3. Set lockReleased to false. - let lockReleased = false; - - // 4. Let file be this's [[file]]. - const file = this[$file]; - - // 5. Enqueue the following steps to the file system queue: - queueMicrotask(() => { - // 1. Release the lock on file. - releaseLock(file); - - // 2. Set lockReleased to true. - lockReleased = true; - }); - - // 6. Pause until lockReleased is true. - while (!lockReleased) { - // noop - } - } -} - -export function createFileSystemSyncAccessHandle( - file: FileEntry, -): FileSystemSyncAccessHandle { - // 1. Let handle be a new FileSystemSyncAccessHandle in realm. - const handle = new FileSystemSyncAccessHandle(); - - // 2. Set handle’s [[file]] to file. - handle[$file] = file; - - // 3. Set handle’s [[state]] to "open". - handle[state] = "open"; - - // 4. Return handle. - return handle; -} diff --git a/src/file_system/file_system_writable_file_stream.ts b/src/file_system/file_system_writable_file_stream.ts deleted file mode 100644 index 93078f0..0000000 --- a/src/file_system/file_system_writable_file_stream.ts +++ /dev/null @@ -1,315 +0,0 @@ -import { releaseLock } from "./algorithm.ts"; -import type { - FileEntry, - FileSystemWriteChunkType, - WriteParams, -} from "./type.ts"; -import { $file, buffer, seekOffset } from "./symbol.ts"; -import { concat } from "@std/bytes"; - -export class FileSystemWritableFileStream - extends WritableStream { - [$file]!: FileEntry; - [seekOffset]: number = 0; - - /** - * @see https://fs.spec.whatwg.org/#filesystemwritablefilestream-buffer - */ - [buffer]: Uint8Array = new Uint8Array(0); - - seek(position: number): Promise { - // 1. Let writer be the result of getting a writer for this. - const writer = super.getWriter(); - - // 2. Let result be the result of writing a chunk to writer given «[ "type" → "seek", "position" → position ]». - const result = writer.write({ type: "seek", position }); - - // 3. Release writer. - writer.releaseLock(); - - // 4. Return result. - return result; - } - - truncate(size: number): Promise { - // 1. Let writer be the result of getting a writer for this. - const writer = super.getWriter(); - - // 2. Let result be the result of writing a chunk to writer given «[ "type" → "truncate", "size" → size ]». - const result = writer.write({ type: "truncate", size }); - - // 3. Release writer. - writer.releaseLock(); - - // 4. Return result. - return result; - } - - write(data: FileSystemWriteChunkType): Promise { - // 1. Let writer be the result of getting a writer for this. - const writer = super.getWriter(); - - // 2. Let result be the result of writing a chunk to writer given data. - const result = writer.write(data); - - // 3. Release writer. - writer.releaseLock(); - - // 4. Return result. - return result; - } -} - -/** - * @see https://fs.spec.whatwg.org/#create-a-new-filesystemwritablefilestream - */ -export function createFileSystemWritableFileStream( - file: FileEntry, -): FileSystemWritableFileStream { - // 3. Let writeAlgorithm be an algorithm which takes a chunk argument and returns the result of running the write a chunk algorithm with stream and chunk. - const writeAlgorithm: UnderlyingSinkWriteCallback = - (chunk: FileSystemWriteChunkType) => writeChunk(stream, chunk); - - // 4. Let closeAlgorithm be these steps: - const closeAlgorithm: UnderlyingSinkCloseCallback = () => { - // 1. Let closeResult be a new promise. - const { promise: closeResult, reject, resolve } = Promise.withResolvers< - void - >(); - - // 2. Enqueue the following steps to the file system queue: - queueMicrotask(async () => { - // 1. Let accessResult be the result of running file’s query access given "readwrite". - const accessResult = await file.queryAccess("readwrite"); - - // 2. Queue a storage task with file’s relevant global object to run these steps: - - // 1. If accessResult’s permission state is not "granted", reject closeResult with a DOMException of accessResult’s error name and abort these steps. - if (accessResult && accessResult.permissionState !== "granted") { - return reject(new DOMException(accessResult.errorName)); - } - - // 2. Run implementation-defined malware scans and safe browsing checks. If these checks fail, reject closeResult with an "AbortError" DOMException and abort these steps. - - // 3. Set stream’s [[file]]'s binary data to stream’s [[buffer]]. If that throws an exception, reject closeResult with that exception and abort these steps. - stream[$file].binaryData = stream[buffer]; - - // 4. Enqueue the following steps to the file system queue: - - // 1. Release the lock on stream’s [[file]]. - releaseLock(stream[$file]); - - // 2. Queue a storage task with file’s relevant global object to resolve closeResult with undefined. - resolve(); - }); - - // 3. Return closeResult. - return closeResult; - }; - - // 5. Let abortAlgorithm be these steps: - const abortAlgorithm: UnderlyingSinkAbortCallback = () => { - // 1. Enqueue this step to the file system queue: - - // 1. Release the lock on stream’s [[file]]. - releaseLock(stream[$file]); - }; - - // 6. Let highWaterMark be 1. - const highWaterMark = 1; - - // 7. Let sizeAlgorithm be an algorithm that returns 1. - const sizeAlgorithm: QueuingStrategySize = () => 1; - - // 1. Let stream be a new FileSystemWritableFileStream in realm. - const stream = new FileSystemWritableFileStream({ - abort: abortAlgorithm, - close: closeAlgorithm, - write: writeAlgorithm, - }, { highWaterMark, size: sizeAlgorithm }); - - // 2. Set stream’s [[file]] to file. - stream[$file] = file; - - // 8. Set up stream with writeAlgorithm set to writeAlgorithm, closeAlgorithm set to closeAlgorithm, abortAlgorithm set to abortAlgorithm, highWaterMark set to highWaterMark, and sizeAlgorithm set to sizeAlgorithm. - // setup( - // stream, - // writeAlgorithm, - // closeAlgorithm, - // abortAlgorithm, - // highWaterMark, - // sizeAlgorithm, - // ); - - // 9. Return stream. - return stream; -} - -/** - * @see https://fs.spec.whatwg.org/#write-a-chunk - */ -export function writeChunk( - stream: FileSystemWritableFileStream, - chunk: FileSystemWriteChunkType, -): Promise { - // 1. Let input be the result of converting chunk to a FileSystemWriteChunkType. If this throws an exception, then return a promise rejected with that exception. - const input = toWriteParams(chunk); - - // 2. Let p be a new promise. - const { promise: p, reject, resolve } = Promise.withResolvers(); - - // 3. Enqueue the following steps to the file system queue: - queueMicrotask(async () => { - // 1. Let accessResult be the result of running stream’s [[file]]'s query access given "readwrite". - const accessResult = await stream[$file].queryAccess("readwrite"); - - // 2. Queue a storage task with stream’s relevant global object to run these steps: - - // 1. If accessResult’s permission state is not "granted", reject p with a DOMException of accessResult’s error name and abort these steps. - if (accessResult && accessResult.permissionState !== "granted") { - return reject(new DOMException(accessResult.errorName)); - } - - // 2. Let command be input["type"] if input is a dictionary; otherwise "write". - const command = input.type; - - // 3. If command is "write": - if (command === "write") { - // 1. If input is undefined or input is a dictionary and input["data"] does not exist, reject p with a TypeError and abort these steps. - if (!input || input.data === null || input.data === undefined) { - return reject(new TypeError()); - } - - // 2. Let data be input["data"] if input is a dictionary; otherwise input. - const data = input.data; - - // 3. Let writePosition be stream’s [[seekOffset]]. - let writePosition = stream[seekOffset]; - - // 4. If input is a dictionary and input["position"] exists, set writePosition to input["position"]. - if (typeof input.position === "number") writePosition = input.position; - - // 5. Let oldSize be stream’s [[buffer]]'s length. - const oldSize = stream[buffer].byteLength; - - let dataBytes: Uint8Array; - // 6. If data is a BufferSource, let dataBytes be a copy of data. - // 7. Otherwise, if data is a Blob: - if (data instanceof Blob) { - // 1. Let dataBytes be the result of performing the read operation on data. If this throws an exception, reject p with that exception and abort these steps. - dataBytes = await data.bytes(); - // 8. Otherwise: - } else if (typeof data === "string") { - // 1. Assert: data is a USVString. - - // 2. Let dataBytes be the result of UTF-8 encoding data. - dataBytes = new TextEncoder().encode(data); - } else { - if (data instanceof ArrayBuffer) { - dataBytes = new Uint8Array(data); - } else { - dataBytes = new Uint8Array(data.buffer); - } - } - - // 9. If writePosition is larger than oldSize, append writePosition - oldSize 0x00 (NUL) bytes to the end of stream’s [[buffer]]. - if (writePosition > oldSize) { - const size = writePosition - oldSize; - - stream[buffer] = concat([stream[buffer], new Uint8Array(size)]); - } - - // 10. Let head be a byte sequence containing the first writePosition bytes of stream’s[[buffer]]. - const head = stream[buffer].slice(0, writePosition); - - // 11. Let tail be an empty byte sequence. - let tail = new Uint8Array(0); - - // 12. If writePosition + data’s length is smaller than oldSize: - if (writePosition + length(data) < oldSize) { - // 1. Let tail be a byte sequence containing the last oldSize - (writePosition + data’s length) bytes of stream’s [[buffer]]. - const index = oldSize - (writePosition + length(data)); - - tail = stream[buffer].slice(-index); - } - - // 13. Set stream’s [[buffer]] to the concatenation of head, data and tail. - const i = concat([head, dataBytes, tail]); - - stream[buffer] = i; - - // 14. If the operations modifying stream’s [[buffer]] in the previous steps failed due to exceeding the storage quota, reject p with a "QuotaExceededError" DOMException and abort these steps, leaving stream’s [[buffer]] unmodified. - - // 15. Set stream’s [[seekOffset]] to writePosition + data’s length. - stream[seekOffset] = writePosition + length(data); - - // 16. Resolve p. - resolve(); - - // 4. Otherwise, if command is "seek": - } else if (command === "seek") { - // 1. Assert: chunk is a dictionary. - - // 2. If chunk["position"] does not exist, reject p with a TypeError and abort these steps. - if (typeof input.position !== "number") return reject(new TypeError()); - - // 3. Set stream’s [[seekOffset]] to chunk["position"]. - stream[seekOffset] = input.position; - - // 4. Resolve p. - resolve(); - - // 5. Otherwise, if command is "truncate": - } else if (command === "truncate") { - // 1. Assert: chunk is a dictionary. - - // 2. If chunk["size"] does not exist, reject p with a TypeError and abort these steps. - if (typeof input.size !== "number") return reject(new TypeError()); - - // 3. Let newSize be chunk["size"]. - const newSize = input.size; - - // 4. Let oldSize be stream’s [[buffer]]'s length. - const oldSize = stream[buffer].byteLength; - - // 5. If newSize is larger than oldSize: - if (newSize > oldSize) { - // 1. Set stream’s [[buffer]] to a byte sequence formed by concating stream’s [[buffer]] with a byte sequence containing newSize-oldSize 0x00 bytes. - - // 2. If the operation in the previous step failed due to exceeding the storage quota, reject p with a "QuotaExceededError" DOMException and abort these steps, leaving stream’s [[buffer]] unmodified. - } // 6. Otherwise, if newSize is smaller than oldSize: - else if (newSize < oldSize) { - // 1. Set stream’s [[buffer]] to a byte sequence containing the first newSize bytes in stream’s [[buffer]]. - } - - // 7. If stream’s [[seekOffset]] is bigger than newSize, set stream’s [[seekOffset]] to newSize. - if (stream[seekOffset] > newSize) stream[seekOffset] = newSize; - - // 8. Resolve p. - resolve(); - } - }); - - // 4. Return p. - return p; -} - -function toWriteParams(chunk: FileSystemWriteChunkType): WriteParams { - if (typeof chunk === "string") return { type: "write", data: chunk }; - if (chunk instanceof Blob) return { type: "write", data: chunk }; - if (chunk instanceof ArrayBuffer) return { type: "write", data: chunk }; - - if ("buffer" in chunk) { - return { type: "write", data: chunk }; - } - - return chunk; -} - -function length(data: string | BufferSource | Blob): number { - if (typeof data === "string") return data.length; - - if (data instanceof Blob) return data.size; - - return data.byteLength; -} diff --git a/src/file_system/mod.ts b/src/file_system/mod.ts deleted file mode 100644 index 8f5e654..0000000 --- a/src/file_system/mod.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type * from "./type.ts"; -export * from "./algorithm.ts"; -export * from "./file_system_directory_handle.ts"; -export * from "./file_system_file_handle.ts"; -export * from "./file_system_handle.ts"; diff --git a/src/file_system/symbol.ts b/src/file_system/symbol.ts deleted file mode 100644 index ea9ce09..0000000 --- a/src/file_system/symbol.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const locator = Symbol("[[locator]]"); -export const $file = Symbol("[[file]]"); -export const seekOffset = Symbol("[[seekOffset]]"); -export const buffer = Symbol("[[buffer]]"); diff --git a/src/file_system/type.ts b/src/file_system/type.ts deleted file mode 100644 index d778901..0000000 --- a/src/file_system/type.ts +++ /dev/null @@ -1,179 +0,0 @@ -export interface FileSystemGetFileOptions { - create?: boolean; -} - -export interface FileSystemGetDirectoryOptions { - create?: boolean; -} - -export interface FileSystemRemoveOptions { - recursive?: boolean; -} - -export interface FileSystemCreateWritableOptions { - keepExistingData?: boolean; -} - -export type FileSystemHandleKind = "directory" | "file"; - -export type AllowSharedBufferSource = ArrayBuffer | ArrayBufferView; - -export interface FileSystemReadWriteOptions { - at?: number; -} - -export type FileSystemWriteChunkType = - | BufferSource - | Blob - | string - | WriteParams; - -export interface WriteParams { - data?: BufferSource | Blob | string | null; - position?: number | null; - size?: number | null; - type: WriteCommandType; -} - -export type WriteCommandType = "seek" | "truncate" | "write"; - -export type PermissionState = "denied" | "granted" | "prompt"; - -interface BaseEntry { - /** - * @see https://fs.spec.whatwg.org/#entry-name - */ - name: string; - - /** - * @see https://fs.spec.whatwg.org/#entry-query-access - */ - queryAccess( - mode: AccessMode, - ): FileSystemAccessResult | Promise; - - /** - * @see https://fs.spec.whatwg.org/#entry-request-access - */ - requestAccess( - mode: AccessMode, - ): FileSystemAccessResult | Promise; -} - -export type AccessMode = "read" | "readwrite"; - -export interface FileEntry extends BaseEntry { - /** - * @see https://fs.spec.whatwg.org/#file-entry-binary-data - */ - binaryData: Uint8Array; - - /** A number representing the number of milliseconds since the Unix Epoch. - * @see https://fs.spec.whatwg.org/#file-entry-modification-timestamp - */ - modificationTimestamp: number; - - /** - * @see https://fs.spec.whatwg.org/#file-entry-lock - */ - lock: "open" | "taken-exclusive" | "taken-shared"; - - /** A number representing the number shared locks that are taken at a given point in time - * @see https://fs.spec.whatwg.org/#file-entry-shared-lock-count - */ - sharedLockCount: number; -} - -export interface DirectoryEntry extends BaseEntry { - /** File system entries. - * @see https://fs.spec.whatwg.org/#directory-entry-children - */ - children: FileSystemEntry[]; -} - -/** - * @see https://fs.spec.whatwg.org/#entry - */ -export type FileSystemEntry = FileEntry | DirectoryEntry; - -/** Struct encapsulating the result of {@link BaseEntry.queryAccess querying} or {@link BaseEntry.requestAccess requesting} access to the file system. - * @see https://fs.spec.whatwg.org/#file-system-access-result - */ -export interface FileSystemAccessResult { - /** - * @see https://fs.spec.whatwg.org/#file-system-access-result-permission-state - */ - permissionState: PermissionState; - - /** A string which must be the empty string if {@link permissionState permission state} is "granted"; otherwise an name listed in the DOMException names table. It is expected that in most cases when {@link permissionState permission state} is not "granted", this should be "NotAllowedError". - * @see https://fs.spec.whatwg.org/#file-system-access-result-error-name - */ - errorName: string; -} - -/** A potential location of a {@link FileSystemEntry file system entry}. - * - * @see https://fs.spec.whatwg.org/#file-system-locator - */ -export type FileSystemLocator = FileLocator | DirectoryLocator; - -interface BaseLocator { - /** - * @see https://fs.spec.whatwg.org/#locator-path - */ - path: string[]; - - /** - * @see https://fs.spec.whatwg.org/#locator-kind - */ - kind: FileSystemHandleKind; - - /** - * @see https://fs.spec.whatwg.org/#locator-root - */ - root: string; -} - -/** - * @see https://fs.spec.whatwg.org/#directory-locator - */ -export interface DirectoryLocator extends BaseLocator { - /** - * @see https://fs.spec.whatwg.org/#locator-kind - */ - kind: "directory"; -} - -/** - * @see https://fs.spec.whatwg.org/#file-locator - */ -export interface FileLocator extends BaseLocator { - /** - * @see https://fs.spec.whatwg.org/#locator-kind - */ - kind: "file"; -} - -export interface UnderlyingFileSystem { - create(entry: FileSystemEntry, locator: FileSystemLocator): void; - remove(entry: FileSystemEntry, locator: FileSystemLocator): void; - stream( - entry: FileEntry, - locator: FileSystemLocator, - ): ReadableStream; - write(locator: FileLocator, data: Uint8Array): void; -} - -export interface IO { - binaryData(locator: FileSystemLocator): Uint8Array; - modificationTimestamp(locator: FileSystemLocator): number; - queryAccess( - locator: FileSystemLocator, - mode: AccessMode, - ): FileSystemAccessResult | Promise; - requestAccess( - locator: FileSystemLocator, - mode: AccessMode, - ): FileSystemAccessResult | Promise; - children(locator: FileSystemLocator): FileSystemLocator[]; -} diff --git a/src/file_system/webidl/async.ts b/src/file_system/webidl/async.ts deleted file mode 100644 index e363561..0000000 --- a/src/file_system/webidl/async.ts +++ /dev/null @@ -1,78 +0,0 @@ -// deno-lint-ignore-file no-explicit-any - -export interface AsynchronouslyIterable< - T, - C extends object = object, - I = unknown, -> { - init(instance: C, iterator: AsyncIterableIterator & I): void; - next( - instance: C, - iterator: AsyncIterableIterator & I, - ): Promise>; -} - -interface PairAsynchronouslyIterableDeclaration { - keys(): AsyncIterableIterator; - entries(): AsyncIterableIterator<[K, V]>; -} - -export interface ValueAsyncIterable { - [Symbol.asyncIterator](): AsyncIterableIterator; - values(): AsyncIterableIterator; -} - -export interface PairAsyncIterable - extends PairAsynchronouslyIterableDeclaration { - [Symbol.asyncIterator](): AsyncIterableIterator<[K, V]>; - values(): AsyncIterableIterator; -} - -export function asynciterator< - K, - V, - T extends new (...args: any) => any, - I = unknown, ->( - definition: AsynchronouslyIterable<[K, V], InstanceType, I>, -): (target: T, context: ClassDecoratorContext) => void { - return (target) => { - return class extends target { - [Symbol.asyncIterator]() { - const _this = this as InstanceType; - - const iterator = { - next() { - return definition.next( - _this, - this as AsyncIterableIterator<[K, V]> & I, - ); - }, - [Symbol.asyncIterator]() { - return this; - }, - } satisfies AsyncIterableIterator<[K, V]> as - & AsyncIterableIterator<[K, V]> - & I; - - definition.init(this as InstanceType, iterator); - - return iterator; - } - - async *keys() { - for await (const [key] of this[Symbol.asyncIterator]()) yield key; - } - - async *values() { - for await (const [_, value] of this[Symbol.asyncIterator]()) { - yield value; - } - } - - entries() { - return this[Symbol.asyncIterator]; - } - }; - }; -} diff --git a/src/file_system_access.ts b/src/file_system_access.ts index 21a802b..d1f8b6e 100644 --- a/src/file_system_access.ts +++ b/src/file_system_access.ts @@ -1,7 +1,7 @@ import type { FileSystemDirectoryHandle, FileSystemFileHandle, -} from "@miyauci/file-system"; +} from "@miyauci/fs"; import type { Adaptor } from "./type.ts"; import { crateShowOpenFilePicker } from "./show_open_file_picker.ts"; import type { DirectoryPickerOptions, OpenFilePickerOptions } from "./type.ts"; @@ -10,14 +10,12 @@ import { createShowDirectoryPicker } from "./show_directory_picker.ts"; export class FileSystemAccess { constructor(adaptor: Adaptor) { this.showOpenFilePicker = crateShowOpenFilePicker( - adaptor.fs, - adaptor.io, + adaptor, adaptor.openFileDialog.bind(adaptor), ); this.showDirectoryPicker = createShowDirectoryPicker( - adaptor.fs, - adaptor.io, + adaptor, adaptor.openDirectoryDialog.bind(adaptor), ); } diff --git a/src/show_directory_picker.ts b/src/show_directory_picker.ts index b7b0e7c..0180f56 100644 --- a/src/show_directory_picker.ts +++ b/src/show_directory_picker.ts @@ -1,15 +1,17 @@ import { createFileSystemDirectoryHandle, - FileSystemDirectoryHandle, - type IO, - type UnderlyingFileSystem, -} from "@miyauci/file-system"; + type FileSystemDirectoryHandle, + type FileSystemFileOrDirectoryHandleContext, +} from "@miyauci/fs"; +import { List } from "@miyauci/infra"; import { isTooSensitiveOrDangerous } from "./algorithm.ts"; import type { DirectoryPickerOptions, OpenDirectoryPicker } from "./type.ts"; export function showDirectoryPickerWith( - fs: UnderlyingFileSystem, - io: IO, + context: Pick< + FileSystemFileOrDirectoryHandleContext, + "locateEntry" | "typeByEntry" | "userAgent" + >, openDirectoryPicker: OpenDirectoryPicker, options?: DirectoryPickerOptions, ): Promise { @@ -44,11 +46,11 @@ export function showDirectoryPickerWith( } // 7. Set result to a new FileSystemDirectoryHandle associated with entry. - const result = createFileSystemDirectoryHandle(root, [""], { - FileSystemDirectoryHandle, - fs, - io, - }); + const result = createFileSystemDirectoryHandle( + root, + new List([""]), + context, + ); // 8. Remember a picked directory given options["id"], entry and environment. @@ -83,10 +85,12 @@ export function showDirectoryPickerWith( } export function createShowDirectoryPicker( - fs: UnderlyingFileSystem, - io: IO, + context: Pick< + FileSystemFileOrDirectoryHandleContext, + "locateEntry" | "typeByEntry" | "userAgent" + >, openDirectoryPicker: OpenDirectoryPicker, ) { return (options?: DirectoryPickerOptions) => - showDirectoryPickerWith(fs, io, openDirectoryPicker, options); + showDirectoryPickerWith(context, openDirectoryPicker, options); } diff --git a/src/show_open_file_picker.ts b/src/show_open_file_picker.ts index 96791fe..66f0b22 100644 --- a/src/show_open_file_picker.ts +++ b/src/show_open_file_picker.ts @@ -1,15 +1,16 @@ import { - createFileSystemFileHandle, - type FileSystemFileHandle, - type IO, - type UnderlyingFileSystem, -} from "@miyauci/file-system"; + FileSystemFileHandle, + type FileSystemFileOrDirectoryHandleContext, +} from "@miyauci/fs"; import { isTooSensitiveOrDangerous } from "./algorithm.ts"; import type { OpenFileDialog, OpenFilePickerOptions } from "./type.ts"; +import { List } from "jsr:@miyauci/infra@1.0.0-beta.2"; export function showOpenFilePickerWith( - fs: UnderlyingFileSystem, - io: IO, + context: Pick< + FileSystemFileOrDirectoryHandleContext, + "locateEntry" | "typeByEntry" | "userAgent" + >, openFileDialog: OpenFileDialog, options: OpenFilePickerOptions = {}, ): Promise { @@ -53,10 +54,15 @@ export function showOpenFilePickerWith( // 2. At the discretion of the user agent, either go back to the beginning of these in parallel steps, or reject p with an "AbortError" DOMException and abort. } - const path: string[] = [name]; - // 2. Add a new FileSystemFileHandle associated with entry to result. - result.push(createFileSystemFileHandle(root, path, fs, io)); + result.push( + new FileSystemFileHandle({ + locateEntry: context.locateEntry.bind(context), + locator: { kind: "file", path: new List([name]), root }, + typeByEntry: context.typeByEntry.bind(context), + userAgent: context.userAgent, + }), + ); } // 8. Remember a picked directory given options["id"], entries[0] and environment. @@ -78,10 +84,12 @@ export function showOpenFilePickerWith( // ) {} export function crateShowOpenFilePicker( - fs: UnderlyingFileSystem, - io: IO, + context: Pick< + FileSystemFileOrDirectoryHandleContext, + "locateEntry" | "typeByEntry" | "userAgent" + >, open: OpenFileDialog, ) { return (options?: OpenFilePickerOptions) => - showOpenFilePickerWith(fs, io, open, options); + showOpenFilePickerWith(context, open, options); } diff --git a/src/type.ts b/src/type.ts index 915307e..92af282 100644 --- a/src/type.ts +++ b/src/type.ts @@ -1,8 +1,7 @@ import type { + FileSystemFileOrDirectoryHandleContext, FileSystemHandle, - IO, - UnderlyingFileSystem, -} from "@miyauci/file-system"; +} from "@miyauci/fs"; export interface FilePickerOptions { types?: FilePickerAcceptType[]; @@ -66,9 +65,11 @@ export interface OpenFileDialog { (options?: OpenFilePickerOptions): { root: string; name: string }[]; } -export interface Adaptor { +export interface Adaptor extends + Pick< + FileSystemFileOrDirectoryHandleContext, + "locateEntry" | "typeByEntry" | "userAgent" + > { openFileDialog: OpenFileDialog; openDirectoryDialog: OpenDirectoryPicker; - fs: UnderlyingFileSystem; - io: IO; }