@zenfs/dom 1.1.9 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/access.d.ts CHANGED
@@ -1,18 +1,34 @@
1
1
  import type { CreationOptions, FileSystem, InodeLike } from '@zenfs/core';
2
- import { IndexFS } from '@zenfs/core';
2
+ import { IndexFS, Inode } from '@zenfs/core';
3
3
  export interface WebAccessOptions {
4
4
  handle: FileSystemDirectoryHandle;
5
5
  metadata?: string;
6
+ /**
7
+ * If set, disables caching of file system handles.
8
+ * This could significantly degrade lookup performance.
9
+ */
10
+ disableHandleCache?: boolean;
6
11
  }
7
12
  type HKindToType<T extends FileSystemHandleKind> = T extends 'directory' ? FileSystemDirectoryHandle : T extends 'file' ? FileSystemFileHandle : FileSystemHandle;
8
13
  declare const WebAccessFS_base: import("@zenfs/core").Mixin<typeof IndexFS, import("@zenfs/core").AsyncMixin>;
14
+ /**
15
+ * @todo Consider supporting synchronous stuff with `FileSystemFileHandle.createSyncAccessHandle()`\
16
+ * @internal
17
+ */
9
18
  export declare class WebAccessFS extends WebAccessFS_base {
19
+ protected readonly root: FileSystemDirectoryHandle;
20
+ protected readonly disableHandleCache: boolean;
21
+ /**
22
+ * Used to speed up handle lookups.
23
+ * Without this, every lookup would be O(n) on the path length.
24
+ * With the cache, these become O(1) operations.
25
+ */
10
26
  protected _handles: Map<string, FileSystemHandle>;
11
27
  /**
12
28
  * Loads all of the handles.
13
29
  * @internal @hidden
14
30
  */
15
- _loadHandles(path: string, handle: FileSystemDirectoryHandle): Promise<void>;
31
+ protected _loadHandles(path: string, handle: FileSystemDirectoryHandle): Promise<void>;
16
32
  /**
17
33
  * Loads metadata
18
34
  * @internal @hidden
@@ -22,7 +38,8 @@ export declare class WebAccessFS extends WebAccessFS_base {
22
38
  * @hidden
23
39
  */
24
40
  _sync: FileSystem;
25
- constructor(handle: FileSystemDirectoryHandle);
41
+ constructor(root: FileSystemDirectoryHandle, disableHandleCache?: boolean);
42
+ stat(path: string): Promise<Inode>;
26
43
  protected remove(path: string): Promise<void>;
27
44
  protected removeSync(): void;
28
45
  read(path: string, buffer: Uint8Array, offset: number, end: number): Promise<void>;
@@ -33,7 +50,7 @@ export declare class WebAccessFS extends WebAccessFS_base {
33
50
  */
34
51
  writeFile(path: string, data: Uint8Array): Promise<void>;
35
52
  mkdir(path: string, options: CreationOptions): Promise<InodeLike>;
36
- protected get<const T extends FileSystemHandleKind | null>(kind: T | undefined, path: string): T extends FileSystemHandleKind ? HKindToType<T> : FileSystemHandle;
53
+ protected get<const T extends FileSystemHandleKind | null>(kind: T | undefined, path: string): Promise<T extends FileSystemHandleKind ? HKindToType<T> : FileSystemHandle>;
37
54
  }
38
55
  declare const _WebAccess: {
39
56
  readonly name: "WebAccess";
@@ -46,6 +63,10 @@ declare const _WebAccess: {
46
63
  readonly type: "string";
47
64
  readonly required: false;
48
65
  };
66
+ readonly disableHandleCache: {
67
+ readonly type: "boolean";
68
+ readonly required: false;
69
+ };
49
70
  };
50
71
  readonly create: (options: WebAccessOptions) => Promise<WebAccessFS>;
51
72
  };
package/dist/access.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { Async, constants, IndexFS, InMemory, Inode } from '@zenfs/core';
2
2
  import { basename, dirname, join } from '@zenfs/core/path.js';
3
- import { S_IFDIR, S_IFMT } from '@zenfs/core/vfs/constants.js';
4
3
  import { log, withErrno } from 'kerium';
5
4
  import { alert } from 'kerium/log';
6
5
  import { _throw } from 'utilium';
