@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 +25 -4
- package/dist/access.js +90 -22
- package/package.json +1 -1
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(
|
|
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(
|
|
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
|
-
|
|
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',
|
|
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.
|
|
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.
|
|
181
|
+
if (!this.disableHandleCache)
|
|
182
|
+
this._handles.set(path, dir);
|
|
135
183
|
return inode;
|
|
136
184
|
}
|
|
137
|
-
get(kind = null, path) {
|
|
138
|
-
const
|
|
139
|
-
if (!
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
},
|