@zenfs/core 0.9.6 → 0.10.0
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/backends/AsyncStore.d.ts +3 -2
- package/dist/backends/AsyncStore.js +40 -33
- package/dist/backends/Fetch.d.ts +84 -0
- package/dist/backends/Fetch.js +171 -0
- package/dist/backends/InMemory.d.ts +1 -1
- package/dist/backends/Index.d.ts +7 -10
- package/dist/backends/Index.js +26 -24
- package/dist/backends/Locked.d.ts +11 -11
- package/dist/backends/Locked.js +50 -49
- package/dist/backends/Overlay.js +22 -22
- package/dist/backends/SyncStore.d.ts +6 -6
- package/dist/backends/SyncStore.js +30 -30
- package/dist/backends/backend.d.ts +5 -4
- package/dist/backends/backend.js +6 -6
- package/dist/backends/port/fs.d.ts +124 -0
- package/dist/backends/port/fs.js +241 -0
- package/dist/backends/port/rpc.d.ts +60 -0
- package/dist/backends/port/rpc.js +71 -0
- package/dist/backends/port/store.d.ts +30 -0
- package/dist/backends/port/store.js +142 -0
- package/dist/browser.min.js +4 -4
- package/dist/browser.min.js.map +4 -4
- package/dist/config.d.ts +9 -11
- package/dist/config.js +13 -13
- package/dist/emulation/async.d.ts +76 -77
- package/dist/emulation/async.js +45 -45
- package/dist/emulation/dir.js +8 -7
- package/dist/emulation/index.d.ts +1 -1
- package/dist/emulation/index.js +1 -1
- package/dist/emulation/path.d.ts +3 -2
- package/dist/emulation/path.js +19 -45
- package/dist/emulation/promises.d.ts +112 -113
- package/dist/emulation/promises.js +167 -173
- package/dist/emulation/shared.d.ts +6 -17
- package/dist/emulation/shared.js +9 -9
- package/dist/emulation/streams.js +3 -2
- package/dist/emulation/sync.d.ts +71 -64
- package/dist/emulation/sync.js +62 -63
- package/dist/{ApiError.d.ts → error.d.ts} +15 -15
- package/dist/error.js +292 -0
- package/dist/file.d.ts +6 -4
- package/dist/file.js +17 -9
- package/dist/filesystem.d.ts +1 -1
- package/dist/filesystem.js +18 -15
- package/dist/index.d.ts +4 -1
- package/dist/index.js +4 -1
- package/dist/mutex.js +4 -3
- package/dist/stats.d.ts +7 -7
- package/dist/stats.js +50 -10
- package/dist/utils.d.ts +10 -9
- package/dist/utils.js +15 -15
- package/package.json +5 -5
- package/readme.md +19 -11
- package/src/backends/AsyncStore.ts +42 -36
- package/src/backends/Fetch.ts +230 -0
- package/src/backends/Index.ts +33 -29
- package/src/backends/Locked.ts +50 -49
- package/src/backends/Overlay.ts +24 -24
- package/src/backends/SyncStore.ts +34 -34
- package/src/backends/backend.ts +13 -11
- package/src/backends/port/fs.ts +308 -0
- package/src/backends/port/readme.md +59 -0
- package/src/backends/port/rpc.ts +144 -0
- package/src/backends/port/store.ts +187 -0
- package/src/config.ts +25 -29
- package/src/emulation/async.ts +191 -199
- package/src/emulation/dir.ts +8 -8
- package/src/emulation/index.ts +1 -1
- package/src/emulation/path.ts +25 -49
- package/src/emulation/promises.ts +286 -287
- package/src/emulation/shared.ts +14 -23
- package/src/emulation/streams.ts +9 -8
- package/src/emulation/sync.ts +182 -182
- package/src/{ApiError.ts → error.ts} +91 -89
- package/src/file.ts +23 -13
- package/src/filesystem.ts +26 -22
- package/src/index.ts +4 -1
- package/src/mutex.ts +6 -4
- package/src/stats.ts +32 -23
- package/src/utils.ts +23 -24
- package/tsconfig.json +4 -3
- package/dist/ApiError.js +0 -292
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { ErrnoError, Errno } from '../error.js';
|
|
2
|
+
import { NoSyncFile } from '../file.js';
|
|
3
|
+
import type { FileSystemMetadata } from '../filesystem.js';
|
|
4
|
+
import { Stats } from '../stats.js';
|
|
5
|
+
import { type ListingTree, FileIndex, type IndexFileInode, AsyncIndexFS } from './Index.js';
|
|
6
|
+
import type { Backend } from './backend.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @hidden
|
|
10
|
+
*/
|
|
11
|
+
function convertError(e: Error): never {
|
|
12
|
+
throw new ErrnoError(Errno.EIO, e.message);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Asynchronously download a file as a buffer or a JSON object.
|
|
17
|
+
* Note that the third function signature with a non-specialized type is
|
|
18
|
+
* invalid, but TypeScript requires it when you specialize string arguments to
|
|
19
|
+
* constants.
|
|
20
|
+
* @hidden
|
|
21
|
+
*/
|
|
22
|
+
async function fetchFile(p: string, type: 'buffer'): Promise<Uint8Array>;
|
|
23
|
+
async function fetchFile<T extends object>(p: string, type: 'json'): Promise<T>;
|
|
24
|
+
async function fetchFile<T extends object>(p: string, type: 'buffer' | 'json'): Promise<T | Uint8Array>;
|
|
25
|
+
async function fetchFile<T extends object>(p: string, type: 'buffer' | 'json'): Promise<T | Uint8Array> {
|
|
26
|
+
const response = await fetch(p).catch(convertError);
|
|
27
|
+
if (!response.ok) {
|
|
28
|
+
throw new ErrnoError(Errno.EIO, 'fetch failed: response returned code ' + response.status);
|
|
29
|
+
}
|
|
30
|
+
switch (type) {
|
|
31
|
+
case 'buffer':
|
|
32
|
+
const arrayBuffer = await response.arrayBuffer().catch(convertError);
|
|
33
|
+
return new Uint8Array(arrayBuffer);
|
|
34
|
+
case 'json':
|
|
35
|
+
return response.json().catch(convertError) as Promise<T>;
|
|
36
|
+
default:
|
|
37
|
+
throw new ErrnoError(Errno.EINVAL, 'Invalid download type: ' + type);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Asynchronously retrieves the size of the given file in bytes.
|
|
43
|
+
* @hidden
|
|
44
|
+
*/
|
|
45
|
+
async function fetchSize(p: string): Promise<number> {
|
|
46
|
+
const response = await fetch(p, { method: 'HEAD' }).catch(convertError);
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
throw new ErrnoError(Errno.EIO, 'fetch failed: HEAD response returned code ' + response.status);
|
|
49
|
+
}
|
|
50
|
+
return parseInt(response.headers.get('Content-Length') || '-1', 10);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Configuration options for FetchFS.
|
|
55
|
+
*/
|
|
56
|
+
export interface FetchOptions {
|
|
57
|
+
/**
|
|
58
|
+
* URL to a file index as a JSON file or the file index object itself.
|
|
59
|
+
* Defaults to `index.json`.
|
|
60
|
+
*/
|
|
61
|
+
index?: string | ListingTree;
|
|
62
|
+
|
|
63
|
+
/** Used as the URL prefix for fetched files.
|
|
64
|
+
* Default: Fetch files relative to the index.
|
|
65
|
+
*/
|
|
66
|
+
baseUrl?: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* A simple filesystem backed by HTTP using the fetch API.
|
|
71
|
+
*
|
|
72
|
+
*
|
|
73
|
+
* Listings objects look like the following:
|
|
74
|
+
*
|
|
75
|
+
* ```json
|
|
76
|
+
* {
|
|
77
|
+
* "home": {
|
|
78
|
+
* "jvilk": {
|
|
79
|
+
* "someFile.txt": null,
|
|
80
|
+
* "someDir": {
|
|
81
|
+
* // Empty directory
|
|
82
|
+
* }
|
|
83
|
+
* }
|
|
84
|
+
* }
|
|
85
|
+
* }
|
|
86
|
+
* ```
|
|
87
|
+
*
|
|
88
|
+
* This example has the folder `/home/jvilk` with subfile `someFile.txt` and subfolder `someDir`.
|
|
89
|
+
*/
|
|
90
|
+
export class FetchFS extends AsyncIndexFS<Stats> {
|
|
91
|
+
public readonly prefixUrl: string;
|
|
92
|
+
|
|
93
|
+
protected _init: Promise<void>;
|
|
94
|
+
|
|
95
|
+
protected async _initialize(index: string | ListingTree): Promise<void> {
|
|
96
|
+
if (typeof index != 'string') {
|
|
97
|
+
this._index = FileIndex.FromListing(index);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
const response = await fetch(index);
|
|
103
|
+
this._index = FileIndex.FromListing((await response.json()) as ListingTree);
|
|
104
|
+
} catch (e) {
|
|
105
|
+
throw new ErrnoError(Errno.EINVAL, 'Invalid or unavailable file listing tree');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
public async ready(): Promise<this> {
|
|
110
|
+
await this._init;
|
|
111
|
+
return this;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
constructor({ index = 'index.json', baseUrl = '' }: FetchOptions) {
|
|
115
|
+
super({});
|
|
116
|
+
|
|
117
|
+
// prefix url must end in a directory separator.
|
|
118
|
+
if (baseUrl.at(-1) != '/') {
|
|
119
|
+
baseUrl += '/';
|
|
120
|
+
}
|
|
121
|
+
this.prefixUrl = baseUrl;
|
|
122
|
+
|
|
123
|
+
this._init = this._initialize(index);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
public metadata(): FileSystemMetadata {
|
|
127
|
+
return {
|
|
128
|
+
...super.metadata(),
|
|
129
|
+
name: FetchFS.name,
|
|
130
|
+
readonly: true,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
public empty(): void {
|
|
135
|
+
for (const file of this._index.files()) {
|
|
136
|
+
delete file.data!.fileData;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Special function: Preload the given file into the index.
|
|
142
|
+
* @param path
|
|
143
|
+
* @param buffer
|
|
144
|
+
*/
|
|
145
|
+
public preloadFile(path: string, buffer: Uint8Array): void {
|
|
146
|
+
const inode = this._index.get(path)!;
|
|
147
|
+
if (!inode) {
|
|
148
|
+
throw ErrnoError.With('ENOENT', path, 'preloadFile');
|
|
149
|
+
}
|
|
150
|
+
if (!inode.isFile()) {
|
|
151
|
+
throw ErrnoError.With('EISDIR', path, 'preloadFile');
|
|
152
|
+
}
|
|
153
|
+
const stats = inode.data!;
|
|
154
|
+
stats.size = buffer.length;
|
|
155
|
+
stats.fileData = buffer;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
protected async statFileInode(inode: IndexFileInode<Stats>, path: string): Promise<Stats> {
|
|
159
|
+
const stats = inode.data!;
|
|
160
|
+
// At this point, a non-opened file will still have default stats from the listing.
|
|
161
|
+
if (stats.size < 0) {
|
|
162
|
+
stats.size = await this._fetchSize(path);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return stats;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
protected async openFileInode(inode: IndexFileInode<Stats>, path: string, flag: string): Promise<NoSyncFile<this>> {
|
|
169
|
+
const stats = inode.data!;
|
|
170
|
+
// Use existing file contents. This maintains the previously-used flag.
|
|
171
|
+
if (stats.fileData) {
|
|
172
|
+
return new NoSyncFile(this, path, flag, new Stats(stats), stats.fileData);
|
|
173
|
+
}
|
|
174
|
+
// @todo be lazier about actually requesting the file
|
|
175
|
+
const data = await this._fetchFile(path, 'buffer');
|
|
176
|
+
// we don't initially have file sizes
|
|
177
|
+
stats.size = data.length;
|
|
178
|
+
stats.fileData = data;
|
|
179
|
+
return new NoSyncFile(this, path, flag, new Stats(stats), data);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private _getRemotePath(filePath: string): string {
|
|
183
|
+
if (filePath.charAt(0) === '/') {
|
|
184
|
+
filePath = filePath.slice(1);
|
|
185
|
+
}
|
|
186
|
+
return this.prefixUrl + filePath;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Asynchronously download the given file.
|
|
191
|
+
*/
|
|
192
|
+
protected _fetchFile(p: string, type: 'buffer'): Promise<Uint8Array>;
|
|
193
|
+
protected _fetchFile(p: string, type: 'json'): Promise<object>;
|
|
194
|
+
protected _fetchFile(p: string, type: 'buffer' | 'json'): Promise<object>;
|
|
195
|
+
protected _fetchFile(p: string, type: 'buffer' | 'json'): Promise<object> {
|
|
196
|
+
return fetchFile(this._getRemotePath(p), type);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Only requests the HEAD content, for the file size.
|
|
201
|
+
*/
|
|
202
|
+
protected _fetchSize(path: string): Promise<number> {
|
|
203
|
+
return fetchSize(this._getRemotePath(path));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export const Fetch = {
|
|
208
|
+
name: 'Fetch',
|
|
209
|
+
|
|
210
|
+
options: {
|
|
211
|
+
index: {
|
|
212
|
+
type: ['string', 'object'],
|
|
213
|
+
required: false,
|
|
214
|
+
description: 'URL to a file index as a JSON file or the file index object itself, generated with the make_http_index script. Defaults to `index.json`.',
|
|
215
|
+
},
|
|
216
|
+
baseUrl: {
|
|
217
|
+
type: 'string',
|
|
218
|
+
required: false,
|
|
219
|
+
description: 'Used as the URL prefix for fetched files. Default: Fetch files relative to the index.',
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
isAvailable(): boolean {
|
|
224
|
+
return typeof globalThis.fetch == 'function';
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
create(options: FetchOptions) {
|
|
228
|
+
return new FetchFS(options);
|
|
229
|
+
},
|
|
230
|
+
} as const satisfies Backend<FetchFS, FetchOptions>;
|
package/src/backends/Index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ErrnoError, Errno } from '../error.js';
|
|
2
2
|
import type { Cred } from '../cred.js';
|
|
3
3
|
import { basename, dirname, join } from '../emulation/path.js';
|
|
4
4
|
import { NoSyncFile, flagToMode, isWriteable } from '../file.js';
|
|
@@ -86,7 +86,10 @@ export class FileIndex<TData> {
|
|
|
86
86
|
for (const dir of this._index.values()) {
|
|
87
87
|
for (const file of dir.listing) {
|
|
88
88
|
const item = dir.get(file);
|
|
89
|
-
if (!item
|
|
89
|
+
if (!item) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (!item.isFile()) {
|
|
90
93
|
continue;
|
|
91
94
|
}
|
|
92
95
|
|
|
@@ -110,10 +113,10 @@ export class FileIndex<TData> {
|
|
|
110
113
|
*/
|
|
111
114
|
public add(path: string, inode: IndexInode<TData>): boolean {
|
|
112
115
|
if (!inode) {
|
|
113
|
-
throw new
|
|
116
|
+
throw new ErrnoError(Errno.EINVAL, 'Inode must be specified', path, 'FileIndex.add');
|
|
114
117
|
}
|
|
115
118
|
if (!path.startsWith('/')) {
|
|
116
|
-
throw new
|
|
119
|
+
throw new ErrnoError(Errno.EINVAL, 'Path not absolute', path, 'FileIndex.add');
|
|
117
120
|
}
|
|
118
121
|
|
|
119
122
|
// Check if it already exists.
|
|
@@ -123,11 +126,12 @@ export class FileIndex<TData> {
|
|
|
123
126
|
|
|
124
127
|
const dirpath = dirname(path);
|
|
125
128
|
|
|
129
|
+
const hasParent = this._index.has(dirpath);
|
|
130
|
+
|
|
131
|
+
const parent = hasParent ? this._index.get(dirpath)! : new IndexDirInode<TData>();
|
|
132
|
+
|
|
126
133
|
// Try to add to its parent directory first.
|
|
127
|
-
|
|
128
|
-
if (!parent && path != '/') {
|
|
129
|
-
// Create parent.
|
|
130
|
-
parent = new IndexDirInode<TData>();
|
|
134
|
+
if (!hasParent && path != '/') {
|
|
131
135
|
if (!this.add(dirpath, parent)) {
|
|
132
136
|
return false;
|
|
133
137
|
}
|
|
@@ -184,7 +188,7 @@ export class FileIndex<TData> {
|
|
|
184
188
|
* @return The removed item,
|
|
185
189
|
* or null if it did not exist.
|
|
186
190
|
*/
|
|
187
|
-
public remove(path: string): IndexInode<TData> |
|
|
191
|
+
public remove(path: string): IndexInode<TData> | void {
|
|
188
192
|
const dirpath = dirname(path);
|
|
189
193
|
|
|
190
194
|
// Try to remove it from its parent directory first.
|
|
@@ -217,7 +221,7 @@ export class FileIndex<TData> {
|
|
|
217
221
|
* Retrieves the directory listing of the given path.
|
|
218
222
|
* @return An array of files in the given path, or 'null' if it does not exist.
|
|
219
223
|
*/
|
|
220
|
-
public ls(path: string): string[] |
|
|
224
|
+
public ls(path: string): string[] | void {
|
|
221
225
|
return this._index.get(path)?.listing;
|
|
222
226
|
}
|
|
223
227
|
|
|
@@ -225,7 +229,7 @@ export class FileIndex<TData> {
|
|
|
225
229
|
* Returns the inode of the given item.
|
|
226
230
|
* @return Returns null if the item does not exist.
|
|
227
231
|
*/
|
|
228
|
-
public get(path: string): IndexInode<TData> | null {
|
|
232
|
+
public get(path: string): IndexInode<TData> | void | null {
|
|
229
233
|
const dirpath = dirname(path);
|
|
230
234
|
|
|
231
235
|
// Retrieve from its parent directory.
|
|
@@ -316,7 +320,7 @@ export class IndexDirInode<TData> extends IndexInode<TData> {
|
|
|
316
320
|
* Returns the inode for the indicated item, or null if it does not exist.
|
|
317
321
|
* @param path Name of item in this directory.
|
|
318
322
|
*/
|
|
319
|
-
public get(path: string): IndexInode<TData> |
|
|
323
|
+
public get(path: string): IndexInode<TData> | void {
|
|
320
324
|
return this._listing.get(path);
|
|
321
325
|
}
|
|
322
326
|
/**
|
|
@@ -340,7 +344,7 @@ export class IndexDirInode<TData> extends IndexInode<TData> {
|
|
|
340
344
|
* @return Returns the item
|
|
341
345
|
* removed, or null if the item did not exist.
|
|
342
346
|
*/
|
|
343
|
-
public remove(p: string): IndexInode<TData> |
|
|
347
|
+
public remove(p: string): IndexInode<TData> | void {
|
|
344
348
|
const item = this._listing.get(p);
|
|
345
349
|
if (!item) {
|
|
346
350
|
return;
|
|
@@ -361,7 +365,7 @@ export abstract class IndexFS<TData> extends Readonly(FileSystem) {
|
|
|
361
365
|
public async stat(path: string): Promise<Stats> {
|
|
362
366
|
const inode = this._index.get(path);
|
|
363
367
|
if (!inode) {
|
|
364
|
-
throw
|
|
368
|
+
throw ErrnoError.With('ENOENT', path, 'stat');
|
|
365
369
|
}
|
|
366
370
|
|
|
367
371
|
if (inode.isDirectory()) {
|
|
@@ -372,13 +376,13 @@ export abstract class IndexFS<TData> extends Readonly(FileSystem) {
|
|
|
372
376
|
return this.statFileInode(inode, path);
|
|
373
377
|
}
|
|
374
378
|
|
|
375
|
-
throw new
|
|
379
|
+
throw new ErrnoError(Errno.EINVAL, 'Invalid inode.');
|
|
376
380
|
}
|
|
377
381
|
|
|
378
382
|
public statSync(path: string): Stats {
|
|
379
383
|
const inode = this._index.get(path);
|
|
380
384
|
if (!inode) {
|
|
381
|
-
throw
|
|
385
|
+
throw ErrnoError.With('ENOENT', path, 'stat');
|
|
382
386
|
}
|
|
383
387
|
|
|
384
388
|
if (inode.isDirectory()) {
|
|
@@ -389,24 +393,24 @@ export abstract class IndexFS<TData> extends Readonly(FileSystem) {
|
|
|
389
393
|
return this.statFileInodeSync(inode, path);
|
|
390
394
|
}
|
|
391
395
|
|
|
392
|
-
throw new
|
|
396
|
+
throw new ErrnoError(Errno.EINVAL, 'Invalid inode.');
|
|
393
397
|
}
|
|
394
398
|
|
|
395
399
|
public async openFile(path: string, flag: string, cred: Cred): Promise<NoSyncFile<this>> {
|
|
396
400
|
if (isWriteable(flag)) {
|
|
397
401
|
// You can't write to files on this file system.
|
|
398
|
-
throw new
|
|
402
|
+
throw new ErrnoError(Errno.EPERM, path);
|
|
399
403
|
}
|
|
400
404
|
|
|
401
405
|
// Check if the path exists, and is a file.
|
|
402
406
|
const inode = this._index.get(path);
|
|
403
407
|
|
|
404
408
|
if (!inode) {
|
|
405
|
-
throw
|
|
409
|
+
throw ErrnoError.With('ENOENT', path, 'openFile');
|
|
406
410
|
}
|
|
407
411
|
|
|
408
412
|
if (!inode.toStats().hasAccess(flagToMode(flag), cred)) {
|
|
409
|
-
throw
|
|
413
|
+
throw ErrnoError.With('EACCES', path, 'openFile');
|
|
410
414
|
}
|
|
411
415
|
|
|
412
416
|
if (inode.isDirectory()) {
|
|
@@ -420,18 +424,18 @@ export abstract class IndexFS<TData> extends Readonly(FileSystem) {
|
|
|
420
424
|
public openFileSync(path: string, flag: string, cred: Cred): NoSyncFile<this> {
|
|
421
425
|
if (isWriteable(flag)) {
|
|
422
426
|
// You can't write to files on this file system.
|
|
423
|
-
throw new
|
|
427
|
+
throw new ErrnoError(Errno.EPERM, path);
|
|
424
428
|
}
|
|
425
429
|
|
|
426
430
|
// Check if the path exists, and is a file.
|
|
427
431
|
const inode = this._index.get(path);
|
|
428
432
|
|
|
429
433
|
if (!inode) {
|
|
430
|
-
throw
|
|
434
|
+
throw ErrnoError.With('ENOENT', path, 'openFile');
|
|
431
435
|
}
|
|
432
436
|
|
|
433
437
|
if (!inode.toStats().hasAccess(flagToMode(flag), cred)) {
|
|
434
|
-
throw
|
|
438
|
+
throw ErrnoError.With('EACCES', path, 'openFile');
|
|
435
439
|
}
|
|
436
440
|
|
|
437
441
|
if (inode.isDirectory()) {
|
|
@@ -446,28 +450,28 @@ export abstract class IndexFS<TData> extends Readonly(FileSystem) {
|
|
|
446
450
|
// Check if it exists.
|
|
447
451
|
const inode = this._index.get(path);
|
|
448
452
|
if (!inode) {
|
|
449
|
-
throw
|
|
453
|
+
throw ErrnoError.With('ENOENT', path, 'readdir');
|
|
450
454
|
}
|
|
451
455
|
|
|
452
456
|
if (inode.isDirectory()) {
|
|
453
457
|
return inode.listing;
|
|
454
458
|
}
|
|
455
459
|
|
|
456
|
-
throw
|
|
460
|
+
throw ErrnoError.With('ENOTDIR', path, 'readdir');
|
|
457
461
|
}
|
|
458
462
|
|
|
459
463
|
public readdirSync(path: string): string[] {
|
|
460
464
|
// Check if it exists.
|
|
461
465
|
const inode = this._index.get(path);
|
|
462
466
|
if (!inode) {
|
|
463
|
-
throw
|
|
467
|
+
throw ErrnoError.With('ENOENT', path, 'readdir');
|
|
464
468
|
}
|
|
465
469
|
|
|
466
470
|
if (inode.isDirectory()) {
|
|
467
471
|
return inode.listing;
|
|
468
472
|
}
|
|
469
473
|
|
|
470
|
-
throw
|
|
474
|
+
throw ErrnoError.With('ENOTDIR', path, 'readdir');
|
|
471
475
|
}
|
|
472
476
|
|
|
473
477
|
protected abstract statFileInode(inode: IndexFileInode<TData>, path: string): Promise<Stats>;
|
|
@@ -491,10 +495,10 @@ export abstract class SyncIndexFS<TData> extends IndexFS<TData> {
|
|
|
491
495
|
|
|
492
496
|
export abstract class AsyncIndexFS<TData> extends IndexFS<TData> {
|
|
493
497
|
protected statFileInodeSync(inode: IndexFileInode<TData>, path: string): Stats {
|
|
494
|
-
throw
|
|
498
|
+
throw ErrnoError.With('ENOSYS', path, 'AsyncIndexFS.statFileInodeSync');
|
|
495
499
|
}
|
|
496
500
|
|
|
497
501
|
protected openFileInodeSync(inode: IndexFileInode<TData>, path: string, flag: string): NoSyncFile<this> {
|
|
498
|
-
throw
|
|
502
|
+
throw ErrnoError.With('ENOSYS', path, 'AsyncIndexFS.openFileInodeSync');
|
|
499
503
|
}
|
|
500
504
|
}
|
package/src/backends/Locked.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ErrnoError } from '../error.js';
|
|
1
2
|
import type { Cred } from '../cred.js';
|
|
2
3
|
import type { File } from '../file.js';
|
|
3
4
|
import type { FileSystem, FileSystemMetadata } from '../filesystem.js';
|
|
@@ -39,23 +40,23 @@ export class LockedFS<FS extends FileSystem> implements FileSystem {
|
|
|
39
40
|
|
|
40
41
|
public renameSync(oldPath: string, newPath: string, cred: Cred): void {
|
|
41
42
|
if (this._mu.isLocked(oldPath)) {
|
|
42
|
-
throw
|
|
43
|
+
throw ErrnoError.With('EBUSY', oldPath, 'rename');
|
|
43
44
|
}
|
|
44
45
|
return this.fs.renameSync(oldPath, newPath, cred);
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
public async stat(
|
|
48
|
-
await this._mu.lock(
|
|
49
|
-
const stats = await this.fs.stat(
|
|
50
|
-
this._mu.unlock(
|
|
48
|
+
public async stat(path: string, cred: Cred): Promise<Stats> {
|
|
49
|
+
await this._mu.lock(path);
|
|
50
|
+
const stats = await this.fs.stat(path, cred);
|
|
51
|
+
this._mu.unlock(path);
|
|
51
52
|
return stats;
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
public statSync(
|
|
55
|
-
if (this._mu.isLocked(
|
|
56
|
-
throw
|
|
55
|
+
public statSync(path: string, cred: Cred): Stats {
|
|
56
|
+
if (this._mu.isLocked(path)) {
|
|
57
|
+
throw ErrnoError.With('EBUSY', path, 'stat');
|
|
57
58
|
}
|
|
58
|
-
return this.fs.statSync(
|
|
59
|
+
return this.fs.statSync(path, cred);
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
public async openFile(path: string, flag: string, cred: Cred): Promise<File> {
|
|
@@ -67,7 +68,7 @@ export class LockedFS<FS extends FileSystem> implements FileSystem {
|
|
|
67
68
|
|
|
68
69
|
public openFileSync(path: string, flag: string, cred: Cred): File {
|
|
69
70
|
if (this._mu.isLocked(path)) {
|
|
70
|
-
throw
|
|
71
|
+
throw ErrnoError.With('EBUSY', path, 'openFile');
|
|
71
72
|
}
|
|
72
73
|
return this.fs.openFileSync(path, flag, cred);
|
|
73
74
|
}
|
|
@@ -81,7 +82,7 @@ export class LockedFS<FS extends FileSystem> implements FileSystem {
|
|
|
81
82
|
|
|
82
83
|
public createFileSync(path: string, flag: string, mode: number, cred: Cred): File {
|
|
83
84
|
if (this._mu.isLocked(path)) {
|
|
84
|
-
throw
|
|
85
|
+
throw ErrnoError.With('EBUSY', path, 'createFile');
|
|
85
86
|
}
|
|
86
87
|
return this.fs.createFileSync(path, flag, mode, cred);
|
|
87
88
|
}
|
|
@@ -92,65 +93,65 @@ export class LockedFS<FS extends FileSystem> implements FileSystem {
|
|
|
92
93
|
this._mu.unlock(p);
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
public unlinkSync(
|
|
96
|
-
if (this._mu.isLocked(
|
|
97
|
-
throw
|
|
96
|
+
public unlinkSync(path: string, cred: Cred): void {
|
|
97
|
+
if (this._mu.isLocked(path)) {
|
|
98
|
+
throw ErrnoError.With('EBUSY', path, 'unlink');
|
|
98
99
|
}
|
|
99
|
-
return this.fs.unlinkSync(
|
|
100
|
+
return this.fs.unlinkSync(path, cred);
|
|
100
101
|
}
|
|
101
102
|
|
|
102
|
-
public async rmdir(
|
|
103
|
-
await this._mu.lock(
|
|
104
|
-
await this.fs.rmdir(
|
|
105
|
-
this._mu.unlock(
|
|
103
|
+
public async rmdir(path: string, cred: Cred): Promise<void> {
|
|
104
|
+
await this._mu.lock(path);
|
|
105
|
+
await this.fs.rmdir(path, cred);
|
|
106
|
+
this._mu.unlock(path);
|
|
106
107
|
}
|
|
107
108
|
|
|
108
|
-
public rmdirSync(
|
|
109
|
-
if (this._mu.isLocked(
|
|
110
|
-
throw
|
|
109
|
+
public rmdirSync(path: string, cred: Cred): void {
|
|
110
|
+
if (this._mu.isLocked(path)) {
|
|
111
|
+
throw ErrnoError.With('EBUSY', path, 'rmdir');
|
|
111
112
|
}
|
|
112
|
-
return this.fs.rmdirSync(
|
|
113
|
+
return this.fs.rmdirSync(path, cred);
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
public async mkdir(
|
|
116
|
-
await this._mu.lock(
|
|
117
|
-
await this.fs.mkdir(
|
|
118
|
-
this._mu.unlock(
|
|
116
|
+
public async mkdir(path: string, mode: number, cred: Cred): Promise<void> {
|
|
117
|
+
await this._mu.lock(path);
|
|
118
|
+
await this.fs.mkdir(path, mode, cred);
|
|
119
|
+
this._mu.unlock(path);
|
|
119
120
|
}
|
|
120
121
|
|
|
121
|
-
public mkdirSync(
|
|
122
|
-
if (this._mu.isLocked(
|
|
123
|
-
throw
|
|
122
|
+
public mkdirSync(path: string, mode: number, cred: Cred): void {
|
|
123
|
+
if (this._mu.isLocked(path)) {
|
|
124
|
+
throw ErrnoError.With('EBUSY', path, 'mkdir');
|
|
124
125
|
}
|
|
125
|
-
return this.fs.mkdirSync(
|
|
126
|
+
return this.fs.mkdirSync(path, mode, cred);
|
|
126
127
|
}
|
|
127
128
|
|
|
128
|
-
public async readdir(
|
|
129
|
-
await this._mu.lock(
|
|
130
|
-
const files = await this.fs.readdir(
|
|
131
|
-
this._mu.unlock(
|
|
129
|
+
public async readdir(path: string, cred: Cred): Promise<string[]> {
|
|
130
|
+
await this._mu.lock(path);
|
|
131
|
+
const files = await this.fs.readdir(path, cred);
|
|
132
|
+
this._mu.unlock(path);
|
|
132
133
|
return files;
|
|
133
134
|
}
|
|
134
135
|
|
|
135
|
-
public readdirSync(
|
|
136
|
-
if (this._mu.isLocked(
|
|
137
|
-
throw
|
|
136
|
+
public readdirSync(path: string, cred: Cred): string[] {
|
|
137
|
+
if (this._mu.isLocked(path)) {
|
|
138
|
+
throw ErrnoError.With('EBUSY', path, 'readdir');
|
|
138
139
|
}
|
|
139
|
-
return this.fs.readdirSync(
|
|
140
|
+
return this.fs.readdirSync(path, cred);
|
|
140
141
|
}
|
|
141
142
|
|
|
142
|
-
public async exists(
|
|
143
|
-
await this._mu.lock(
|
|
144
|
-
const exists = await this.fs.exists(
|
|
145
|
-
this._mu.unlock(
|
|
143
|
+
public async exists(path: string, cred: Cred): Promise<boolean> {
|
|
144
|
+
await this._mu.lock(path);
|
|
145
|
+
const exists = await this.fs.exists(path, cred);
|
|
146
|
+
this._mu.unlock(path);
|
|
146
147
|
return exists;
|
|
147
148
|
}
|
|
148
149
|
|
|
149
|
-
public existsSync(
|
|
150
|
-
if (this._mu.isLocked(
|
|
151
|
-
throw
|
|
150
|
+
public existsSync(path: string, cred: Cred): boolean {
|
|
151
|
+
if (this._mu.isLocked(path)) {
|
|
152
|
+
throw ErrnoError.With('EBUSY', path, 'exists');
|
|
152
153
|
}
|
|
153
|
-
return this.fs.existsSync(
|
|
154
|
+
return this.fs.existsSync(path, cred);
|
|
154
155
|
}
|
|
155
156
|
|
|
156
157
|
public async link(srcpath: string, dstpath: string, cred: Cred): Promise<void> {
|
|
@@ -161,7 +162,7 @@ export class LockedFS<FS extends FileSystem> implements FileSystem {
|
|
|
161
162
|
|
|
162
163
|
public linkSync(srcpath: string, dstpath: string, cred: Cred): void {
|
|
163
164
|
if (this._mu.isLocked(srcpath)) {
|
|
164
|
-
throw
|
|
165
|
+
throw ErrnoError.With('EBUSY', srcpath, 'link');
|
|
165
166
|
}
|
|
166
167
|
return this.fs.linkSync(srcpath, dstpath, cred);
|
|
167
168
|
}
|
|
@@ -174,7 +175,7 @@ export class LockedFS<FS extends FileSystem> implements FileSystem {
|
|
|
174
175
|
|
|
175
176
|
public syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void {
|
|
176
177
|
if (this._mu.isLocked(path)) {
|
|
177
|
-
throw
|
|
178
|
+
throw ErrnoError.With('EBUSY', path, 'sync');
|
|
178
179
|
}
|
|
179
180
|
return this.fs.syncSync(path, data, stats);
|
|
180
181
|
}
|