@@ -18,6 +17,10 @@ function isResizable(buffer) {
18
17
  function isKind(handle, kind) {
19
18
  return handle.kind == kind;
20
19
  }
20
+ /**
21
+ * @todo Consider supporting synchronous stuff with `FileSystemFileHandle.createSyncAccessHandle()`\
22
+ * @internal
23
+ */
21
24
  export class WebAccessFS extends Async(IndexFS) {
22
25
  /**
23
26
  * Loads all of the handles.
@@ -36,8 +39,10 @@ export class WebAccessFS extends Async(IndexFS) {
36
39
  * @internal @hidden
37
40
  */
38
41
  async _loadMetadata(metadataPath) {
42
+ this._handles.set('/', this.root);
43
+ await this._loadHandles('/', this.root);
39
44
  if (metadataPath) {
40
- const handle = this.get('file', metadataPath);
45
+ const handle = await this.get('file', metadataPath);
41
46
  const file = await handle.getFile();
42
47
  const raw = await file.text();
43
48
  const data = JSON.parse(raw);
@@ -55,18 +60,55 @@ export class WebAccessFS extends Async(IndexFS) {
55
60
  this.index.set(path, new Inode({ mode: 0o777 | constants.S_IFDIR, size: 0 }));
56
61
  }
57
62
  }
58
- constructor(handle) {
63
+ constructor(root, disableHandleCache = false) {
59
64
  super(0x77656261, 'webaccessfs');
65
+ this.root = root;
66
+ this.disableHandleCache = disableHandleCache;
67
+ /**
68
+ * Used to speed up handle lookups.
69
+ * Without this, every lookup would be O(n) on the path length.
70
+ * With the cache, these become O(1) operations.
71
+ */
60
72
  this._handles = new Map();
61
73
  /**
62
74
  * @hidden
63
75
  */
64
76
  this._sync = InMemory.create({ label: 'accessfs-cache' });
65
77
  this.attributes.set('no_buffer_resize', true);
66
- this._handles.set('/', handle);
78
+ if (disableHandleCache)
79
+ this.attributes.set('no_handle_cache', true);
80
+ }
81
+ async stat(path) {
82
+ try {
83
+ return await super.stat(path);
84
+ }
85
+ catch (ex) {
86
+ if (ex.code != 'ENOENT')
87
+ throw ex;
88
+ // This handle must have been created after initialization
89
+ // Try to add a new inode for the handle to the index
90
+ const handle = await this.get(null, path);
91
+ const inode = new Inode();
92
+ if (isKind(handle, 'file')) {
93
+ const file = await handle.getFile();
94
+ inode.update({
95
+ mode: 0o644 | constants.S_IFREG,
96
+ size: file.size,
97
+ mtimeMs: file.lastModified,
98
+ });
99
+ }
100
+ else {
101
+ inode.update({
102
+ mode: constants.S_IFDIR | 0o777,
103
+ size: 0,
104
+ });
105
+ }
106
+ this.index.set(path, inode);
107
+ return inode;
108
+ }
67
109
  }
68
110
  async remove(path) {
69
- const handle = this.get('directory', dirname(path));
111
+ const handle = await this.get('directory', dirname(path));
70
112
  await handle.removeEntry(basename(path), { recursive: true }).catch(ex => _throw(convertException(ex, path)));
71
113
  }
72
114
  removeSync() {
@@ -75,11 +117,15 @@ export class WebAccessFS extends Async(IndexFS) {
75
117
  async read(path, buffer, offset, end) {
76
118
  if (end <= offset)
77
119
  return;
78
- const handle = this.get('file', path);
120
+ const handle = await this.get('file', path);
79
121
  const file = await handle.getFile();
80
122
  const data = await file.arrayBuffer();
81
123
  if (data.byteLength < end - offset)
82
- throw alert(withErrno('EIO', `Unexpected mismatch in file data size. This should not happen.\n\t\tTried to read ${end - offset} bytes but the file is ${data.byteLength} bytes.`));
124
+ throw alert(withErrno('EIO', [
125
+ 'Unexpected mismatch in file data size. This should not happen.',
126
+ `tried to read ${end - offset} bytes but the file is ${data.byteLength} bytes.`,
127
+ `path: ${path}`,
128
+ ].join('\n' + ' '.repeat(24))));
83
129
  buffer.set(new Uint8Array(data, offset, end - offset));
84
130
  }
85
131
  async write(path, buffer, offset) {
@@ -91,15 +137,16 @@ export class WebAccessFS extends Async(IndexFS) {
91
137
  const inode = this.index.get(path);
92
138
  if (!inode)
93
139
  throw withErrno('ENOENT');
94
- const isDir = (inode.mode & S_IFMT) == S_IFDIR;
140
+ const isDir = (inode.mode & constants.S_IFMT) == constants.S_IFDIR;
95
141
  let handle;
96
142
  try {
97
- handle = this.get(isDir ? 'directory' : 'file', path);
143
+ handle = await this.get(isDir ? 'directory' : 'file', path);
98
144
  }
99
145
  catch {
100
- const parent = this.get('directory', dirname(path));
146
+ const parent = await this.get('directory', dirname(path));
101
147
  handle = await parent[isDir ? 'getDirectoryHandle' : 'getFileHandle'](basename(path), { create: true }).catch((ex) => _throw(convertException(ex, path)));
102
- this._handles.set(path, handle);
148
+ if (!this.disableHandleCache)
149
+ this._handles.set(path, handle);
103
150
  }
104
151
  if (isDir)
105
152
  return;
@@ -129,18 +176,39 @@ export class WebAccessFS extends Async(IndexFS) {
129
176
  }
130
177
  async mkdir(path, options) {
131
178
  const inode = await super.mkdir(path, options);
132
- const handle = this.get('directory', dirname(path));
179
+ const handle = await this.get('directory', dirname(path));
133
180
  const dir = await handle.getDirectoryHandle(basename(path), { create: true }).catch((ex) => _throw(convertException(ex, path)));
134
- this._handles.set(path, dir);
181
+ if (!this.disableHandleCache)
182
+ this._handles.set(path, dir);
135
183
  return inode;
136
184
  }
137
- get(kind = null, path) {
138
- const handle = this._handles.get(path);
139
- if (!handle)
140
- throw withErrno('ENODATA');
141
- if (kind && !isKind(handle, kind))
142
- throw withErrno(kind == 'directory' ? 'ENOTDIR' : 'EISDIR');
143
- return handle;
185
+ async get(kind = null, path) {
186
+ const maybeHandle = this._handles.get(path);
187
+ if (!this.disableHandleCache && maybeHandle) {
188
+ if (kind && !isKind(maybeHandle, kind))
189
+ throw withErrno(kind == 'directory' ? 'ENOTDIR' : 'EISDIR');
190
+ return maybeHandle;
191
+ // Otherwise, fall through to the slow path
192
+ }
193
+ if (path == '/')
194
+ return this.root;
195
+ const parts = path.slice(1).split('/');
196
+ let dir = this.root;
197
+ for (let i = 0; i < parts.length - 1; ++i) {
198
+ dir = await dir.getDirectoryHandle(parts[i]).catch(ex => _throw(convertException(ex, path)));
199
+ }
200
+ try {
201
+ const handle = await dir[kind == 'file' ? 'getFileHandle' : 'getDirectoryHandle'](parts.at(-1));
202
+ if (!this.disableHandleCache)
203
+ this._handles.set(path, handle);
204
+ return handle;
205
+ }
206
+ catch (ex) {
207
+ if (ex.name == 'TypeMismatchError')
208
+ throw withErrno(kind == 'file' ? 'EISDIR' : 'ENOTDIR');
209
+ else
210
+ throw convertException(ex, path);
211
+ }
144
212
  }
145
213
  }
146
214
  const _WebAccess = {
@@ -148,10 +216,10 @@ const _WebAccess = {
148
216
  options: {
149
217
  handle: { type: 'object', required: true },
150
218
  metadata: { type: 'string', required: false },
219
+ disableHandleCache: { type: 'boolean', required: false },
151
220
  },
152
221
  async create(options) {
153
- const fs = new WebAccessFS(options.handle);
154
- await fs._loadHandles('/', options.handle);
222
+ const fs = new WebAccessFS(options.handle, options.disableHandleCache);
155
223
  await fs._loadMetadata(options.metadata);
156
224
  return fs;
157
225
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenfs/dom",
3
- "version": "1.1.9",
3
+ "version": "1.2.1",
4
4
  "description": "DOM backends for ZenFS",
5
5
  "funding": {
6
6
  "type": "individual",