@zenfs/core 0.9.2 → 0.9.4
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/Index.d.ts +3 -0
- package/dist/browser.min.js +3 -3
- package/dist/browser.min.js.map +3 -3
- package/dist/filesystem.d.ts +1 -1
- package/dist/filesystem.js +6 -6
- package/package.json +2 -9
- package/src/ApiError.ts +310 -0
- package/src/backends/AsyncStore.ts +635 -0
- package/src/backends/InMemory.ts +56 -0
- package/src/backends/Index.ts +500 -0
- package/src/backends/Locked.ts +181 -0
- package/src/backends/Overlay.ts +591 -0
- package/src/backends/SyncStore.ts +589 -0
- package/src/backends/backend.ts +152 -0
- package/src/config.ts +101 -0
- package/src/cred.ts +21 -0
- package/src/emulation/async.ts +910 -0
- package/src/emulation/constants.ts +176 -0
- package/src/emulation/dir.ts +139 -0
- package/src/emulation/index.ts +8 -0
- package/src/emulation/path.ts +468 -0
- package/src/emulation/promises.ts +1071 -0
- package/src/emulation/shared.ts +128 -0
- package/src/emulation/streams.ts +33 -0
- package/src/emulation/sync.ts +898 -0
- package/src/file.ts +721 -0
- package/src/filesystem.ts +544 -0
- package/src/index.ts +21 -0
- package/src/inode.ts +229 -0
- package/src/mutex.ts +52 -0
- package/src/stats.ts +385 -0
- package/src/utils.ts +287 -0
package/src/file.ts
ADDED
|
@@ -0,0 +1,721 @@
|
|
|
1
|
+
import type { FileReadResult } from 'node:fs/promises';
|
|
2
|
+
import { ApiError, ErrorCode } from './ApiError.js';
|
|
3
|
+
import { O_APPEND, O_CREAT, O_EXCL, O_RDONLY, O_RDWR, O_SYNC, O_TRUNC, O_WRONLY, S_IFMT } from './emulation/constants.js';
|
|
4
|
+
import type { FileSystem } from './filesystem.js';
|
|
5
|
+
import { size_max } from './inode.js';
|
|
6
|
+
import { Stats, type FileType } from './stats.js';
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
Typescript does not include a type declaration for resizable array buffers.
|
|
10
|
+
It has been standardized into ECMAScript though
|
|
11
|
+
Remove this if TS adds them to lib declarations
|
|
12
|
+
*/
|
|
13
|
+
declare global {
|
|
14
|
+
interface ArrayBuffer {
|
|
15
|
+
readonly resizable: boolean;
|
|
16
|
+
|
|
17
|
+
readonly maxByteLength?: number;
|
|
18
|
+
|
|
19
|
+
resize(newLength: number): void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface SharedArrayBuffer {
|
|
23
|
+
readonly resizable: boolean;
|
|
24
|
+
|
|
25
|
+
readonly maxByteLength?: number;
|
|
26
|
+
|
|
27
|
+
resize(newLength: number): void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface ArrayBufferConstructor {
|
|
31
|
+
new (byteLength: number, options: { maxByteLength?: number }): ArrayBuffer;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @hidden
|
|
37
|
+
*/
|
|
38
|
+
export enum ActionType {
|
|
39
|
+
// Indicates that the code should not do anything.
|
|
40
|
+
NOP = 0,
|
|
41
|
+
// Indicates that the code should throw an exception.
|
|
42
|
+
THROW = 1,
|
|
43
|
+
// Indicates that the code should truncate the file, but only if it is a file.
|
|
44
|
+
TRUNCATE = 2,
|
|
45
|
+
// Indicates that the code should create the file.
|
|
46
|
+
CREATE = 3,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const validFlags = ['r', 'r+', 'rs', 'rs+', 'w', 'wx', 'w+', 'wx+', 'a', 'ax', 'a+', 'ax+'];
|
|
50
|
+
|
|
51
|
+
export function parseFlag(flag: string | number): string {
|
|
52
|
+
if (typeof flag === 'number') {
|
|
53
|
+
return flagToString(flag);
|
|
54
|
+
}
|
|
55
|
+
if (!validFlags.includes(flag)) {
|
|
56
|
+
throw new Error('Invalid flag string: ' + flag);
|
|
57
|
+
}
|
|
58
|
+
return flag;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function flagToString(flag: number): string {
|
|
62
|
+
switch (flag) {
|
|
63
|
+
case O_RDONLY:
|
|
64
|
+
return 'r';
|
|
65
|
+
case O_RDONLY | O_SYNC:
|
|
66
|
+
return 'rs';
|
|
67
|
+
case O_RDWR:
|
|
68
|
+
return 'r+';
|
|
69
|
+
case O_RDWR | O_SYNC:
|
|
70
|
+
return 'rs+';
|
|
71
|
+
case O_TRUNC | O_CREAT | O_WRONLY:
|
|
72
|
+
return 'w';
|
|
73
|
+
case O_TRUNC | O_CREAT | O_WRONLY | O_EXCL:
|
|
74
|
+
return 'wx';
|
|
75
|
+
case O_TRUNC | O_CREAT | O_RDWR:
|
|
76
|
+
return 'w+';
|
|
77
|
+
case O_TRUNC | O_CREAT | O_RDWR | O_EXCL:
|
|
78
|
+
return 'wx+';
|
|
79
|
+
case O_APPEND | O_CREAT | O_WRONLY:
|
|
80
|
+
return 'a';
|
|
81
|
+
case O_APPEND | O_CREAT | O_WRONLY | O_EXCL:
|
|
82
|
+
return 'ax';
|
|
83
|
+
case O_APPEND | O_CREAT | O_RDWR:
|
|
84
|
+
return 'a+';
|
|
85
|
+
case O_APPEND | O_CREAT | O_RDWR | O_EXCL:
|
|
86
|
+
return 'ax+';
|
|
87
|
+
default:
|
|
88
|
+
throw new Error('Invalid flag number: ' + flag);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function flagToNumber(flag: string): number {
|
|
93
|
+
switch (flag) {
|
|
94
|
+
case 'r':
|
|
95
|
+
return O_RDONLY;
|
|
96
|
+
case 'rs':
|
|
97
|
+
return O_RDONLY | O_SYNC;
|
|
98
|
+
case 'r+':
|
|
99
|
+
return O_RDWR;
|
|
100
|
+
case 'rs+':
|
|
101
|
+
return O_RDWR | O_SYNC;
|
|
102
|
+
case 'w':
|
|
103
|
+
return O_TRUNC | O_CREAT | O_WRONLY;
|
|
104
|
+
case 'wx':
|
|
105
|
+
return O_TRUNC | O_CREAT | O_WRONLY | O_EXCL;
|
|
106
|
+
case 'w+':
|
|
107
|
+
return O_TRUNC | O_CREAT | O_RDWR;
|
|
108
|
+
case 'wx+':
|
|
109
|
+
return O_TRUNC | O_CREAT | O_RDWR | O_EXCL;
|
|
110
|
+
case 'a':
|
|
111
|
+
return O_APPEND | O_CREAT | O_WRONLY;
|
|
112
|
+
case 'ax':
|
|
113
|
+
return O_APPEND | O_CREAT | O_WRONLY | O_EXCL;
|
|
114
|
+
case 'a+':
|
|
115
|
+
return O_APPEND | O_CREAT | O_RDWR;
|
|
116
|
+
case 'ax+':
|
|
117
|
+
return O_APPEND | O_CREAT | O_RDWR | O_EXCL;
|
|
118
|
+
default:
|
|
119
|
+
throw new Error('Invalid flag string: ' + flag);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Parses a flag as a mode (W_OK, R_OK, and/or X_OK)
|
|
125
|
+
* @param flag the flag to parse
|
|
126
|
+
*/
|
|
127
|
+
export function flagToMode(flag: string): number {
|
|
128
|
+
let mode = 0;
|
|
129
|
+
mode <<= 1;
|
|
130
|
+
mode += +isReadable(flag);
|
|
131
|
+
mode <<= 1;
|
|
132
|
+
mode += +isWriteable(flag);
|
|
133
|
+
mode <<= 1;
|
|
134
|
+
return mode;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function isReadable(flag: string): boolean {
|
|
138
|
+
return flag.indexOf('r') !== -1 || flag.indexOf('+') !== -1;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function isWriteable(flag: string): boolean {
|
|
142
|
+
return flag.indexOf('w') !== -1 || flag.indexOf('a') !== -1 || flag.indexOf('+') !== -1;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function isTruncating(flag: string): boolean {
|
|
146
|
+
return flag.indexOf('w') !== -1;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function isAppendable(flag: string): boolean {
|
|
150
|
+
return flag.indexOf('a') !== -1;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function isSynchronous(flag: string): boolean {
|
|
154
|
+
return flag.indexOf('s') !== -1;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function isExclusive(flag: string): boolean {
|
|
158
|
+
return flag.indexOf('x') !== -1;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function pathExistsAction(flag: string): ActionType {
|
|
162
|
+
if (isExclusive(flag)) {
|
|
163
|
+
return ActionType.THROW;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (isTruncating(flag)) {
|
|
167
|
+
return ActionType.TRUNCATE;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return ActionType.NOP;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function pathNotExistsAction(flag: string): ActionType {
|
|
174
|
+
if ((isWriteable(flag) || isAppendable(flag)) && flag !== 'r+') {
|
|
175
|
+
return ActionType.CREATE;
|
|
176
|
+
}
|
|
177
|
+
return ActionType.THROW;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export abstract class File {
|
|
181
|
+
/**
|
|
182
|
+
* Get the current file position.
|
|
183
|
+
*/
|
|
184
|
+
public abstract position?: number;
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* The path to the file
|
|
188
|
+
*/
|
|
189
|
+
public abstract readonly path?: string;
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Asynchronous `stat`.
|
|
193
|
+
*/
|
|
194
|
+
public abstract stat(): Promise<Stats>;
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Synchronous `stat`.
|
|
198
|
+
*/
|
|
199
|
+
public abstract statSync(): Stats;
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Asynchronous close.
|
|
203
|
+
*/
|
|
204
|
+
public abstract close(): Promise<void>;
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Synchronous close.
|
|
208
|
+
*/
|
|
209
|
+
public abstract closeSync(): void;
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Asynchronous truncate.
|
|
213
|
+
*/
|
|
214
|
+
public abstract truncate(len: number): Promise<void>;
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Synchronous truncate.
|
|
218
|
+
*/
|
|
219
|
+
public abstract truncateSync(len: number): void;
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Asynchronous sync.
|
|
223
|
+
*/
|
|
224
|
+
public abstract sync(): Promise<void>;
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Synchronous sync.
|
|
228
|
+
*/
|
|
229
|
+
public abstract syncSync(): void;
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Write buffer to the file.
|
|
233
|
+
* Note that it is unsafe to use fs.write multiple times on the same file
|
|
234
|
+
* without waiting for the callback.
|
|
235
|
+
* @param buffer Uint8Array containing the data to write to
|
|
236
|
+
* the file.
|
|
237
|
+
* @param offset Offset in the buffer to start reading data from.
|
|
238
|
+
* @param length The amount of bytes to write to the file.
|
|
239
|
+
* @param position Offset from the beginning of the file where this
|
|
240
|
+
* data should be written. If position is null, the data will be written at
|
|
241
|
+
* the current position.
|
|
242
|
+
* @returns Promise resolving to the new length of the buffer
|
|
243
|
+
*/
|
|
244
|
+
public abstract write(buffer: Uint8Array, offset?: number, length?: number, position?: number): Promise<number>;
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Write buffer to the file.
|
|
248
|
+
* Note that it is unsafe to use fs.writeSync multiple times on the same file
|
|
249
|
+
* without waiting for it to return.
|
|
250
|
+
* @param buffer Uint8Array containing the data to write to
|
|
251
|
+
* the file.
|
|
252
|
+
* @param offset Offset in the buffer to start reading data from.
|
|
253
|
+
* @param length The amount of bytes to write to the file.
|
|
254
|
+
* @param position Offset from the beginning of the file where this
|
|
255
|
+
* data should be written. If position is null, the data will be written at
|
|
256
|
+
* the current position.
|
|
257
|
+
*/
|
|
258
|
+
public abstract writeSync(buffer: Uint8Array, offset?: number, length?: number, position?: number): number;
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Read data from the file.
|
|
262
|
+
* @param buffer The buffer that the data will be
|
|
263
|
+
* written to.
|
|
264
|
+
* @param offset The offset within the buffer where writing will
|
|
265
|
+
* start.
|
|
266
|
+
* @param length An integer specifying the number of bytes to read.
|
|
267
|
+
* @param position An integer specifying where to begin reading from
|
|
268
|
+
* in the file. If position is null, data will be read from the current file
|
|
269
|
+
* position.
|
|
270
|
+
* @returns Promise resolving to the new length of the buffer
|
|
271
|
+
*/
|
|
272
|
+
public abstract read<TBuffer extends NodeJS.ArrayBufferView>(buffer: TBuffer, offset?: number, length?: number, position?: number): Promise<FileReadResult<TBuffer>>;
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Read data from the file.
|
|
276
|
+
* @param buffer The buffer that the data will be written to.
|
|
277
|
+
* @param offset The offset within the buffer where writing will start.
|
|
278
|
+
* @param length An integer specifying the number of bytes to read.
|
|
279
|
+
* @param position An integer specifying where to begin reading from
|
|
280
|
+
* in the file. If position is null, data will be read from the current file
|
|
281
|
+
* position.
|
|
282
|
+
*/
|
|
283
|
+
public abstract readSync(buffer: ArrayBufferView, offset?: number, length?: number, position?: number): number;
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Asynchronous `datasync`.
|
|
287
|
+
*
|
|
288
|
+
* Default implementation maps to `sync`.
|
|
289
|
+
*/
|
|
290
|
+
public datasync(): Promise<void> {
|
|
291
|
+
return this.sync();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Synchronous `datasync`.
|
|
296
|
+
*
|
|
297
|
+
* Default implementation maps to `syncSync`.
|
|
298
|
+
*/
|
|
299
|
+
public datasyncSync(): void {
|
|
300
|
+
return this.syncSync();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Asynchronous `chown`.
|
|
305
|
+
*/
|
|
306
|
+
public abstract chown(uid: number, gid: number): Promise<void>;
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Synchronous `chown`.
|
|
310
|
+
*/
|
|
311
|
+
public abstract chownSync(uid: number, gid: number): void;
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Asynchronous `fchmod`.
|
|
315
|
+
*/
|
|
316
|
+
public abstract chmod(mode: number): Promise<void>;
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Synchronous `fchmod`.
|
|
320
|
+
*/
|
|
321
|
+
public abstract chmodSync(mode: number): void;
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Change the file timestamps of the file.
|
|
325
|
+
*/
|
|
326
|
+
public abstract utimes(atime: Date, mtime: Date): Promise<void>;
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Change the file timestamps of the file.
|
|
330
|
+
*/
|
|
331
|
+
public abstract utimesSync(atime: Date, mtime: Date): void;
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Set the file type
|
|
335
|
+
* @internal
|
|
336
|
+
*/
|
|
337
|
+
public abstract _setType(type: FileType): Promise<void>;
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Set the file type
|
|
341
|
+
* @internal
|
|
342
|
+
*/
|
|
343
|
+
public abstract _setTypeSync(type: FileType): void;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* An implementation of the File interface that operates on a file that is
|
|
348
|
+
* completely in-memory. PreloadFiles are backed by a Uint8Array.
|
|
349
|
+
*
|
|
350
|
+
* @todo 'close' lever that disables functionality once closed.
|
|
351
|
+
*/
|
|
352
|
+
export class PreloadFile<FS extends FileSystem> extends File {
|
|
353
|
+
protected _position: number = 0;
|
|
354
|
+
protected _dirty: boolean = false;
|
|
355
|
+
/**
|
|
356
|
+
* Creates a file with the given path and, optionally, the given contents. Note
|
|
357
|
+
* that, if contents is specified, it will be mutated by the file!
|
|
358
|
+
* @param _mode The mode that the file was opened using.
|
|
359
|
+
* Dictates permissions and where the file pointer starts.
|
|
360
|
+
* @param stats The stats object for the given file.
|
|
361
|
+
* PreloadFile will mutate this object. Note that this object must contain
|
|
362
|
+
* the appropriate mode that the file was opened as.
|
|
363
|
+
* @param buffer A buffer containing the entire
|
|
364
|
+
* contents of the file. PreloadFile will mutate this buffer. If not
|
|
365
|
+
* specified, we assume it is a new file.
|
|
366
|
+
*/
|
|
367
|
+
constructor(
|
|
368
|
+
/**
|
|
369
|
+
* The file system that created the file.
|
|
370
|
+
*/
|
|
371
|
+
protected fs: FS,
|
|
372
|
+
/**
|
|
373
|
+
* Path to the file
|
|
374
|
+
*/
|
|
375
|
+
public readonly path: string,
|
|
376
|
+
public readonly flag: string,
|
|
377
|
+
public readonly stats: Stats,
|
|
378
|
+
protected _buffer: Uint8Array = new Uint8Array(new ArrayBuffer(0, { maxByteLength: size_max }))
|
|
379
|
+
) {
|
|
380
|
+
super();
|
|
381
|
+
|
|
382
|
+
/*
|
|
383
|
+
Note:
|
|
384
|
+
This invariant is *not* maintained once the file starts getting modified.
|
|
385
|
+
It only actually matters if file is readable, as writeable modes may truncate/append to file.
|
|
386
|
+
*/
|
|
387
|
+
if (this.stats.size == _buffer.byteLength) {
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (isReadable(this.flag)) {
|
|
392
|
+
throw new Error(`Size mismatch: buffer length ${_buffer.byteLength}, stats size ${this.stats.size}`);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
this._dirty = true;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Get the underlying buffer for this file. Mutating not recommended and will mess up dirty tracking.
|
|
400
|
+
*/
|
|
401
|
+
public get buffer(): Uint8Array {
|
|
402
|
+
return this._buffer;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Get the current file position.
|
|
407
|
+
*
|
|
408
|
+
* We emulate the following bug mentioned in the Node documentation:
|
|
409
|
+
* > On Linux, positional writes don't work when the file is opened in append
|
|
410
|
+
* mode. The kernel ignores the position argument and always appends the data
|
|
411
|
+
* to the end of the file.
|
|
412
|
+
* @return The current file position.
|
|
413
|
+
*/
|
|
414
|
+
public get position(): number {
|
|
415
|
+
if (isAppendable(this.flag)) {
|
|
416
|
+
return this.stats.size;
|
|
417
|
+
}
|
|
418
|
+
return this._position;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Set the file position.
|
|
423
|
+
* @param newPos new position
|
|
424
|
+
*/
|
|
425
|
+
public set position(newPos: number) {
|
|
426
|
+
this._position = newPos;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
public async sync(): Promise<void> {
|
|
430
|
+
if (!this.isDirty()) {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
await this.fs.sync(this.path, this._buffer, this.stats);
|
|
434
|
+
this._dirty = false;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
public syncSync(): void {
|
|
438
|
+
if (!this.isDirty()) {
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
this.fs.syncSync(this.path, this._buffer, this.stats);
|
|
442
|
+
this._dirty = false;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
public async close(): Promise<void> {
|
|
446
|
+
await this.sync();
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
public closeSync(): void {
|
|
450
|
+
this.syncSync();
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Asynchronous `stat`.
|
|
455
|
+
*/
|
|
456
|
+
public async stat(): Promise<Stats> {
|
|
457
|
+
return new Stats(this.stats);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Synchronous `stat`.
|
|
462
|
+
*/
|
|
463
|
+
public statSync(): Stats {
|
|
464
|
+
return new Stats(this.stats);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Asynchronous truncate.
|
|
469
|
+
* @param len
|
|
470
|
+
*/
|
|
471
|
+
public truncate(len: number): Promise<void> {
|
|
472
|
+
this.truncateSync(len);
|
|
473
|
+
if (isSynchronous(this.flag)) {
|
|
474
|
+
return this.sync();
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Synchronous truncate.
|
|
480
|
+
* @param len
|
|
481
|
+
*/
|
|
482
|
+
public truncateSync(len: number): void {
|
|
483
|
+
this._dirty = true;
|
|
484
|
+
if (!isWriteable(this.flag)) {
|
|
485
|
+
throw new ApiError(ErrorCode.EPERM, 'File not opened with a writeable mode.');
|
|
486
|
+
}
|
|
487
|
+
this.stats.mtimeMs = Date.now();
|
|
488
|
+
if (len > this._buffer.length) {
|
|
489
|
+
const buf = new Uint8Array(len - this._buffer.length);
|
|
490
|
+
// Write will set stats.size for us.
|
|
491
|
+
this.writeSync(buf, 0, buf.length, this._buffer.length);
|
|
492
|
+
if (isSynchronous(this.flag)) {
|
|
493
|
+
this.syncSync();
|
|
494
|
+
}
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
this.stats.size = len;
|
|
498
|
+
// Truncate buffer to 'len'.
|
|
499
|
+
this._buffer = this._buffer.subarray(0, len);
|
|
500
|
+
if (isSynchronous(this.flag)) {
|
|
501
|
+
this.syncSync();
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Write buffer to the file.
|
|
507
|
+
* Note that it is unsafe to use fs.write multiple times on the same file
|
|
508
|
+
* without waiting for the callback.
|
|
509
|
+
* @param buffer Uint8Array containing the data to write to
|
|
510
|
+
* the file.
|
|
511
|
+
* @param offset Offset in the buffer to start reading data from.
|
|
512
|
+
* @param length The amount of bytes to write to the file.
|
|
513
|
+
* @param position Offset from the beginning of the file where this
|
|
514
|
+
* data should be written. If position is null, the data will be written at
|
|
515
|
+
* the current position.
|
|
516
|
+
*/
|
|
517
|
+
public async write(buffer: Uint8Array, offset: number = 0, length: number = this.stats.size, position: number = 0): Promise<number> {
|
|
518
|
+
return this.writeSync(buffer, offset, length, position);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Write buffer to the file.
|
|
523
|
+
* Note that it is unsafe to use fs.writeSync multiple times on the same file
|
|
524
|
+
* without waiting for the callback.
|
|
525
|
+
* @param buffer Uint8Array containing the data to write to
|
|
526
|
+
* the file.
|
|
527
|
+
* @param offset Offset in the buffer to start reading data from.
|
|
528
|
+
* @param length The amount of bytes to write to the file.
|
|
529
|
+
* @param position Offset from the beginning of the file where this
|
|
530
|
+
* data should be written. If position is null, the data will be written at
|
|
531
|
+
* the current position.
|
|
532
|
+
* @returns bytes written
|
|
533
|
+
*/
|
|
534
|
+
public writeSync(buffer: Uint8Array, offset: number = 0, length: number = this.stats.size, position: number = 0): number {
|
|
535
|
+
this._dirty = true;
|
|
536
|
+
position ??= this.position;
|
|
537
|
+
if (!isWriteable(this.flag)) {
|
|
538
|
+
throw new ApiError(ErrorCode.EPERM, 'File not opened with a writeable mode.');
|
|
539
|
+
}
|
|
540
|
+
const endFp = position + length;
|
|
541
|
+
if (endFp > this.stats.size) {
|
|
542
|
+
this.stats.size = endFp;
|
|
543
|
+
if (endFp > this._buffer.byteLength) {
|
|
544
|
+
if (this._buffer.buffer.resizable && this._buffer.buffer.maxByteLength <= endFp) {
|
|
545
|
+
this._buffer.buffer.resize(endFp);
|
|
546
|
+
} else {
|
|
547
|
+
// Extend the buffer!
|
|
548
|
+
const newBuffer = new Uint8Array(new ArrayBuffer(endFp, { maxByteLength: size_max }));
|
|
549
|
+
newBuffer.set(this._buffer);
|
|
550
|
+
this._buffer = newBuffer;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
const slice = buffer.slice(offset, offset + length);
|
|
555
|
+
this._buffer.set(slice, position);
|
|
556
|
+
const bytesWritten = slice.byteLength;
|
|
557
|
+
this.stats.mtimeMs = Date.now();
|
|
558
|
+
if (isSynchronous(this.flag)) {
|
|
559
|
+
this.syncSync();
|
|
560
|
+
return bytesWritten;
|
|
561
|
+
}
|
|
562
|
+
this.position = position + bytesWritten;
|
|
563
|
+
return bytesWritten;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Read data from the file.
|
|
568
|
+
* @param buffer The buffer that the data will be
|
|
569
|
+
* written to.
|
|
570
|
+
* @param offset The offset within the buffer where writing will
|
|
571
|
+
* start.
|
|
572
|
+
* @param length An integer specifying the number of bytes to read.
|
|
573
|
+
* @param position An integer specifying where to begin reading from
|
|
574
|
+
* in the file. If position is null, data will be read from the current file
|
|
575
|
+
* position.
|
|
576
|
+
*/
|
|
577
|
+
public async read<TBuffer extends ArrayBufferView>(
|
|
578
|
+
buffer: TBuffer,
|
|
579
|
+
offset: number = 0,
|
|
580
|
+
length: number = this.stats.size,
|
|
581
|
+
position: number = 0
|
|
582
|
+
): Promise<{ bytesRead: number; buffer: TBuffer }> {
|
|
583
|
+
return { bytesRead: this.readSync(buffer, offset, length, position), buffer };
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Read data from the file.
|
|
588
|
+
* @param buffer The buffer that the data will be
|
|
589
|
+
* written to.
|
|
590
|
+
* @param offset The offset within the buffer where writing will start.
|
|
591
|
+
* @param length An integer specifying the number of bytes to read.
|
|
592
|
+
* @param position An integer specifying where to begin reading from
|
|
593
|
+
* in the file. If position is null, data will be read from the current file
|
|
594
|
+
* position.
|
|
595
|
+
* @returns number of bytes written
|
|
596
|
+
*/
|
|
597
|
+
public readSync(buffer: ArrayBufferView, offset: number = 0, length: number = this.stats.size, position: number = 0): number {
|
|
598
|
+
if (!isReadable(this.flag)) {
|
|
599
|
+
throw new ApiError(ErrorCode.EPERM, 'File not opened with a readable mode.');
|
|
600
|
+
}
|
|
601
|
+
position ??= this.position;
|
|
602
|
+
let end = position + length;
|
|
603
|
+
if (end > this.stats.size) {
|
|
604
|
+
end = position + Math.max(this.stats.size - position, 0);
|
|
605
|
+
}
|
|
606
|
+
this.stats.atimeMs = Date.now();
|
|
607
|
+
this._position = end;
|
|
608
|
+
const bytesRead = end - position;
|
|
609
|
+
if (bytesRead == 0) {
|
|
610
|
+
// No copy/read. Return immediatly for better performance
|
|
611
|
+
return bytesRead;
|
|
612
|
+
}
|
|
613
|
+
new Uint8Array(buffer.buffer).set(this._buffer.slice(position, end), offset);
|
|
614
|
+
return bytesRead;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Asynchronous `fchmod`.
|
|
619
|
+
* @param mode the mode
|
|
620
|
+
*/
|
|
621
|
+
public async chmod(mode: number): Promise<void> {
|
|
622
|
+
this.chmodSync(mode);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Synchronous `fchmod`.
|
|
627
|
+
* @param mode
|
|
628
|
+
*/
|
|
629
|
+
public chmodSync(mode: number): void {
|
|
630
|
+
this._dirty = true;
|
|
631
|
+
this.stats.chmod(mode);
|
|
632
|
+
this.syncSync();
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Asynchronous `fchown`.
|
|
637
|
+
* @param uid
|
|
638
|
+
* @param gid
|
|
639
|
+
*/
|
|
640
|
+
public async chown(uid: number, gid: number): Promise<void> {
|
|
641
|
+
this.chownSync(uid, gid);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Synchronous `fchown`.
|
|
646
|
+
* @param uid
|
|
647
|
+
* @param gid
|
|
648
|
+
*/
|
|
649
|
+
public chownSync(uid: number, gid: number): void {
|
|
650
|
+
this._dirty = true;
|
|
651
|
+
this.stats.chown(uid, gid);
|
|
652
|
+
this.syncSync();
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
public async utimes(atime: Date, mtime: Date): Promise<void> {
|
|
656
|
+
this.utimesSync(atime, mtime);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
public utimesSync(atime: Date, mtime: Date): void {
|
|
660
|
+
this._dirty = true;
|
|
661
|
+
this.stats.atime = atime;
|
|
662
|
+
this.stats.mtime = mtime;
|
|
663
|
+
this.syncSync();
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
protected isDirty(): boolean {
|
|
667
|
+
return this._dirty;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Resets the dirty bit. Should only be called after a sync has completed successfully.
|
|
672
|
+
*/
|
|
673
|
+
protected resetDirty() {
|
|
674
|
+
this._dirty = false;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
public _setType(type: FileType): Promise<void> {
|
|
678
|
+
this._dirty = true;
|
|
679
|
+
this.stats.mode = (this.stats.mode & ~S_IFMT) | type;
|
|
680
|
+
return this.sync();
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
public _setTypeSync(type: FileType): void {
|
|
684
|
+
this._dirty = true;
|
|
685
|
+
this.stats.mode = (this.stats.mode & ~S_IFMT) | type;
|
|
686
|
+
this.syncSync();
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* For the filesystems which do not sync to anything..
|
|
692
|
+
*/
|
|
693
|
+
export class NoSyncFile<T extends FileSystem> extends PreloadFile<T> {
|
|
694
|
+
constructor(_fs: T, _path: string, _flag: string, _stat: Stats, contents?: Uint8Array) {
|
|
695
|
+
super(_fs, _path, _flag, _stat, contents);
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Asynchronous sync. Doesn't do anything, simply calls the cb.
|
|
699
|
+
*/
|
|
700
|
+
public async sync(): Promise<void> {
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Synchronous sync. Doesn't do anything.
|
|
705
|
+
*/
|
|
706
|
+
public syncSync(): void {
|
|
707
|
+
// NOP.
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Asynchronous close. Doesn't do anything, simply calls the cb.
|
|
711
|
+
*/
|
|
712
|
+
public async close(): Promise<void> {
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Synchronous close. Doesn't do anything.
|
|
717
|
+
*/
|
|
718
|
+
public closeSync(): void {
|
|
719
|
+
// NOP.
|
|
720
|
+
}
|
|
721
|
+
}
|