@zenfs/core 0.16.4 → 0.17.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/backend.d.ts +3 -4
- package/dist/backends/fetch.d.ts +8 -3
- package/dist/backends/fetch.js +3 -2
- package/dist/backends/{index/fs.d.ts → file_index.d.ts} +49 -10
- package/dist/backends/{index/fs.js → file_index.js} +84 -5
- package/dist/backends/memory.d.ts +6 -1
- package/dist/backends/memory.js +2 -1
- package/dist/backends/overlay.d.ts +16 -16
- package/dist/backends/overlay.js +59 -82
- package/dist/backends/port/fs.d.ts +6 -2
- package/dist/backends/port/fs.js +4 -2
- package/dist/backends/store/fs.js +484 -304
- package/dist/backends/store/simple.js +5 -1
- package/dist/backends/store/store.d.ts +4 -1
- package/dist/backends/store/store.js +9 -5
- package/dist/browser.min.js +4 -4
- package/dist/browser.min.js.map +4 -4
- package/dist/config.d.ts +3 -3
- package/dist/emulation/async.d.ts +0 -3
- package/dist/emulation/async.js +6 -2
- package/dist/emulation/dir.d.ts +4 -0
- package/dist/emulation/dir.js +8 -6
- package/dist/emulation/promises.d.ts +1 -3
- package/dist/emulation/promises.js +25 -2
- package/dist/emulation/sync.js +0 -1
- package/dist/emulation/watchers.d.ts +9 -4
- package/dist/emulation/watchers.js +7 -0
- package/dist/file.d.ts +17 -1
- package/dist/file.js +86 -1
- package/dist/filesystem.d.ts +0 -63
- package/dist/filesystem.js +0 -311
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -2
- package/dist/mixins/async.d.ts +39 -0
- package/dist/mixins/async.js +216 -0
- package/dist/mixins/mutexed.d.ts +33 -0
- package/dist/mixins/mutexed.js +465 -0
- package/dist/mixins/readonly.d.ts +25 -0
- package/dist/mixins/readonly.js +57 -0
- package/dist/mixins/shared.d.ts +12 -0
- package/dist/mixins/shared.js +4 -0
- package/dist/mixins/sync.d.ts +6 -0
- package/dist/mixins/sync.js +43 -0
- package/package.json +1 -1
- package/src/backends/backend.ts +3 -4
- package/src/backends/fetch.ts +7 -3
- package/src/backends/{index/fs.ts → file_index.ts} +106 -8
- package/src/backends/memory.ts +5 -1
- package/src/backends/overlay.ts +64 -90
- package/src/backends/port/fs.ts +7 -2
- package/src/backends/{index/readme.md → readme.md} +1 -1
- package/src/backends/store/fs.ts +97 -155
- package/src/backends/store/simple.ts +5 -1
- package/src/backends/store/store.ts +10 -5
- package/src/config.ts +3 -1
- package/src/emulation/async.ts +15 -6
- package/src/emulation/dir.ts +19 -16
- package/src/emulation/promises.ts +28 -6
- package/src/emulation/sync.ts +1 -2
- package/src/emulation/watchers.ts +10 -4
- package/src/file.ts +94 -1
- package/src/filesystem.ts +3 -366
- package/src/index.ts +1 -2
- package/src/mixins/async.ts +211 -0
- package/src/mixins/mutexed.ts +245 -0
- package/src/mixins/readonly.ts +97 -0
- package/src/mixins/shared.ts +20 -0
- package/src/mixins/sync.ts +59 -0
- package/dist/backends/index/index.d.ts +0 -43
- package/dist/backends/index/index.js +0 -83
- package/dist/backends/locked.d.ts +0 -92
- package/dist/backends/locked.js +0 -487
- package/src/backends/index/index.ts +0 -104
- package/src/backends/locked.ts +0 -264
|
@@ -1,11 +1,109 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
1
|
+
/* Note: this file is named file_index.ts because Typescript has special behavior regarding index.ts which can't be disabled. */
|
|
2
|
+
|
|
3
|
+
import { isJSON } from 'utilium';
|
|
4
|
+
import type { Cred } from '../cred.js';
|
|
5
|
+
import { basename, dirname } from '../emulation/path.js';
|
|
6
|
+
import { Errno, ErrnoError } from '../error.js';
|
|
7
|
+
import { NoSyncFile, flagToMode, isWriteable } from '../file.js';
|
|
8
|
+
import { FileSystem } from '../filesystem.js';
|
|
9
|
+
import { Readonly } from '../mixins/readonly.js';
|
|
10
|
+
import type { StatsLike } from '../stats.js';
|
|
11
|
+
import { Stats } from '../stats.js';
|
|
12
|
+
import { decode, encode } from '../utils.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* An Index in JSON form
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
export interface IndexData {
|
|
19
|
+
version: 1;
|
|
20
|
+
entries: Record<string, StatsLike<number>>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const version = 1;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* An index of files
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
export class Index extends Map<string, Stats> {
|
|
30
|
+
/**
|
|
31
|
+
* Convience method
|
|
32
|
+
*/
|
|
33
|
+
public files(): Map<string, Stats> {
|
|
34
|
+
const files = new Map<string, Stats>();
|
|
35
|
+
for (const [path, stats] of this) {
|
|
36
|
+
if (stats.isFile()) {
|
|
37
|
+
files.set(path, stats);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return files;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Converts the index to JSON
|
|
45
|
+
*/
|
|
46
|
+
public toJSON(): IndexData {
|
|
47
|
+
return {
|
|
48
|
+
version,
|
|
49
|
+
entries: Object.fromEntries(this),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Converts the index to a string
|
|
55
|
+
*/
|
|
56
|
+
public toString(): string {
|
|
57
|
+
return JSON.stringify(this.toJSON());
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Returns the files in the directory `dir`.
|
|
62
|
+
* This is expensive so it is only called once per directory.
|
|
63
|
+
*/
|
|
64
|
+
protected dirEntries(dir: string): string[] {
|
|
65
|
+
const entries = [];
|
|
66
|
+
for (const entry of this.keys()) {
|
|
67
|
+
if (dirname(entry) == dir) {
|
|
68
|
+
entries.push(basename(entry));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return entries;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Loads the index from JSON data
|
|
76
|
+
*/
|
|
77
|
+
public fromJSON(json: IndexData): void {
|
|
78
|
+
if (json.version != version) {
|
|
79
|
+
throw new ErrnoError(Errno.EINVAL, 'Index version mismatch');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
this.clear();
|
|
83
|
+
|
|
84
|
+
for (const [path, data] of Object.entries(json.entries)) {
|
|
85
|
+
const stats = new Stats(data);
|
|
86
|
+
if (stats.isDirectory()) {
|
|
87
|
+
stats.fileData = encode(JSON.stringify(this.dirEntries(path)));
|
|
88
|
+
}
|
|
89
|
+
this.set(path, stats);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Parses an index from a string
|
|
95
|
+
*/
|
|
96
|
+
public static parse(data: string): Index {
|
|
97
|
+
if (!isJSON(data)) {
|
|
98
|
+
throw new ErrnoError(Errno.EINVAL, 'Invalid JSON');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const json = JSON.parse(data) as IndexData;
|
|
102
|
+
const index = new Index();
|
|
103
|
+
index.fromJSON(json);
|
|
104
|
+
return index;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
9
107
|
|
|
10
108
|
export abstract class IndexFS extends Readonly(FileSystem) {
|
|
11
109
|
protected index: Index = new Index();
|
package/src/backends/memory.ts
CHANGED
|
@@ -26,7 +26,7 @@ export class InMemoryStore extends Map<Ino, Uint8Array> implements SimpleSyncSto
|
|
|
26
26
|
* A simple in-memory file system backed by an InMemoryStore.
|
|
27
27
|
* Files are not persisted across page loads.
|
|
28
28
|
*/
|
|
29
|
-
export const
|
|
29
|
+
export const _InMemory = {
|
|
30
30
|
name: 'InMemory',
|
|
31
31
|
isAvailable(): boolean {
|
|
32
32
|
return true;
|
|
@@ -44,3 +44,7 @@ export const InMemory = {
|
|
|
44
44
|
return fs;
|
|
45
45
|
},
|
|
46
46
|
} as const satisfies Backend<StoreFS<InMemoryStore>, { name?: string }>;
|
|
47
|
+
type _inmemory = typeof _InMemory;
|
|
48
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
49
|
+
interface InMemory extends _inmemory {}
|
|
50
|
+
export const InMemory: InMemory = _InMemory;
|
package/src/backends/overlay.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { ErrnoError, Errno } from '../error.js';
|
|
|
4
4
|
import type { File } from '../file.js';
|
|
5
5
|
import { PreloadFile, parseFlag } from '../file.js';
|
|
6
6
|
import { Stats } from '../stats.js';
|
|
7
|
-
import {
|
|
7
|
+
import { Mutexed } from '../mixins/mutexed.js';
|
|
8
8
|
import { dirname } from '../emulation/path.js';
|
|
9
9
|
import type { Cred } from '../cred.js';
|
|
10
10
|
import { rootCred } from '../cred.js';
|
|
@@ -33,19 +33,19 @@ export interface OverlayOptions {
|
|
|
33
33
|
* OverlayFS makes a read-only filesystem writable by storing writes on a second, writable file system.
|
|
34
34
|
* Deletes are persisted via metadata stored on the writable file system.
|
|
35
35
|
*
|
|
36
|
-
* This class contains no locking whatsoever. It is
|
|
36
|
+
* This class contains no locking whatsoever. It is mutexed to prevent races.
|
|
37
37
|
*
|
|
38
38
|
* @internal
|
|
39
39
|
*/
|
|
40
|
-
export class
|
|
40
|
+
export class UnmutexedOverlayFS extends FileSystem {
|
|
41
41
|
async ready(): Promise<void> {
|
|
42
|
-
await this.
|
|
43
|
-
await this.
|
|
42
|
+
await this.readable.ready();
|
|
43
|
+
await this.writable.ready();
|
|
44
44
|
await this._ready;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
public readonly writable: FileSystem;
|
|
48
|
+
public readonly readable: FileSystem;
|
|
49
49
|
private _isInitialized: boolean = false;
|
|
50
50
|
private _deletedFiles: Set<string> = new Set();
|
|
51
51
|
private _deleteLog: string = '';
|
|
@@ -61,9 +61,9 @@ export class UnlockedOverlayFS extends FileSystem {
|
|
|
61
61
|
|
|
62
62
|
constructor({ writable, readable }: OverlayOptions) {
|
|
63
63
|
super();
|
|
64
|
-
this.
|
|
65
|
-
this.
|
|
66
|
-
if (this.
|
|
64
|
+
this.writable = writable;
|
|
65
|
+
this.readable = readable;
|
|
66
|
+
if (this.writable.metadata().readonly) {
|
|
67
67
|
throw new ErrnoError(Errno.EINVAL, 'Writable file system must be writable.');
|
|
68
68
|
}
|
|
69
69
|
this._ready = this._initialize();
|
|
@@ -76,26 +76,19 @@ export class UnlockedOverlayFS extends FileSystem {
|
|
|
76
76
|
};
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
public getOverlayedFileSystems(): OverlayOptions {
|
|
80
|
-
return {
|
|
81
|
-
readable: this._readable,
|
|
82
|
-
writable: this._writable,
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
79
|
public async sync(path: string, data: Uint8Array, stats: Readonly<Stats>): Promise<void> {
|
|
87
80
|
const cred = stats.cred(0, 0);
|
|
88
81
|
await this.createParentDirectories(path, cred);
|
|
89
|
-
if (!(await this.
|
|
90
|
-
await this.
|
|
82
|
+
if (!(await this.writable.exists(path, cred))) {
|
|
83
|
+
await this.writable.createFile(path, 'w', 0o644, cred);
|
|
91
84
|
}
|
|
92
|
-
await this.
|
|
85
|
+
await this.writable.sync(path, data, stats);
|
|
93
86
|
}
|
|
94
87
|
|
|
95
88
|
public syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void {
|
|
96
89
|
const cred = stats.cred(0, 0);
|
|
97
90
|
this.createParentDirectoriesSync(path, cred);
|
|
98
|
-
this.
|
|
91
|
+
this.writable.syncSync(path, data, stats);
|
|
99
92
|
}
|
|
100
93
|
|
|
101
94
|
/**
|
|
@@ -109,7 +102,7 @@ export class UnlockedOverlayFS extends FileSystem {
|
|
|
109
102
|
|
|
110
103
|
// Read deletion log, process into metadata.
|
|
111
104
|
try {
|
|
112
|
-
const file = await this.
|
|
105
|
+
const file = await this.writable.openFile(deletionLogPath, parseFlag('r'), rootCred);
|
|
113
106
|
const { size } = await file.stat();
|
|
114
107
|
const { buffer } = await file.read(new Uint8Array(size));
|
|
115
108
|
this._deleteLog = decode(buffer);
|
|
@@ -138,7 +131,7 @@ export class UnlockedOverlayFS extends FileSystem {
|
|
|
138
131
|
this.checkPath(newPath);
|
|
139
132
|
|
|
140
133
|
try {
|
|
141
|
-
await this.
|
|
134
|
+
await this.writable.rename(oldPath, newPath, cred);
|
|
142
135
|
} catch (e) {
|
|
143
136
|
if (this._deletedFiles.has(oldPath)) {
|
|
144
137
|
throw ErrnoError.With('ENOENT', oldPath, 'rename');
|
|
@@ -152,7 +145,7 @@ export class UnlockedOverlayFS extends FileSystem {
|
|
|
152
145
|
this.checkPath(newPath);
|
|
153
146
|
|
|
154
147
|
try {
|
|
155
|
-
this.
|
|
148
|
+
this.writable.renameSync(oldPath, newPath, cred);
|
|
156
149
|
} catch (e) {
|
|
157
150
|
if (this._deletedFiles.has(oldPath)) {
|
|
158
151
|
throw ErrnoError.With('ENOENT', oldPath, 'rename');
|
|
@@ -163,12 +156,12 @@ export class UnlockedOverlayFS extends FileSystem {
|
|
|
163
156
|
public async stat(path: string, cred: Cred): Promise<Stats> {
|
|
164
157
|
this.checkInitialized();
|
|
165
158
|
try {
|
|
166
|
-
return await this.
|
|
159
|
+
return await this.writable.stat(path, cred);
|
|
167
160
|
} catch (e) {
|
|
168
161
|
if (this._deletedFiles.has(path)) {
|
|
169
162
|
throw ErrnoError.With('ENOENT', path, 'stat');
|
|
170
163
|
}
|
|
171
|
-
const oldStat = new Stats(await this.
|
|
164
|
+
const oldStat = new Stats(await this.readable.stat(path, cred));
|
|
172
165
|
// Make the oldStat's mode writable. Preserve the topmost part of the mode, which specifies the type
|
|
173
166
|
oldStat.mode |= 0o222;
|
|
174
167
|
return oldStat;
|
|
@@ -178,12 +171,12 @@ export class UnlockedOverlayFS extends FileSystem {
|
|
|
178
171
|
public statSync(path: string, cred: Cred): Stats {
|
|
179
172
|
this.checkInitialized();
|
|
180
173
|
try {
|
|
181
|
-
return this.
|
|
174
|
+
return this.writable.statSync(path, cred);
|
|
182
175
|
} catch (e) {
|
|
183
176
|
if (this._deletedFiles.has(path)) {
|
|
184
177
|
throw ErrnoError.With('ENOENT', path, 'stat');
|
|
185
178
|
}
|
|
186
|
-
const oldStat = new Stats(this.
|
|
179
|
+
const oldStat = new Stats(this.readable.statSync(path, cred));
|
|
187
180
|
// Make the oldStat's mode writable. Preserve the topmost part of the mode, which specifies the type.
|
|
188
181
|
oldStat.mode |= 0o222;
|
|
189
182
|
return oldStat;
|
|
@@ -191,22 +184,22 @@ export class UnlockedOverlayFS extends FileSystem {
|
|
|
191
184
|
}
|
|
192
185
|
|
|
193
186
|
public async openFile(path: string, flag: string, cred: Cred): Promise<File> {
|
|
194
|
-
if (await this.
|
|
195
|
-
return this.
|
|
187
|
+
if (await this.writable.exists(path, cred)) {
|
|
188
|
+
return this.writable.openFile(path, flag, cred);
|
|
196
189
|
}
|
|
197
190
|
// Create an OverlayFile.
|
|
198
|
-
const file = await this.
|
|
191
|
+
const file = await this.readable.openFile(path, parseFlag('r'), cred);
|
|
199
192
|
const stats = new Stats(await file.stat());
|
|
200
193
|
const { buffer } = await file.read(new Uint8Array(stats.size));
|
|
201
194
|
return new PreloadFile(this, path, flag, stats, buffer);
|
|
202
195
|
}
|
|
203
196
|
|
|
204
197
|
public openFileSync(path: string, flag: string, cred: Cred): File {
|
|
205
|
-
if (this.
|
|
206
|
-
return this.
|
|
198
|
+
if (this.writable.existsSync(path, cred)) {
|
|
199
|
+
return this.writable.openFileSync(path, flag, cred);
|
|
207
200
|
}
|
|
208
201
|
// Create an OverlayFile.
|
|
209
|
-
const file = this.
|
|
202
|
+
const file = this.readable.openFileSync(path, parseFlag('r'), cred);
|
|
210
203
|
const stats = new Stats(file.statSync());
|
|
211
204
|
const data = new Uint8Array(stats.size);
|
|
212
205
|
file.readSync(data);
|
|
@@ -215,24 +208,24 @@ export class UnlockedOverlayFS extends FileSystem {
|
|
|
215
208
|
|
|
216
209
|
public async createFile(path: string, flag: string, mode: number, cred: Cred): Promise<File> {
|
|
217
210
|
this.checkInitialized();
|
|
218
|
-
await this.
|
|
211
|
+
await this.writable.createFile(path, flag, mode, cred);
|
|
219
212
|
return this.openFile(path, flag, cred);
|
|
220
213
|
}
|
|
221
214
|
|
|
222
215
|
public createFileSync(path: string, flag: string, mode: number, cred: Cred): File {
|
|
223
216
|
this.checkInitialized();
|
|
224
|
-
this.
|
|
217
|
+
this.writable.createFileSync(path, flag, mode, cred);
|
|
225
218
|
return this.openFileSync(path, flag, cred);
|
|
226
219
|
}
|
|
227
220
|
|
|
228
221
|
public async link(srcpath: string, dstpath: string, cred: Cred): Promise<void> {
|
|
229
222
|
this.checkInitialized();
|
|
230
|
-
await this.
|
|
223
|
+
await this.writable.link(srcpath, dstpath, cred);
|
|
231
224
|
}
|
|
232
225
|
|
|
233
226
|
public linkSync(srcpath: string, dstpath: string, cred: Cred): void {
|
|
234
227
|
this.checkInitialized();
|
|
235
|
-
this.
|
|
228
|
+
this.writable.linkSync(srcpath, dstpath, cred);
|
|
236
229
|
}
|
|
237
230
|
|
|
238
231
|
public async unlink(path: string, cred: Cred): Promise<void> {
|
|
@@ -242,8 +235,8 @@ export class UnlockedOverlayFS extends FileSystem {
|
|
|
242
235
|
throw ErrnoError.With('ENOENT', path, 'unlink');
|
|
243
236
|
}
|
|
244
237
|
|
|
245
|
-
if (await this.
|
|
246
|
-
await this.
|
|
238
|
+
if (await this.writable.exists(path, cred)) {
|
|
239
|
+
await this.writable.unlink(path, cred);
|
|
247
240
|
}
|
|
248
241
|
|
|
249
242
|
// if it still exists add to the delete log
|
|
@@ -259,8 +252,8 @@ export class UnlockedOverlayFS extends FileSystem {
|
|
|
259
252
|
throw ErrnoError.With('ENOENT', path, 'unlink');
|
|
260
253
|
}
|
|
261
254
|
|
|
262
|
-
if (this.
|
|
263
|
-
this.
|
|
255
|
+
if (this.writable.existsSync(path, cred)) {
|
|
256
|
+
this.writable.unlinkSync(path, cred);
|
|
264
257
|
}
|
|
265
258
|
|
|
266
259
|
// if it still exists add to the delete log
|
|
@@ -274,8 +267,8 @@ export class UnlockedOverlayFS extends FileSystem {
|
|
|
274
267
|
if (!(await this.exists(path, cred))) {
|
|
275
268
|
throw ErrnoError.With('ENOENT', path, 'rmdir');
|
|
276
269
|
}
|
|
277
|
-
if (await this.
|
|
278
|
-
await this.
|
|
270
|
+
if (await this.writable.exists(path, cred)) {
|
|
271
|
+
await this.writable.rmdir(path, cred);
|
|
279
272
|
}
|
|
280
273
|
if (await this.exists(path, cred)) {
|
|
281
274
|
// Check if directory is empty.
|
|
@@ -292,8 +285,8 @@ export class UnlockedOverlayFS extends FileSystem {
|
|
|
292
285
|
if (!this.existsSync(path, cred)) {
|
|
293
286
|
throw ErrnoError.With('ENOENT', path, 'rmdir');
|
|
294
287
|
}
|
|
295
|
-
if (this.
|
|
296
|
-
this.
|
|
288
|
+
if (this.writable.existsSync(path, cred)) {
|
|
289
|
+
this.writable.rmdirSync(path, cred);
|
|
297
290
|
}
|
|
298
291
|
if (this.existsSync(path, cred)) {
|
|
299
292
|
// Check if directory is empty.
|
|
@@ -312,7 +305,7 @@ export class UnlockedOverlayFS extends FileSystem {
|
|
|
312
305
|
}
|
|
313
306
|
// The below will throw should any of the parent directories fail to exist on _writable.
|
|
314
307
|
await this.createParentDirectories(path, cred);
|
|
315
|
-
await this.
|
|
308
|
+
await this.writable.mkdir(path, mode, cred);
|
|
316
309
|
}
|
|
317
310
|
|
|
318
311
|
public mkdirSync(path: string, mode: number, cred: Cred): void {
|
|
@@ -322,7 +315,7 @@ export class UnlockedOverlayFS extends FileSystem {
|
|
|
322
315
|
}
|
|
323
316
|
// The below will throw should any of the parent directories fail to exist on _writable.
|
|
324
317
|
this.createParentDirectoriesSync(path, cred);
|
|
325
|
-
this.
|
|
318
|
+
this.writable.mkdirSync(path, mode, cred);
|
|
326
319
|
}
|
|
327
320
|
|
|
328
321
|
public async readdir(path: string, cred: Cred): Promise<string[]> {
|
|
@@ -335,12 +328,12 @@ export class UnlockedOverlayFS extends FileSystem {
|
|
|
335
328
|
// Readdir in both, check delete log on RO file system's listing, merge, return.
|
|
336
329
|
const contents: string[] = [];
|
|
337
330
|
try {
|
|
338
|
-
contents.push(...(await this.
|
|
331
|
+
contents.push(...(await this.writable.readdir(path, cred)));
|
|
339
332
|
} catch (e) {
|
|
340
333
|
// NOP.
|
|
341
334
|
}
|
|
342
335
|
try {
|
|
343
|
-
contents.push(...(await this.
|
|
336
|
+
contents.push(...(await this.readable.readdir(path, cred)).filter((fPath: string) => !this._deletedFiles.has(`${path}/${fPath}`)));
|
|
344
337
|
} catch (e) {
|
|
345
338
|
// NOP.
|
|
346
339
|
}
|
|
@@ -362,12 +355,12 @@ export class UnlockedOverlayFS extends FileSystem {
|
|
|
362
355
|
// Readdir in both, check delete log on RO file system's listing, merge, return.
|
|
363
356
|
let contents: string[] = [];
|
|
364
357
|
try {
|
|
365
|
-
contents = contents.concat(this.
|
|
358
|
+
contents = contents.concat(this.writable.readdirSync(path, cred));
|
|
366
359
|
} catch (e) {
|
|
367
360
|
// NOP.
|
|
368
361
|
}
|
|
369
362
|
try {
|
|
370
|
-
contents = contents.concat(this.
|
|
363
|
+
contents = contents.concat(this.readable.readdirSync(path, cred).filter((fPath: string) => !this._deletedFiles.has(`${path}/${fPath}`)));
|
|
371
364
|
} catch (e) {
|
|
372
365
|
// NOP.
|
|
373
366
|
}
|
|
@@ -391,7 +384,7 @@ export class UnlockedOverlayFS extends FileSystem {
|
|
|
391
384
|
return;
|
|
392
385
|
}
|
|
393
386
|
this._deleteLogUpdatePending = true;
|
|
394
|
-
const log = await this.
|
|
387
|
+
const log = await this.writable.openFile(deletionLogPath, parseFlag('w'), cred);
|
|
395
388
|
try {
|
|
396
389
|
await log.write(encode(this._deleteLog));
|
|
397
390
|
if (this._deleteLogUpdateNeeded) {
|
|
@@ -445,21 +438,21 @@ export class UnlockedOverlayFS extends FileSystem {
|
|
|
445
438
|
private createParentDirectoriesSync(path: string, cred: Cred): void {
|
|
446
439
|
let parent = dirname(path),
|
|
447
440
|
toCreate: string[] = [];
|
|
448
|
-
while (!this.
|
|
441
|
+
while (!this.writable.existsSync(parent, cred)) {
|
|
449
442
|
toCreate.push(parent);
|
|
450
443
|
parent = dirname(parent);
|
|
451
444
|
}
|
|
452
445
|
toCreate = toCreate.reverse();
|
|
453
446
|
|
|
454
447
|
for (const p of toCreate) {
|
|
455
|
-
this.
|
|
448
|
+
this.writable.mkdirSync(p, this.statSync(p, cred).mode, cred);
|
|
456
449
|
}
|
|
457
450
|
}
|
|
458
451
|
|
|
459
452
|
private async createParentDirectories(path: string, cred: Cred): Promise<void> {
|
|
460
453
|
let parent = dirname(path),
|
|
461
454
|
toCreate: string[] = [];
|
|
462
|
-
while (!(await this.
|
|
455
|
+
while (!(await this.writable.exists(parent, cred))) {
|
|
463
456
|
toCreate.push(parent);
|
|
464
457
|
parent = dirname(parent);
|
|
465
458
|
}
|
|
@@ -467,7 +460,7 @@ export class UnlockedOverlayFS extends FileSystem {
|
|
|
467
460
|
|
|
468
461
|
for (const p of toCreate) {
|
|
469
462
|
const stats = await this.stat(p, cred);
|
|
470
|
-
await this.
|
|
463
|
+
await this.writable.mkdir(p, stats.mode, cred);
|
|
471
464
|
}
|
|
472
465
|
}
|
|
473
466
|
|
|
@@ -480,7 +473,7 @@ export class UnlockedOverlayFS extends FileSystem {
|
|
|
480
473
|
if (!this.existsSync(path, cred)) {
|
|
481
474
|
throw ErrnoError.With('ENOENT', path, 'operateOnWriteable');
|
|
482
475
|
}
|
|
483
|
-
if (!this.
|
|
476
|
+
if (!this.writable.existsSync(path, cred)) {
|
|
484
477
|
// File is on readable storage. Copy to writable storage before
|
|
485
478
|
// changing its mode.
|
|
486
479
|
this.copyToWritableSync(path, cred);
|
|
@@ -492,7 +485,7 @@ export class UnlockedOverlayFS extends FileSystem {
|
|
|
492
485
|
throw ErrnoError.With('ENOENT', path, 'operateOnWritable');
|
|
493
486
|
}
|
|
494
487
|
|
|
495
|
-
if (!(await this.
|
|
488
|
+
if (!(await this.writable.exists(path, cred))) {
|
|
496
489
|
return this.copyToWritable(path, cred);
|
|
497
490
|
}
|
|
498
491
|
}
|
|
@@ -504,15 +497,15 @@ export class UnlockedOverlayFS extends FileSystem {
|
|
|
504
497
|
private copyToWritableSync(path: string, cred: Cred): void {
|
|
505
498
|
const stats = this.statSync(path, cred);
|
|
506
499
|
if (stats.isDirectory()) {
|
|
507
|
-
this.
|
|
500
|
+
this.writable.mkdirSync(path, stats.mode, cred);
|
|
508
501
|
return;
|
|
509
502
|
}
|
|
510
503
|
|
|
511
504
|
const data = new Uint8Array(stats.size);
|
|
512
|
-
const readable = this.
|
|
505
|
+
const readable = this.readable.openFileSync(path, parseFlag('r'), cred);
|
|
513
506
|
readable.readSync(data);
|
|
514
507
|
readable.closeSync();
|
|
515
|
-
const writable = this.
|
|
508
|
+
const writable = this.writable.openFileSync(path, parseFlag('w'), cred);
|
|
516
509
|
writable.writeSync(data);
|
|
517
510
|
writable.closeSync();
|
|
518
511
|
}
|
|
@@ -520,15 +513,15 @@ export class UnlockedOverlayFS extends FileSystem {
|
|
|
520
513
|
private async copyToWritable(path: string, cred: Cred): Promise<void> {
|
|
521
514
|
const stats = await this.stat(path, cred);
|
|
522
515
|
if (stats.isDirectory()) {
|
|
523
|
-
await this.
|
|
516
|
+
await this.writable.mkdir(path, stats.mode, cred);
|
|
524
517
|
return;
|
|
525
518
|
}
|
|
526
519
|
|
|
527
520
|
const data = new Uint8Array(stats.size);
|
|
528
|
-
const readable = await this.
|
|
521
|
+
const readable = await this.readable.openFile(path, parseFlag('r'), cred);
|
|
529
522
|
await readable.read(data);
|
|
530
523
|
await readable.close();
|
|
531
|
-
const writable = await this.
|
|
524
|
+
const writable = await this.writable.openFile(path, parseFlag('w'), cred);
|
|
532
525
|
await writable.write(data);
|
|
533
526
|
await writable.close();
|
|
534
527
|
}
|
|
@@ -540,32 +533,9 @@ export class UnlockedOverlayFS extends FileSystem {
|
|
|
540
533
|
* file system.
|
|
541
534
|
* @internal
|
|
542
535
|
*/
|
|
543
|
-
export class OverlayFS extends
|
|
544
|
-
/**
|
|
545
|
-
* @param options The options to initialize the OverlayFS with
|
|
546
|
-
*/
|
|
547
|
-
constructor(options: OverlayOptions) {
|
|
548
|
-
super(new UnlockedOverlayFS(options));
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
public getOverlayedFileSystems(): OverlayOptions {
|
|
552
|
-
return super.fs.getOverlayedFileSystems();
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
public getDeletionLog(): string {
|
|
556
|
-
return super.fs.getDeletionLog();
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
public resDeletionLog(): string {
|
|
560
|
-
return super.fs.getDeletionLog();
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
public unwrap(): UnlockedOverlayFS {
|
|
564
|
-
return super.fs;
|
|
565
|
-
}
|
|
566
|
-
}
|
|
536
|
+
export class OverlayFS extends Mutexed(UnmutexedOverlayFS) {}
|
|
567
537
|
|
|
568
|
-
|
|
538
|
+
const _Overlay = {
|
|
569
539
|
name: 'Overlay',
|
|
570
540
|
|
|
571
541
|
options: {
|
|
@@ -589,3 +559,7 @@ export const Overlay = {
|
|
|
589
559
|
return new OverlayFS(options);
|
|
590
560
|
},
|
|
591
561
|
} as const satisfies Backend<OverlayFS, OverlayOptions>;
|
|
562
|
+
type _overlay = typeof _Overlay;
|
|
563
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
564
|
+
interface Overlay extends _overlay {}
|
|
565
|
+
export const Overlay: Overlay = _Overlay;
|
package/src/backends/port/fs.ts
CHANGED
|
@@ -4,7 +4,8 @@ import type { ExtractProperties } from 'utilium';
|
|
|
4
4
|
import type { Cred } from '../../cred.js';
|
|
5
5
|
import { Errno, ErrnoError } from '../../error.js';
|
|
6
6
|
import { File } from '../../file.js';
|
|
7
|
-
import {
|
|
7
|
+
import { FileSystem, type FileSystemMetadata } from '../../filesystem.js';
|
|
8
|
+
import { Async } from '../../mixins/async.js';
|
|
8
9
|
import { Stats, type FileType } from '../../stats.js';
|
|
9
10
|
import { InMemory } from '../memory.js';
|
|
10
11
|
import type { Backend, FilesystemOf } from '../backend.js';
|
|
@@ -285,7 +286,7 @@ export function detachFS(port: RPC.Port, fs: FileSystem): void {
|
|
|
285
286
|
RPC.detach<FileOrFSRequest>(port, request => handleRequest(port, fs, request));
|
|
286
287
|
}
|
|
287
288
|
|
|
288
|
-
export const
|
|
289
|
+
export const _Port = {
|
|
289
290
|
name: 'Port',
|
|
290
291
|
|
|
291
292
|
options: {
|
|
@@ -315,6 +316,10 @@ export const Port = {
|
|
|
315
316
|
return new PortFS(options);
|
|
316
317
|
},
|
|
317
318
|
} satisfies Backend<PortFS, RPC.Options>;
|
|
319
|
+
type _port = typeof _Port;
|
|
320
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
321
|
+
interface Port extends _port {}
|
|
322
|
+
export const Port: Port = _Port;
|
|
318
323
|
|
|
319
324
|
export async function resolveRemoteMount<T extends Backend>(port: RPC.Port, config: MountConfiguration<T>, _depth = 0): Promise<FilesystemOf<T>> {
|
|
320
325
|
const stopAndReplay = RPC.catchMessages(port);
|