@gjsify/fs 0.1.13 → 0.2.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/lib/esm/callback.js +22 -13
- package/lib/esm/cp.js +253 -0
- package/lib/esm/dir.js +160 -0
- package/lib/esm/fd-ops.js +189 -0
- package/lib/esm/file-handle.js +263 -84
- package/lib/esm/fs-watcher.js +88 -4
- package/lib/esm/glob.js +164 -0
- package/lib/esm/index.js +128 -2
- package/lib/esm/promises.js +90 -27
- package/lib/esm/read-stream.js +53 -43
- package/lib/esm/stat-watcher.js +121 -0
- package/lib/esm/statfs.js +57 -0
- package/lib/esm/sync.js +70 -52
- package/lib/esm/utils.js +7 -0
- package/lib/esm/utimes.js +62 -0
- package/lib/esm/write-stream.js +2 -5
- package/lib/types/cp.d.ts +18 -0
- package/lib/types/cp.spec.d.ts +2 -0
- package/lib/types/dir.d.ts +29 -0
- package/lib/types/dir.spec.d.ts +2 -0
- package/lib/types/fd-ops.d.ts +57 -0
- package/lib/types/fd-ops.spec.d.ts +2 -0
- package/lib/types/file-handle.d.ts +34 -4
- package/lib/types/fs-watcher.d.ts +9 -2
- package/lib/types/glob.d.ts +8 -0
- package/lib/types/glob.spec.d.ts +2 -0
- package/lib/types/index.d.ts +51 -1
- package/lib/types/promises.d.ts +31 -4
- package/lib/types/read-stream.d.ts +3 -1
- package/lib/types/stat-watcher.d.ts +21 -0
- package/lib/types/statfs.d.ts +35 -0
- package/lib/types/statfs.spec.d.ts +2 -0
- package/lib/types/sync.d.ts +4 -7
- package/lib/types/utils.d.ts +2 -0
- package/lib/types/utimes.d.ts +13 -0
- package/lib/types/utimes.spec.d.ts +2 -0
- package/lib/types/watch.spec.d.ts +2 -0
- package/lib/types/watchfile.spec.d.ts +2 -0
- package/lib/types/write-stream.d.ts +1 -2
- package/package.json +12 -12
- package/src/callback.ts +22 -13
- package/src/cp.spec.ts +181 -0
- package/src/cp.ts +328 -0
- package/src/dir.spec.ts +204 -0
- package/src/dir.ts +199 -0
- package/src/fd-ops.spec.ts +234 -0
- package/src/fd-ops.ts +251 -0
- package/src/file-handle.ts +264 -94
- package/src/fs-watcher.ts +101 -6
- package/src/glob.spec.ts +201 -0
- package/src/glob.ts +205 -0
- package/src/index.ts +74 -0
- package/src/promises.ts +94 -29
- package/src/read-stream.ts +49 -43
- package/src/stat-watcher.ts +116 -0
- package/src/statfs.spec.ts +67 -0
- package/src/statfs.ts +92 -0
- package/src/streams.spec.ts +58 -0
- package/src/sync.ts +75 -57
- package/src/test.mts +13 -2
- package/src/utils.ts +10 -0
- package/src/utimes.spec.ts +113 -0
- package/src/utimes.ts +97 -0
- package/src/watch.spec.ts +171 -0
- package/src/watchfile.spec.ts +185 -0
- package/src/write-stream.ts +5 -8
- package/tsconfig.tsbuildinfo +1 -1
package/lib/esm/file-handle.js
CHANGED
|
@@ -1,63 +1,150 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createGLibFileError } from "@gjsify/utils";
|
|
2
2
|
import { ReadStream } from "./read-stream.js";
|
|
3
3
|
import { WriteStream } from "./write-stream.js";
|
|
4
|
-
import { Stats } from "./stats.js";
|
|
4
|
+
import { Stats, BigIntStats, STAT_ATTRIBUTES } from "./stats.js";
|
|
5
5
|
import { getEncodingFromOptions, encodeUint8Array } from "./encoding.js";
|
|
6
|
+
import { normalizePath } from "./utils.js";
|
|
7
|
+
import { chmodSync, chownSync } from "./sync.js";
|
|
6
8
|
import GLib from "@girs/glib-2.0";
|
|
7
9
|
import Gio from "@girs/gio-2.0";
|
|
10
|
+
import { createInterface } from "node:readline";
|
|
8
11
|
import { Buffer } from "node:buffer";
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
12
|
+
const O_WRONLY = 1;
|
|
13
|
+
const O_RDWR = 2;
|
|
14
|
+
const O_CREAT = 64;
|
|
15
|
+
const O_TRUNC = 512;
|
|
16
|
+
const O_APPEND = 1024;
|
|
17
|
+
function resolveIOMode(flags) {
|
|
18
|
+
if (flags === void 0 || flags === null) return "r";
|
|
19
|
+
if (typeof flags === "number") {
|
|
20
|
+
const rdwr = (flags & O_RDWR) !== 0;
|
|
21
|
+
const wronly = (flags & O_WRONLY) !== 0;
|
|
22
|
+
const append = (flags & O_APPEND) !== 0;
|
|
23
|
+
const trunc = (flags & O_TRUNC) !== 0;
|
|
24
|
+
if (rdwr) return trunc ? "w+" : "r+";
|
|
25
|
+
if (wronly) return append ? "a" : "w";
|
|
26
|
+
return "r";
|
|
27
|
+
}
|
|
28
|
+
switch (flags) {
|
|
29
|
+
case "ax":
|
|
30
|
+
case "wx":
|
|
31
|
+
return "w";
|
|
32
|
+
case "ax+":
|
|
33
|
+
case "wx+":
|
|
34
|
+
return "w+";
|
|
35
|
+
case "as":
|
|
36
|
+
case "rs+":
|
|
37
|
+
return "r+";
|
|
38
|
+
case "as+":
|
|
39
|
+
return "a+";
|
|
40
|
+
default:
|
|
41
|
+
return flags;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function openIOChannel(path, mode, creat) {
|
|
45
|
+
try {
|
|
46
|
+
return GLib.IOChannel.new_file(path, mode);
|
|
47
|
+
} catch (err) {
|
|
48
|
+
const gErr = err;
|
|
49
|
+
if (creat && mode === "r+" && gErr?.code === GLib.FileError.NOENT) {
|
|
50
|
+
GLib.file_set_contents(path, new Uint8Array(0));
|
|
51
|
+
return GLib.IOChannel.new_file(path, mode);
|
|
52
|
+
}
|
|
53
|
+
throw err;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
31
56
|
function mapOpenError(err, path) {
|
|
32
|
-
|
|
33
|
-
const msg = gErr?.message ?? "";
|
|
34
|
-
const code = GLIB_FILE_ERROR_TO_NODE[gErr?.code ?? -1] ?? "EIO";
|
|
35
|
-
const error = new Error(`${code}: ${msg || "unknown error"}, open '${path}'`);
|
|
36
|
-
error.code = code;
|
|
37
|
-
error.syscall = "open";
|
|
38
|
-
error.path = path;
|
|
39
|
-
return error;
|
|
57
|
+
return createGLibFileError(err, "open", { path });
|
|
40
58
|
}
|
|
41
59
|
class FileHandle {
|
|
42
60
|
constructor(options) {
|
|
43
61
|
this.options = options;
|
|
44
62
|
this.options.flags ||= "r";
|
|
45
63
|
this.options.mode ||= 438;
|
|
64
|
+
const pathStr = normalizePath(options.path);
|
|
65
|
+
const creat = typeof options.flags === "number" && (options.flags & O_CREAT) !== 0;
|
|
66
|
+
const ioMode = resolveIOMode(options.flags);
|
|
46
67
|
try {
|
|
47
|
-
this._file =
|
|
68
|
+
this._file = openIOChannel(pathStr, ioMode, creat);
|
|
48
69
|
} catch (err) {
|
|
49
|
-
throw mapOpenError(err,
|
|
70
|
+
throw mapOpenError(err, pathStr);
|
|
50
71
|
}
|
|
51
72
|
this._file.set_encoding(null);
|
|
52
73
|
this.fd = this._file.unix_get_fd();
|
|
74
|
+
this._gFile = Gio.File.new_for_path(pathStr);
|
|
75
|
+
this._ioMode = ioMode;
|
|
53
76
|
FileHandle.instances[this.fd] = this;
|
|
54
77
|
return FileHandle.getInstance(this.fd);
|
|
55
78
|
}
|
|
56
79
|
options;
|
|
57
80
|
/** Not part of the default implementation, used internal by gjsify */
|
|
58
81
|
_file;
|
|
82
|
+
/**
|
|
83
|
+
* Lazily-opened Gio streams for positional read() / write() so each call does
|
|
84
|
+
* not re-load the entire file via Gio.File.load_contents(). The IOStream is
|
|
85
|
+
* used when the handle was opened with write capability (r+, w, w+, a, a+) —
|
|
86
|
+
* it shares seek state between input and output so writes are visible to
|
|
87
|
+
* subsequent reads without a flush. For read-only handles we only open a
|
|
88
|
+
* FileInputStream; trying to open_readwrite on a read-only file can fall
|
|
89
|
+
* back to create_readwrite(REPLACE_DESTINATION) which would truncate it.
|
|
90
|
+
*/
|
|
91
|
+
_ioStream = null;
|
|
92
|
+
_readStream = null;
|
|
93
|
+
_gFile;
|
|
94
|
+
_ioMode;
|
|
95
|
+
// Serialize async I/O on the shared FileIOStream. Concurrent write_bytes_async
|
|
96
|
+
// calls hit Gio.IOErrorEnum.PENDING ("Datenstrom hat noch einen ausstehenden
|
|
97
|
+
// Vorgang"); overlapping seek()s on the shared cursor also corrupt positions.
|
|
98
|
+
// random-access-file (used by fs-chunk-store / webtorrent) issues many
|
|
99
|
+
// concurrent positional writes, so every async op on this handle chains
|
|
100
|
+
// through _ioLock.
|
|
101
|
+
_ioLock = Promise.resolve();
|
|
59
102
|
/** Not part of the default implementation, used internal by gjsify */
|
|
60
103
|
static instances = {};
|
|
104
|
+
/**
|
|
105
|
+
* Lazy-open the read-capable stream and return both the input stream and
|
|
106
|
+
* its seekable view. Both FileInputStream (read-only handle) and
|
|
107
|
+
* FileIOStream (read/write handle) implement Gio.Seekable, but we return
|
|
108
|
+
* both to avoid callers needing to know which concrete type they got.
|
|
109
|
+
*/
|
|
110
|
+
_getReadStream() {
|
|
111
|
+
if (this._ioStream) {
|
|
112
|
+
return {
|
|
113
|
+
input: this._ioStream.get_input_stream(),
|
|
114
|
+
seekable: this._ioStream
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
if (this._ioMode === "r") {
|
|
118
|
+
if (!this._readStream) this._readStream = this._gFile.read(null);
|
|
119
|
+
return {
|
|
120
|
+
input: this._readStream,
|
|
121
|
+
seekable: this._readStream
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
this._ioStream = this._gFile.open_readwrite(null);
|
|
125
|
+
return {
|
|
126
|
+
input: this._ioStream.get_input_stream(),
|
|
127
|
+
seekable: this._ioStream
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/** Lazy-open the write-capable stream (IOStream) for this handle. Only valid
|
|
131
|
+
* when the handle was opened with a write-capable mode. */
|
|
132
|
+
_getWriteStream() {
|
|
133
|
+
if (this._ioStream) return this._ioStream;
|
|
134
|
+
if (this._ioMode === "r") {
|
|
135
|
+
throw new Error("FileHandle opened read-only; cannot write");
|
|
136
|
+
}
|
|
137
|
+
this._ioStream = this._gFile.open_readwrite(null);
|
|
138
|
+
return this._ioStream;
|
|
139
|
+
}
|
|
140
|
+
/** Serialize an async operation on the shared FileIOStream. */
|
|
141
|
+
_serialize(op) {
|
|
142
|
+
const prev = this._ioLock;
|
|
143
|
+
const next = prev.catch(() => {
|
|
144
|
+
}).then(op);
|
|
145
|
+
this._ioLock = next;
|
|
146
|
+
return next;
|
|
147
|
+
}
|
|
61
148
|
/**
|
|
62
149
|
* The numeric file descriptor managed by the {FileHandle} object.
|
|
63
150
|
* @since v10.0.0
|
|
@@ -98,7 +185,7 @@ class FileHandle {
|
|
|
98
185
|
* @return Fulfills with `undefined` upon success.
|
|
99
186
|
*/
|
|
100
187
|
async chown(uid, gid) {
|
|
101
|
-
|
|
188
|
+
chownSync(normalizePath(this.options.path), uid, gid);
|
|
102
189
|
}
|
|
103
190
|
/**
|
|
104
191
|
* Modifies the permissions on the file. See [`chmod(2)`](http://man7.org/linux/man-pages/man2/chmod.2.html).
|
|
@@ -107,7 +194,7 @@ class FileHandle {
|
|
|
107
194
|
* @return Fulfills with `undefined` upon success.
|
|
108
195
|
*/
|
|
109
196
|
async chmod(mode) {
|
|
110
|
-
|
|
197
|
+
chmodSync(normalizePath(this.options.path), mode);
|
|
111
198
|
}
|
|
112
199
|
/**
|
|
113
200
|
* Unlike the 16 kb default `highWaterMark` for a `stream.Readable`, the stream
|
|
@@ -193,7 +280,7 @@ class FileHandle {
|
|
|
193
280
|
* @return Fulfills with `undefined` upon success.
|
|
194
281
|
*/
|
|
195
282
|
async datasync() {
|
|
196
|
-
|
|
283
|
+
this._file.flush();
|
|
197
284
|
}
|
|
198
285
|
/**
|
|
199
286
|
* Request that all data for the open file descriptor is flushed to the storage
|
|
@@ -203,7 +290,7 @@ class FileHandle {
|
|
|
203
290
|
* @return Fufills with `undefined` upon success.
|
|
204
291
|
*/
|
|
205
292
|
async sync() {
|
|
206
|
-
|
|
293
|
+
this._file.flush();
|
|
207
294
|
}
|
|
208
295
|
async read(...args) {
|
|
209
296
|
let buffer;
|
|
@@ -225,19 +312,18 @@ class FileHandle {
|
|
|
225
312
|
const bufView = buffer;
|
|
226
313
|
const bufOffset = offset ?? 0;
|
|
227
314
|
const readLength = length ?? bufView?.byteLength ?? 65536;
|
|
228
|
-
const gFile = Gio.File.new_for_path(this.options.path.toString());
|
|
229
|
-
const [, fileContents] = gFile.load_contents(null);
|
|
230
|
-
const fileData = fileContents;
|
|
231
315
|
const startPos = position ?? 0;
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
bytesRead
|
|
239
|
-
|
|
240
|
-
|
|
316
|
+
return this._serialize(async () => {
|
|
317
|
+
const { input, seekable } = this._getReadStream();
|
|
318
|
+
seekable.seek(BigInt(startPos), GLib.SeekType.SET, null);
|
|
319
|
+
const bytes = input.read_bytes(readLength, null);
|
|
320
|
+
const data = bytes.get_data();
|
|
321
|
+
const bytesRead = data?.length ?? 0;
|
|
322
|
+
if (bufView && data && bytesRead > 0) {
|
|
323
|
+
bufView.set(data, bufOffset);
|
|
324
|
+
}
|
|
325
|
+
return { bytesRead, buffer };
|
|
326
|
+
});
|
|
241
327
|
}
|
|
242
328
|
/**
|
|
243
329
|
* Returns a `ReadableStream` that may be used to read the files data.
|
|
@@ -303,11 +389,20 @@ class FileHandle {
|
|
|
303
389
|
* @param options See `filehandle.createReadStream()` for the options.
|
|
304
390
|
*/
|
|
305
391
|
readLines(options) {
|
|
306
|
-
|
|
392
|
+
return createInterface({ input: this.createReadStream(options), crlfDelay: Infinity });
|
|
307
393
|
}
|
|
308
394
|
async stat(opts) {
|
|
309
|
-
|
|
310
|
-
|
|
395
|
+
const info = await new Promise((resolve, reject) => {
|
|
396
|
+
this._gFile.query_info_async(STAT_ATTRIBUTES, Gio.FileQueryInfoFlags.NONE, GLib.PRIORITY_DEFAULT, null, (_s, res) => {
|
|
397
|
+
try {
|
|
398
|
+
resolve(this._gFile.query_info_finish(res));
|
|
399
|
+
} catch (e) {
|
|
400
|
+
reject(e);
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
const pathStr = normalizePath(this.options.path);
|
|
405
|
+
return opts?.bigint ? new BigIntStats(info, pathStr) : new Stats(info, pathStr);
|
|
311
406
|
}
|
|
312
407
|
/**
|
|
313
408
|
* Truncates the file.
|
|
@@ -340,18 +435,16 @@ class FileHandle {
|
|
|
340
435
|
async truncate(len = 0) {
|
|
341
436
|
const effectiveLen = Math.max(0, len);
|
|
342
437
|
this._file.flush();
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
const newContent = new Uint8Array(effectiveLen);
|
|
346
|
-
newContent.set(currentContent.slice(0, Math.min(effectiveLen, currentContent.length)));
|
|
347
|
-
gFile.replace_contents(newContent, null, false, Gio.FileCreateFlags.NONE, null);
|
|
438
|
+
const out = this._getWriteStream().get_output_stream();
|
|
439
|
+
out.truncate(effectiveLen, null);
|
|
348
440
|
}
|
|
349
441
|
/**
|
|
350
442
|
* Change the file system timestamps of the object referenced by the `FileHandle` then resolves the promise with no arguments upon success.
|
|
351
443
|
* @since v10.0.0
|
|
352
444
|
*/
|
|
353
445
|
async utimes(atime, mtime) {
|
|
354
|
-
|
|
446
|
+
const { utimesSync } = await import("./utimes.js");
|
|
447
|
+
utimesSync(normalizePath(this.options.path), atime, mtime);
|
|
355
448
|
}
|
|
356
449
|
/**
|
|
357
450
|
* Asynchronously writes data to a file, replacing the file if it already exists.`data` can be a string, a buffer, an
|
|
@@ -410,21 +503,33 @@ class FileHandle {
|
|
|
410
503
|
const writeLength = length ?? writeBuf.byteLength - bufOffset;
|
|
411
504
|
const writeSlice = writeBuf.slice(bufOffset, bufOffset + writeLength);
|
|
412
505
|
const writePos = position ?? 0;
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
506
|
+
const bytesWritten = await this._serialize(async () => {
|
|
507
|
+
const stream = this._getWriteStream();
|
|
508
|
+
stream.seek(BigInt(writePos), GLib.SeekType.SET, null);
|
|
509
|
+
const output = stream.get_output_stream();
|
|
510
|
+
const written = await new Promise((resolve, reject) => {
|
|
511
|
+
output.write_bytes_async(new GLib.Bytes(writeSlice), GLib.PRIORITY_DEFAULT, null, (_source, asyncResult) => {
|
|
512
|
+
try {
|
|
513
|
+
resolve(output.write_bytes_finish(asyncResult));
|
|
514
|
+
} catch (err) {
|
|
515
|
+
reject(err);
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
});
|
|
519
|
+
await new Promise((resolve, reject) => {
|
|
520
|
+
output.flush_async(GLib.PRIORITY_DEFAULT, null, (_source, asyncResult) => {
|
|
521
|
+
try {
|
|
522
|
+
output.flush_finish(asyncResult);
|
|
523
|
+
resolve();
|
|
524
|
+
} catch (err) {
|
|
525
|
+
reject(err);
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
});
|
|
529
|
+
return written;
|
|
530
|
+
});
|
|
426
531
|
return {
|
|
427
|
-
bytesWritten
|
|
532
|
+
bytesWritten,
|
|
428
533
|
buffer: data
|
|
429
534
|
};
|
|
430
535
|
}
|
|
@@ -444,11 +549,18 @@ class FileHandle {
|
|
|
444
549
|
* position.
|
|
445
550
|
*/
|
|
446
551
|
async writev(buffers, position) {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
552
|
+
let bytesWritten = 0;
|
|
553
|
+
for (const buf of buffers) {
|
|
554
|
+
const b = Buffer.from(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
555
|
+
const res = await this.write(
|
|
556
|
+
b,
|
|
557
|
+
0,
|
|
558
|
+
b.byteLength,
|
|
559
|
+
position != null ? position + bytesWritten : null
|
|
560
|
+
);
|
|
561
|
+
bytesWritten += res.bytesWritten;
|
|
562
|
+
}
|
|
563
|
+
return { bytesWritten, buffers };
|
|
452
564
|
}
|
|
453
565
|
/**
|
|
454
566
|
* Read from a file and write to an array of [ArrayBufferView](https://developer.mozilla.org/en-US/docs/Web/API/ArrayBufferView) s
|
|
@@ -457,11 +569,65 @@ class FileHandle {
|
|
|
457
569
|
* @return Fulfills upon success an object containing two properties:
|
|
458
570
|
*/
|
|
459
571
|
async readv(buffers, position) {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
572
|
+
let bytesRead = 0;
|
|
573
|
+
for (const buf of buffers) {
|
|
574
|
+
const res = await this.read({
|
|
575
|
+
buffer: Buffer.from(buf.buffer, buf.byteOffset, buf.byteLength),
|
|
576
|
+
position: position != null ? position + bytesRead : null
|
|
577
|
+
});
|
|
578
|
+
bytesRead += res.bytesRead;
|
|
579
|
+
if (res.bytesRead < buf.byteLength) break;
|
|
580
|
+
}
|
|
581
|
+
return { bytesRead, buffers };
|
|
582
|
+
}
|
|
583
|
+
/** @internal */
|
|
584
|
+
_flushSync() {
|
|
585
|
+
this._file.flush();
|
|
586
|
+
}
|
|
587
|
+
/** @internal */
|
|
588
|
+
_closeSync() {
|
|
589
|
+
try {
|
|
590
|
+
this._ioStream?.close(null);
|
|
591
|
+
} catch {
|
|
592
|
+
}
|
|
593
|
+
try {
|
|
594
|
+
this._readStream?.close(null);
|
|
595
|
+
} catch {
|
|
596
|
+
}
|
|
597
|
+
this._ioStream = null;
|
|
598
|
+
this._readStream = null;
|
|
599
|
+
try {
|
|
600
|
+
this._file.shutdown(true);
|
|
601
|
+
} catch {
|
|
602
|
+
}
|
|
603
|
+
delete FileHandle.instances[this.fd];
|
|
604
|
+
}
|
|
605
|
+
/** @internal */
|
|
606
|
+
_readSync(buffer, offset, length, position) {
|
|
607
|
+
const stream = this._gFile.read(null);
|
|
608
|
+
try {
|
|
609
|
+
if (position !== null && position >= 0) {
|
|
610
|
+
stream.seek(position, GLib.SeekType.SET, null);
|
|
611
|
+
}
|
|
612
|
+
const bytes = stream.read_bytes(length, null);
|
|
613
|
+
const arr = bytes.get_data();
|
|
614
|
+
new Uint8Array(buffer.buffer, buffer.byteOffset + offset).set(arr.subarray(0, arr.length));
|
|
615
|
+
return arr.length;
|
|
616
|
+
} finally {
|
|
617
|
+
stream.close(null);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
/** @internal */
|
|
621
|
+
_writeSync(data, position) {
|
|
622
|
+
const stream = this._gFile.open_readwrite(null);
|
|
623
|
+
try {
|
|
624
|
+
if (position !== null && position >= 0) {
|
|
625
|
+
stream.seek(position, GLib.SeekType.SET, null);
|
|
626
|
+
}
|
|
627
|
+
return stream.get_output_stream().write_bytes(GLib.Bytes.new(data), null);
|
|
628
|
+
} finally {
|
|
629
|
+
stream.close(null);
|
|
630
|
+
}
|
|
465
631
|
}
|
|
466
632
|
/**
|
|
467
633
|
* Closes the file handle after waiting for any pending operation on the handle to
|
|
@@ -481,7 +647,20 @@ class FileHandle {
|
|
|
481
647
|
* @return Fulfills with `undefined` upon success.
|
|
482
648
|
*/
|
|
483
649
|
async close() {
|
|
484
|
-
|
|
650
|
+
try {
|
|
651
|
+
this._ioStream?.close(null);
|
|
652
|
+
} catch {
|
|
653
|
+
}
|
|
654
|
+
try {
|
|
655
|
+
this._readStream?.close(null);
|
|
656
|
+
} catch {
|
|
657
|
+
}
|
|
658
|
+
this._ioStream = null;
|
|
659
|
+
this._readStream = null;
|
|
660
|
+
try {
|
|
661
|
+
this._file.shutdown(true);
|
|
662
|
+
} catch {
|
|
663
|
+
}
|
|
485
664
|
}
|
|
486
665
|
async [Symbol.asyncDispose]() {
|
|
487
666
|
await this.close();
|
package/lib/esm/fs-watcher.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import GLib from "@girs/glib-2.0";
|
|
2
2
|
import Gio from "@girs/gio-2.0";
|
|
3
3
|
import { EventEmitter } from "node:events";
|
|
4
|
+
import { normalizePath } from "./utils.js";
|
|
4
5
|
const privates = /* @__PURE__ */ new WeakMap();
|
|
5
6
|
class FSWatcher extends EventEmitter {
|
|
6
7
|
constructor(filename, options, listener) {
|
|
@@ -9,12 +10,17 @@ class FSWatcher extends EventEmitter {
|
|
|
9
10
|
options = { persistent: true };
|
|
10
11
|
const persistent = options.persistent !== false;
|
|
11
12
|
const cancellable = Gio.Cancellable.new();
|
|
12
|
-
const
|
|
13
|
+
const pathStr = normalizePath(filename);
|
|
14
|
+
const file = Gio.File.new_for_path(pathStr);
|
|
13
15
|
const watcher = file.monitor(Gio.FileMonitorFlags.NONE, cancellable);
|
|
14
16
|
watcher.connect("changed", changed.bind(this));
|
|
15
17
|
let sourceId = null;
|
|
16
18
|
if (persistent) {
|
|
17
|
-
sourceId = GLib.timeout_add(
|
|
19
|
+
sourceId = GLib.timeout_add(
|
|
20
|
+
GLib.PRIORITY_LOW,
|
|
21
|
+
2147483647,
|
|
22
|
+
() => GLib.SOURCE_CONTINUE
|
|
23
|
+
);
|
|
18
24
|
}
|
|
19
25
|
privates.set(this, {
|
|
20
26
|
persistent,
|
|
@@ -45,7 +51,11 @@ class FSWatcher extends EventEmitter {
|
|
|
45
51
|
const priv = privates.get(this);
|
|
46
52
|
if (!priv.persistent && !priv.cancellable.is_cancelled()) {
|
|
47
53
|
priv.persistent = true;
|
|
48
|
-
priv.sourceId = GLib.timeout_add(
|
|
54
|
+
priv.sourceId = GLib.timeout_add(
|
|
55
|
+
GLib.PRIORITY_LOW,
|
|
56
|
+
2147483647,
|
|
57
|
+
() => GLib.SOURCE_CONTINUE
|
|
58
|
+
);
|
|
49
59
|
}
|
|
50
60
|
return this;
|
|
51
61
|
}
|
|
@@ -81,7 +91,81 @@ function changed(watcher, file, otherFile, eventType) {
|
|
|
81
91
|
}
|
|
82
92
|
}
|
|
83
93
|
var fs_watcher_default = FSWatcher;
|
|
94
|
+
function gioEventToNodeType(eventType) {
|
|
95
|
+
switch (eventType) {
|
|
96
|
+
case Gio.FileMonitorEvent.CHANGES_DONE_HINT:
|
|
97
|
+
return "change";
|
|
98
|
+
case Gio.FileMonitorEvent.DELETED:
|
|
99
|
+
case Gio.FileMonitorEvent.CREATED:
|
|
100
|
+
case Gio.FileMonitorEvent.RENAMED:
|
|
101
|
+
case Gio.FileMonitorEvent.MOVED_IN:
|
|
102
|
+
case Gio.FileMonitorEvent.MOVED_OUT:
|
|
103
|
+
return "rename";
|
|
104
|
+
default:
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async function* watchAsync(filename, options) {
|
|
109
|
+
const signal = options?.signal;
|
|
110
|
+
if (signal?.aborted) return;
|
|
111
|
+
const pathStr = normalizePath(filename);
|
|
112
|
+
const file = Gio.File.new_for_path(pathStr);
|
|
113
|
+
const cancellable = Gio.Cancellable.new();
|
|
114
|
+
let watcher;
|
|
115
|
+
try {
|
|
116
|
+
watcher = file.monitor(Gio.FileMonitorFlags.NONE, cancellable);
|
|
117
|
+
} catch {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const eventQueue = [];
|
|
121
|
+
const waiterQueue = [];
|
|
122
|
+
let finished = false;
|
|
123
|
+
function enqueue(event) {
|
|
124
|
+
if (finished) return;
|
|
125
|
+
if (waiterQueue.length > 0) {
|
|
126
|
+
waiterQueue.shift().resolve({ value: event, done: false });
|
|
127
|
+
} else {
|
|
128
|
+
eventQueue.push(event);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function terminate() {
|
|
132
|
+
if (finished) return;
|
|
133
|
+
finished = true;
|
|
134
|
+
if (!cancellable.is_cancelled()) cancellable.cancel();
|
|
135
|
+
while (waiterQueue.length > 0) {
|
|
136
|
+
waiterQueue.shift().resolve({ value: void 0, done: true });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const signalId = watcher.connect("changed", (_mon, changedFile, _otherFile, eventType) => {
|
|
140
|
+
const type = gioEventToNodeType(eventType);
|
|
141
|
+
if (type === null) return;
|
|
142
|
+
enqueue({ eventType: type, filename: changedFile?.get_basename() ?? null });
|
|
143
|
+
});
|
|
144
|
+
const abortHandler = () => terminate();
|
|
145
|
+
signal?.addEventListener("abort", abortHandler);
|
|
146
|
+
try {
|
|
147
|
+
while (!finished) {
|
|
148
|
+
if (eventQueue.length > 0) {
|
|
149
|
+
yield eventQueue.shift();
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const result = await new Promise((resolve) => {
|
|
153
|
+
waiterQueue.push({ resolve });
|
|
154
|
+
});
|
|
155
|
+
if (result.done) break;
|
|
156
|
+
yield result.value;
|
|
157
|
+
}
|
|
158
|
+
} finally {
|
|
159
|
+
signal?.removeEventListener("abort", abortHandler);
|
|
160
|
+
try {
|
|
161
|
+
watcher.disconnect(signalId);
|
|
162
|
+
} catch {
|
|
163
|
+
}
|
|
164
|
+
if (!cancellable.is_cancelled()) cancellable.cancel();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
84
167
|
export {
|
|
85
168
|
FSWatcher,
|
|
86
|
-
fs_watcher_default as default
|
|
169
|
+
fs_watcher_default as default,
|
|
170
|
+
watchAsync
|
|
87
171
|
};
|