@gjsify/fs 0.3.13 → 0.3.15
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/_virtual/_rolldown/runtime.js +18 -0
- package/lib/esm/callback.js +301 -302
- package/lib/esm/cp.js +207 -227
- package/lib/esm/dir.js +140 -148
- package/lib/esm/dirent.js +137 -128
- package/lib/esm/encoding.js +30 -27
- package/lib/esm/errors.js +15 -11
- package/lib/esm/fd-ops.js +105 -122
- package/lib/esm/file-handle.js +648 -653
- package/lib/esm/fs-watcher.js +153 -161
- package/lib/esm/glob.js +148 -149
- package/lib/esm/index.js +158 -351
- package/lib/esm/promises.js +413 -386
- package/lib/esm/read-stream.js +104 -108
- package/lib/esm/stat-watcher.js +113 -114
- package/lib/esm/statfs.js +56 -43
- package/lib/esm/stats.js +166 -159
- package/lib/esm/sync.js +366 -341
- package/lib/esm/types/index.js +6 -6
- package/lib/esm/utils.js +15 -17
- package/lib/esm/utimes.js +26 -37
- package/lib/esm/write-stream.js +96 -108
- package/package.json +10 -10
package/lib/esm/file-handle.js
CHANGED
|
@@ -1,671 +1,666 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { encodeUint8Array, getEncodingFromOptions } from "./encoding.js";
|
|
2
|
+
import { normalizePath } from "./utils.js";
|
|
2
3
|
import { ReadStream } from "./read-stream.js";
|
|
3
4
|
import { WriteStream } from "./write-stream.js";
|
|
4
|
-
import {
|
|
5
|
-
import { getEncodingFromOptions, encodeUint8Array } from "./encoding.js";
|
|
6
|
-
import { normalizePath } from "./utils.js";
|
|
5
|
+
import { BigIntStats, STAT_ATTRIBUTES, Stats } from "./stats.js";
|
|
7
6
|
import { chmodSync, chownSync } from "./sync.js";
|
|
8
7
|
import GLib from "@girs/glib-2.0";
|
|
9
8
|
import Gio from "@girs/gio-2.0";
|
|
10
|
-
import { createInterface } from "node:readline";
|
|
11
9
|
import { Buffer } from "node:buffer";
|
|
10
|
+
import { createGLibFileError } from "@gjsify/utils";
|
|
11
|
+
import { createInterface } from "node:readline";
|
|
12
|
+
|
|
13
|
+
//#region src/file-handle.ts
|
|
12
14
|
const O_WRONLY = 1;
|
|
13
15
|
const O_RDWR = 2;
|
|
14
16
|
const O_CREAT = 64;
|
|
15
17
|
const O_TRUNC = 512;
|
|
16
18
|
const O_APPEND = 1024;
|
|
19
|
+
/**
|
|
20
|
+
* Convert open flags (Node.js string or POSIX numeric) to a GLib.IOChannel mode.
|
|
21
|
+
* IOChannel.new_file() takes fopen(3) modes: 'r', 'r+', 'w', 'w+', 'a', 'a+'.
|
|
22
|
+
*/
|
|
17
23
|
function resolveIOMode(flags) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
case "as+":
|
|
39
|
-
return "a+";
|
|
40
|
-
default:
|
|
41
|
-
return flags;
|
|
42
|
-
}
|
|
24
|
+
if (flags === undefined || flags === null) return "r";
|
|
25
|
+
if (typeof flags === "number") {
|
|
26
|
+
const rdwr = (flags & O_RDWR) !== 0;
|
|
27
|
+
const wronly = (flags & O_WRONLY) !== 0;
|
|
28
|
+
const append = (flags & O_APPEND) !== 0;
|
|
29
|
+
const trunc = (flags & O_TRUNC) !== 0;
|
|
30
|
+
if (rdwr) return trunc ? "w+" : "r+";
|
|
31
|
+
if (wronly) return append ? "a" : "w";
|
|
32
|
+
return "r";
|
|
33
|
+
}
|
|
34
|
+
switch (flags) {
|
|
35
|
+
case "ax":
|
|
36
|
+
case "wx": return "w";
|
|
37
|
+
case "ax+":
|
|
38
|
+
case "wx+": return "w+";
|
|
39
|
+
case "as":
|
|
40
|
+
case "rs+": return "r+";
|
|
41
|
+
case "as+": return "a+";
|
|
42
|
+
default: return flags;
|
|
43
|
+
}
|
|
43
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Open the file with the given IOChannel mode. When the flags request
|
|
47
|
+
* create-if-missing + read/write without truncation (numeric O_CREAT | O_RDWR,
|
|
48
|
+
* which maps to IOChannel 'r+' — a mode that requires the file to exist), we
|
|
49
|
+
* catch the ENOENT and create an empty file, then retry. This avoids a TOCTOU
|
|
50
|
+
* existence check and keeps the common "file exists" path to a single syscall.
|
|
51
|
+
*/
|
|
44
52
|
function openIOChannel(path, mode, creat) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
try {
|
|
54
|
+
return GLib.IOChannel.new_file(path, mode);
|
|
55
|
+
} catch (err) {
|
|
56
|
+
const gErr = err;
|
|
57
|
+
if (creat && mode === "r+" && gErr?.code === GLib.FileError.NOENT) {
|
|
58
|
+
GLib.file_set_contents(path, new Uint8Array(0));
|
|
59
|
+
return GLib.IOChannel.new_file(path, mode);
|
|
60
|
+
}
|
|
61
|
+
throw err;
|
|
62
|
+
}
|
|
55
63
|
}
|
|
56
64
|
function mapOpenError(err, path) {
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
class FileHandle {
|
|
60
|
-
constructor(options) {
|
|
61
|
-
this.options = options;
|
|
62
|
-
this.options.flags ||= "r";
|
|
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);
|
|
67
|
-
try {
|
|
68
|
-
this._file = openIOChannel(pathStr, ioMode, creat);
|
|
69
|
-
} catch (err) {
|
|
70
|
-
throw mapOpenError(err, pathStr);
|
|
71
|
-
}
|
|
72
|
-
this._file.set_encoding(null);
|
|
73
|
-
this.fd = this._file.unix_get_fd();
|
|
74
|
-
this._gFile = Gio.File.new_for_path(pathStr);
|
|
75
|
-
this._ioMode = ioMode;
|
|
76
|
-
FileHandle.instances[this.fd] = this;
|
|
77
|
-
return FileHandle.getInstance(this.fd);
|
|
78
|
-
}
|
|
79
|
-
options;
|
|
80
|
-
/** Not part of the default implementation, used internal by gjsify */
|
|
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();
|
|
102
|
-
/** Not part of the default implementation, used internal by gjsify */
|
|
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
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* The numeric file descriptor managed by the {FileHandle} object.
|
|
150
|
-
* @since v10.0.0
|
|
151
|
-
*/
|
|
152
|
-
fd;
|
|
153
|
-
/** Not part of the default implementation, used internal by gjsify */
|
|
154
|
-
static getInstance(fd) {
|
|
155
|
-
const instance = FileHandle.instances[fd];
|
|
156
|
-
if (!instance) {
|
|
157
|
-
throw new Error("No instance found for fd!");
|
|
158
|
-
}
|
|
159
|
-
return FileHandle.instances[fd];
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* Alias of `filehandle.writeFile()`.
|
|
163
|
-
*
|
|
164
|
-
* When operating on file handles, the mode cannot be changed from what it was set
|
|
165
|
-
* to with `fsPromises.open()`. Therefore, this is equivalent to `filehandle.writeFile()`.
|
|
166
|
-
* @since v10.0.0
|
|
167
|
-
* @return Fulfills with `undefined` upon success.
|
|
168
|
-
*/
|
|
169
|
-
async appendFile(data, options) {
|
|
170
|
-
const encoding = getEncodingFromOptions(options);
|
|
171
|
-
if (typeof data === "string") {
|
|
172
|
-
data = Buffer.from(data);
|
|
173
|
-
}
|
|
174
|
-
if (encoding) this._file.set_encoding(encoding);
|
|
175
|
-
const [status, written] = this._file.write_chars(data, data.length);
|
|
176
|
-
if (status === GLib.IOStatus.ERROR) {
|
|
177
|
-
throw new Error("Error on append to file!");
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Changes the ownership of the file. A wrapper for [`chown(2)`](http://man7.org/linux/man-pages/man2/chown.2.html).
|
|
182
|
-
* @since v10.0.0
|
|
183
|
-
* @param uid The file's new owner's user id.
|
|
184
|
-
* @param gid The file's new group's group id.
|
|
185
|
-
* @return Fulfills with `undefined` upon success.
|
|
186
|
-
*/
|
|
187
|
-
async chown(uid, gid) {
|
|
188
|
-
chownSync(normalizePath(this.options.path), uid, gid);
|
|
189
|
-
}
|
|
190
|
-
/**
|
|
191
|
-
* Modifies the permissions on the file. See [`chmod(2)`](http://man7.org/linux/man-pages/man2/chmod.2.html).
|
|
192
|
-
* @since v10.0.0
|
|
193
|
-
* @param mode the file mode bit mask.
|
|
194
|
-
* @return Fulfills with `undefined` upon success.
|
|
195
|
-
*/
|
|
196
|
-
async chmod(mode) {
|
|
197
|
-
chmodSync(normalizePath(this.options.path), mode);
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* Unlike the 16 kb default `highWaterMark` for a `stream.Readable`, the stream
|
|
201
|
-
* returned by this method has a default `highWaterMark` of 64 kb.
|
|
202
|
-
*
|
|
203
|
-
* `options` can include `start` and `end` values to read a range of bytes from
|
|
204
|
-
* the file instead of the entire file. Both `start` and `end` are inclusive and
|
|
205
|
-
* start counting at 0, allowed values are in the
|
|
206
|
-
* \[0, [`Number.MAX_SAFE_INTEGER`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER)\] range. If `start` is
|
|
207
|
-
* omitted or `undefined`, `filehandle.createReadStream()` reads sequentially from
|
|
208
|
-
* the current file position. The `encoding` can be any one of those accepted by `Buffer`.
|
|
209
|
-
*
|
|
210
|
-
* If the `FileHandle` points to a character device that only supports blocking
|
|
211
|
-
* reads (such as keyboard or sound card), read operations do not finish until data
|
|
212
|
-
* is available. This can prevent the process from exiting and the stream from
|
|
213
|
-
* closing naturally.
|
|
214
|
-
*
|
|
215
|
-
* By default, the stream will emit a `'close'` event after it has been
|
|
216
|
-
* destroyed. Set the `emitClose` option to `false` to change this behavior.
|
|
217
|
-
*
|
|
218
|
-
* ```js
|
|
219
|
-
* import { open } from 'node:fs/promises';
|
|
220
|
-
*
|
|
221
|
-
* const fd = await open('/dev/input/event0');
|
|
222
|
-
* // Create a stream from some character device.
|
|
223
|
-
* const stream = fd.createReadStream();
|
|
224
|
-
* setTimeout(() => {
|
|
225
|
-
* stream.close(); // This may not close the stream.
|
|
226
|
-
* // Artificially marking end-of-stream, as if the underlying resource had
|
|
227
|
-
* // indicated end-of-file by itself, allows the stream to close.
|
|
228
|
-
* // This does not cancel pending read operations, and if there is such an
|
|
229
|
-
* // operation, the process may still not be able to exit successfully
|
|
230
|
-
* // until it finishes.
|
|
231
|
-
* stream.push(null);
|
|
232
|
-
* stream.read(0);
|
|
233
|
-
* }, 100);
|
|
234
|
-
* ```
|
|
235
|
-
*
|
|
236
|
-
* If `autoClose` is false, then the file descriptor won't be closed, even if
|
|
237
|
-
* there's an error. It is the application's responsibility to close it and make
|
|
238
|
-
* sure there's no file descriptor leak. If `autoClose` is set to true (default
|
|
239
|
-
* behavior), on `'error'` or `'end'` the file descriptor will be closed
|
|
240
|
-
* automatically.
|
|
241
|
-
*
|
|
242
|
-
* An example to read the last 10 bytes of a file which is 100 bytes long:
|
|
243
|
-
*
|
|
244
|
-
* ```js
|
|
245
|
-
* import { open } from 'node:fs/promises';
|
|
246
|
-
*
|
|
247
|
-
* const fd = await open('sample.txt');
|
|
248
|
-
* fd.createReadStream({ start: 90, end: 99 });
|
|
249
|
-
* ```
|
|
250
|
-
* @since v16.11.0
|
|
251
|
-
*/
|
|
252
|
-
createReadStream(options) {
|
|
253
|
-
return new ReadStream(this.options.path, options);
|
|
254
|
-
}
|
|
255
|
-
/**
|
|
256
|
-
* `options` may also include a `start` option to allow writing data at some
|
|
257
|
-
* position past the beginning of the file, allowed values are in the
|
|
258
|
-
* \[0, [`Number.MAX_SAFE_INTEGER`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER)\] range. Modifying a file rather than
|
|
259
|
-
* replacing it may require the `flags` `open` option to be set to `r+` rather than
|
|
260
|
-
* the default `r`. The `encoding` can be any one of those accepted by `Buffer`.
|
|
261
|
-
*
|
|
262
|
-
* If `autoClose` is set to true (default behavior) on `'error'` or `'finish'`the file descriptor will be closed automatically. If `autoClose` is false,
|
|
263
|
-
* then the file descriptor won't be closed, even if there's an error.
|
|
264
|
-
* It is the application's responsibility to close it and make sure there's no
|
|
265
|
-
* file descriptor leak.
|
|
266
|
-
*
|
|
267
|
-
* By default, the stream will emit a `'close'` event after it has been
|
|
268
|
-
* destroyed. Set the `emitClose` option to `false` to change this behavior.
|
|
269
|
-
* @since v16.11.0
|
|
270
|
-
*/
|
|
271
|
-
createWriteStream(options) {
|
|
272
|
-
return new WriteStream(this.options.path, options);
|
|
273
|
-
}
|
|
274
|
-
/**
|
|
275
|
-
* Forces all currently queued I/O operations associated with the file to the
|
|
276
|
-
* operating system's synchronized I/O completion state. Refer to the POSIX [`fdatasync(2)`](http://man7.org/linux/man-pages/man2/fdatasync.2.html) documentation for details.
|
|
277
|
-
*
|
|
278
|
-
* Unlike `filehandle.sync` this method does not flush modified metadata.
|
|
279
|
-
* @since v10.0.0
|
|
280
|
-
* @return Fulfills with `undefined` upon success.
|
|
281
|
-
*/
|
|
282
|
-
async datasync() {
|
|
283
|
-
this._file.flush();
|
|
284
|
-
}
|
|
285
|
-
/**
|
|
286
|
-
* Request that all data for the open file descriptor is flushed to the storage
|
|
287
|
-
* device. The specific implementation is operating system and device specific.
|
|
288
|
-
* Refer to the POSIX [`fsync(2)`](http://man7.org/linux/man-pages/man2/fsync.2.html) documentation for more detail.
|
|
289
|
-
* @since v10.0.0
|
|
290
|
-
* @return Fufills with `undefined` upon success.
|
|
291
|
-
*/
|
|
292
|
-
async sync() {
|
|
293
|
-
this._file.flush();
|
|
294
|
-
}
|
|
295
|
-
async read(...args) {
|
|
296
|
-
let buffer;
|
|
297
|
-
let offset;
|
|
298
|
-
let length;
|
|
299
|
-
let position;
|
|
300
|
-
if (typeof args[0] === "object" && !(args[0] instanceof Uint8Array) && !(args[0] instanceof Buffer)) {
|
|
301
|
-
const options = args[0];
|
|
302
|
-
buffer = options.buffer;
|
|
303
|
-
offset = options.offset;
|
|
304
|
-
length = options.length;
|
|
305
|
-
position = options.position;
|
|
306
|
-
} else {
|
|
307
|
-
buffer = args[0];
|
|
308
|
-
offset = args[1];
|
|
309
|
-
length = args[2];
|
|
310
|
-
position = args[3];
|
|
311
|
-
}
|
|
312
|
-
const bufView = buffer;
|
|
313
|
-
const bufOffset = offset ?? 0;
|
|
314
|
-
const readLength = length ?? bufView?.byteLength ?? 65536;
|
|
315
|
-
const startPos = position ?? 0;
|
|
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
|
-
});
|
|
327
|
-
}
|
|
328
|
-
/**
|
|
329
|
-
* Returns a `ReadableStream` that may be used to read the files data.
|
|
330
|
-
*
|
|
331
|
-
* An error will be thrown if this method is called more than once or is called after the `FileHandle` is closed
|
|
332
|
-
* or closing.
|
|
333
|
-
*
|
|
334
|
-
* ```js
|
|
335
|
-
* import { open } from 'node:fs/promises';
|
|
336
|
-
*
|
|
337
|
-
* const file = await open('./some/file/to/read');
|
|
338
|
-
*
|
|
339
|
-
* for await (const chunk of file.readableWebStream())
|
|
340
|
-
* console.log(chunk);
|
|
341
|
-
*
|
|
342
|
-
* await file.close();
|
|
343
|
-
* ```
|
|
344
|
-
*
|
|
345
|
-
* While the `ReadableStream` will read the file to completion, it will not close the `FileHandle` automatically. User code must still call the `fileHandle.close()` method.
|
|
346
|
-
*
|
|
347
|
-
* @since v17.0.0
|
|
348
|
-
* @experimental
|
|
349
|
-
*/
|
|
350
|
-
readableWebStream() {
|
|
351
|
-
const Ctor = globalThis.ReadableStream;
|
|
352
|
-
if (typeof Ctor !== "function") {
|
|
353
|
-
throw new Error(
|
|
354
|
-
'readableWebStream() requires a global ReadableStream. Import "node:stream/web" or "@gjsify/streams" before calling this method.'
|
|
355
|
-
);
|
|
356
|
-
}
|
|
357
|
-
return new Ctor();
|
|
358
|
-
}
|
|
359
|
-
/**
|
|
360
|
-
* Asynchronously reads the entire contents of a file. The underlying file will _not_ be closed automatically.
|
|
361
|
-
* The `FileHandle` must have been opened for reading.
|
|
362
|
-
* @param options An object that may contain an optional flag.
|
|
363
|
-
* If a flag is not provided, it defaults to `'r'`.
|
|
364
|
-
*/
|
|
365
|
-
async readFile(options) {
|
|
366
|
-
const encoding = getEncodingFromOptions(options, "buffer");
|
|
367
|
-
if (encoding) this._file.set_encoding(encoding);
|
|
368
|
-
const [status, buf] = this._file.read_to_end();
|
|
369
|
-
if (status === GLib.IOStatus.ERROR) {
|
|
370
|
-
throw new Error("Error on read from file!");
|
|
371
|
-
}
|
|
372
|
-
const res = encodeUint8Array(encoding, buf);
|
|
373
|
-
return res;
|
|
374
|
-
}
|
|
375
|
-
/**
|
|
376
|
-
* Convenience method to create a `readline` interface and stream over the file. For example:
|
|
377
|
-
*
|
|
378
|
-
* ```js
|
|
379
|
-
* import { open } from 'node:fs/promises';
|
|
380
|
-
*
|
|
381
|
-
* const file = await open('./some/file/to/read');
|
|
382
|
-
*
|
|
383
|
-
* for await (const line of file.readLines()) {
|
|
384
|
-
* console.log(line);
|
|
385
|
-
* }
|
|
386
|
-
* ```
|
|
387
|
-
*
|
|
388
|
-
* @since v18.11.0
|
|
389
|
-
* @param options See `filehandle.createReadStream()` for the options.
|
|
390
|
-
*/
|
|
391
|
-
readLines(options) {
|
|
392
|
-
return createInterface({ input: this.createReadStream(options), crlfDelay: Infinity });
|
|
393
|
-
}
|
|
394
|
-
async stat(opts) {
|
|
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);
|
|
406
|
-
}
|
|
407
|
-
/**
|
|
408
|
-
* Truncates the file.
|
|
409
|
-
*
|
|
410
|
-
* If the file was larger than `len` bytes, only the first `len` bytes will be
|
|
411
|
-
* retained in the file.
|
|
412
|
-
*
|
|
413
|
-
* The following example retains only the first four bytes of the file:
|
|
414
|
-
*
|
|
415
|
-
* ```js
|
|
416
|
-
* import { open } from 'node:fs/promises';
|
|
417
|
-
*
|
|
418
|
-
* let filehandle = null;
|
|
419
|
-
* try {
|
|
420
|
-
* filehandle = await open('temp.txt', 'r+');
|
|
421
|
-
* await filehandle.truncate(4);
|
|
422
|
-
* } finally {
|
|
423
|
-
* await filehandle?.close();
|
|
424
|
-
* }
|
|
425
|
-
* ```
|
|
426
|
-
*
|
|
427
|
-
* If the file previously was shorter than `len` bytes, it is extended, and the
|
|
428
|
-
* extended part is filled with null bytes (`'\0'`):
|
|
429
|
-
*
|
|
430
|
-
* If `len` is negative then `0` will be used.
|
|
431
|
-
* @since v10.0.0
|
|
432
|
-
* @param [len=0]
|
|
433
|
-
* @return Fulfills with `undefined` upon success.
|
|
434
|
-
*/
|
|
435
|
-
async truncate(len = 0) {
|
|
436
|
-
const effectiveLen = Math.max(0, len);
|
|
437
|
-
this._file.flush();
|
|
438
|
-
const out = this._getWriteStream().get_output_stream();
|
|
439
|
-
out.truncate(effectiveLen, null);
|
|
440
|
-
}
|
|
441
|
-
/**
|
|
442
|
-
* Change the file system timestamps of the object referenced by the `FileHandle` then resolves the promise with no arguments upon success.
|
|
443
|
-
* @since v10.0.0
|
|
444
|
-
*/
|
|
445
|
-
async utimes(atime, mtime) {
|
|
446
|
-
const { utimesSync } = await import("./utimes.js");
|
|
447
|
-
utimesSync(normalizePath(this.options.path), atime, mtime);
|
|
448
|
-
}
|
|
449
|
-
/**
|
|
450
|
-
* Asynchronously writes data to a file, replacing the file if it already exists.`data` can be a string, a buffer, an
|
|
451
|
-
* [AsyncIterable](https://tc39.github.io/ecma262/#sec-asynciterable-interface) or
|
|
452
|
-
* [Iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#The_iterable_protocol) object.
|
|
453
|
-
* The promise is resolved with no arguments upon success.
|
|
454
|
-
*
|
|
455
|
-
* If `options` is a string, then it specifies the `encoding`.
|
|
456
|
-
*
|
|
457
|
-
* The `FileHandle` has to support writing.
|
|
458
|
-
*
|
|
459
|
-
* It is unsafe to use `filehandle.writeFile()` multiple times on the same file
|
|
460
|
-
* without waiting for the promise to be resolved (or rejected).
|
|
461
|
-
*
|
|
462
|
-
* If one or more `filehandle.write()` calls are made on a file handle and then a`filehandle.writeFile()` call is made, the data will be written from the
|
|
463
|
-
* current position till the end of the file. It doesn't always write from the
|
|
464
|
-
* beginning of the file.
|
|
465
|
-
* @since v10.0.0
|
|
466
|
-
*/
|
|
467
|
-
async writeFile(data, options) {
|
|
468
|
-
const encoding = getEncodingFromOptions(options);
|
|
469
|
-
let buf;
|
|
470
|
-
if (typeof data === "string") {
|
|
471
|
-
buf = Buffer.from(data, encoding || "utf8");
|
|
472
|
-
} else {
|
|
473
|
-
buf = data;
|
|
474
|
-
}
|
|
475
|
-
this._file.seek_position(0, GLib.SeekType.SET);
|
|
476
|
-
const [status] = this._file.write_chars(buf, buf.length);
|
|
477
|
-
if (status === GLib.IOStatus.ERROR) {
|
|
478
|
-
throw new Error("Error writing to file!");
|
|
479
|
-
}
|
|
480
|
-
this._file.flush();
|
|
481
|
-
}
|
|
482
|
-
async write(data, ...args) {
|
|
483
|
-
let position = null;
|
|
484
|
-
let encoding = null;
|
|
485
|
-
let offset = null;
|
|
486
|
-
let length = null;
|
|
487
|
-
if (typeof data === "string") {
|
|
488
|
-
position = args[0];
|
|
489
|
-
encoding = args[1];
|
|
490
|
-
} else {
|
|
491
|
-
offset = args[0];
|
|
492
|
-
length = args[1];
|
|
493
|
-
position = args[2];
|
|
494
|
-
}
|
|
495
|
-
encoding = getEncodingFromOptions(encoding, typeof data === "string" ? "utf8" : null);
|
|
496
|
-
let writeBuf;
|
|
497
|
-
if (typeof data === "string") {
|
|
498
|
-
writeBuf = new TextEncoder().encode(data);
|
|
499
|
-
} else {
|
|
500
|
-
writeBuf = data;
|
|
501
|
-
}
|
|
502
|
-
const bufOffset = offset ?? 0;
|
|
503
|
-
const writeLength = length ?? writeBuf.byteLength - bufOffset;
|
|
504
|
-
const writeSlice = writeBuf.slice(bufOffset, bufOffset + writeLength);
|
|
505
|
-
const writePos = position ?? 0;
|
|
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
|
-
});
|
|
531
|
-
return {
|
|
532
|
-
bytesWritten,
|
|
533
|
-
buffer: data
|
|
534
|
-
};
|
|
535
|
-
}
|
|
536
|
-
/**
|
|
537
|
-
* Write an array of [ArrayBufferView](https://developer.mozilla.org/en-US/docs/Web/API/ArrayBufferView) s to the file.
|
|
538
|
-
*
|
|
539
|
-
* The promise is resolved with an object containing a two properties:
|
|
540
|
-
*
|
|
541
|
-
* It is unsafe to call `writev()` multiple times on the same file without waiting
|
|
542
|
-
* for the promise to be resolved (or rejected).
|
|
543
|
-
*
|
|
544
|
-
* On Linux, positional writes don't work when the file is opened in append mode.
|
|
545
|
-
* The kernel ignores the position argument and always appends the data to
|
|
546
|
-
* the end of the file.
|
|
547
|
-
* @since v12.9.0
|
|
548
|
-
* @param position The offset from the beginning of the file where the data from `buffers` should be written. If `position` is not a `number`, the data will be written at the current
|
|
549
|
-
* position.
|
|
550
|
-
*/
|
|
551
|
-
async writev(buffers, position) {
|
|
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 };
|
|
564
|
-
}
|
|
565
|
-
/**
|
|
566
|
-
* Read from a file and write to an array of [ArrayBufferView](https://developer.mozilla.org/en-US/docs/Web/API/ArrayBufferView) s
|
|
567
|
-
* @since v13.13.0, v12.17.0
|
|
568
|
-
* @param position The offset from the beginning of the file where the data should be read from. If `position` is not a `number`, the data will be read from the current position.
|
|
569
|
-
* @return Fulfills upon success an object containing two properties:
|
|
570
|
-
*/
|
|
571
|
-
async readv(buffers, position) {
|
|
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
|
-
}
|
|
631
|
-
}
|
|
632
|
-
/**
|
|
633
|
-
* Closes the file handle after waiting for any pending operation on the handle to
|
|
634
|
-
* complete.
|
|
635
|
-
*
|
|
636
|
-
* ```js
|
|
637
|
-
* import { open } from 'node:fs/promises';
|
|
638
|
-
*
|
|
639
|
-
* let filehandle;
|
|
640
|
-
* try {
|
|
641
|
-
* filehandle = await open('thefile.txt', 'r');
|
|
642
|
-
* } finally {
|
|
643
|
-
* await filehandle?.close();
|
|
644
|
-
* }
|
|
645
|
-
* ```
|
|
646
|
-
* @since v10.0.0
|
|
647
|
-
* @return Fulfills with `undefined` upon success.
|
|
648
|
-
*/
|
|
649
|
-
async close() {
|
|
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
|
-
}
|
|
664
|
-
}
|
|
665
|
-
async [Symbol.asyncDispose]() {
|
|
666
|
-
await this.close();
|
|
667
|
-
}
|
|
65
|
+
return createGLibFileError(err, "open", { path });
|
|
668
66
|
}
|
|
669
|
-
|
|
670
|
-
|
|
67
|
+
var FileHandle = class FileHandle {
|
|
68
|
+
/** Not part of the default implementation, used internal by gjsify */
|
|
69
|
+
_file;
|
|
70
|
+
/**
|
|
71
|
+
* Lazily-opened Gio streams for positional read() / write() so each call does
|
|
72
|
+
* not re-load the entire file via Gio.File.load_contents(). The IOStream is
|
|
73
|
+
* used when the handle was opened with write capability (r+, w, w+, a, a+) —
|
|
74
|
+
* it shares seek state between input and output so writes are visible to
|
|
75
|
+
* subsequent reads without a flush. For read-only handles we only open a
|
|
76
|
+
* FileInputStream; trying to open_readwrite on a read-only file can fall
|
|
77
|
+
* back to create_readwrite(REPLACE_DESTINATION) which would truncate it.
|
|
78
|
+
*/
|
|
79
|
+
_ioStream = null;
|
|
80
|
+
_readStream = null;
|
|
81
|
+
_gFile;
|
|
82
|
+
_ioMode;
|
|
83
|
+
_ioLock = Promise.resolve();
|
|
84
|
+
/** Not part of the default implementation, used internal by gjsify */
|
|
85
|
+
static instances = {};
|
|
86
|
+
constructor(options) {
|
|
87
|
+
this.options = options;
|
|
88
|
+
this.options.flags ||= "r";
|
|
89
|
+
this.options.mode ||= 438;
|
|
90
|
+
const pathStr = normalizePath(options.path);
|
|
91
|
+
const creat = typeof options.flags === "number" && (options.flags & O_CREAT) !== 0;
|
|
92
|
+
const ioMode = resolveIOMode(options.flags);
|
|
93
|
+
try {
|
|
94
|
+
this._file = openIOChannel(pathStr, ioMode, creat);
|
|
95
|
+
} catch (err) {
|
|
96
|
+
throw mapOpenError(err, pathStr);
|
|
97
|
+
}
|
|
98
|
+
this._file.set_encoding(null);
|
|
99
|
+
this.fd = this._file.unix_get_fd();
|
|
100
|
+
this._gFile = Gio.File.new_for_path(pathStr);
|
|
101
|
+
this._ioMode = ioMode;
|
|
102
|
+
FileHandle.instances[this.fd] = this;
|
|
103
|
+
return FileHandle.getInstance(this.fd);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Lazy-open the read-capable stream and return both the input stream and
|
|
107
|
+
* its seekable view. Both FileInputStream (read-only handle) and
|
|
108
|
+
* FileIOStream (read/write handle) implement Gio.Seekable, but we return
|
|
109
|
+
* both to avoid callers needing to know which concrete type they got.
|
|
110
|
+
*/
|
|
111
|
+
_getReadStream() {
|
|
112
|
+
if (this._ioStream) {
|
|
113
|
+
return {
|
|
114
|
+
input: this._ioStream.get_input_stream(),
|
|
115
|
+
seekable: this._ioStream
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
if (this._ioMode === "r") {
|
|
119
|
+
if (!this._readStream) this._readStream = this._gFile.read(null);
|
|
120
|
+
return {
|
|
121
|
+
input: this._readStream,
|
|
122
|
+
seekable: this._readStream
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
this._ioStream = this._gFile.open_readwrite(null);
|
|
126
|
+
return {
|
|
127
|
+
input: this._ioStream.get_input_stream(),
|
|
128
|
+
seekable: this._ioStream
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/** Lazy-open the write-capable stream (IOStream) for this handle. Only valid
|
|
132
|
+
* when the handle was opened with a write-capable mode. */
|
|
133
|
+
_getWriteStream() {
|
|
134
|
+
if (this._ioStream) return this._ioStream;
|
|
135
|
+
if (this._ioMode === "r") {
|
|
136
|
+
throw new Error("FileHandle opened read-only; cannot write");
|
|
137
|
+
}
|
|
138
|
+
this._ioStream = this._gFile.open_readwrite(null);
|
|
139
|
+
return this._ioStream;
|
|
140
|
+
}
|
|
141
|
+
/** Serialize an async operation on the shared FileIOStream. */
|
|
142
|
+
_serialize(op) {
|
|
143
|
+
const prev = this._ioLock;
|
|
144
|
+
const next = prev.catch(() => {}).then(op);
|
|
145
|
+
this._ioLock = next;
|
|
146
|
+
return next;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* The numeric file descriptor managed by the {FileHandle} object.
|
|
150
|
+
* @since v10.0.0
|
|
151
|
+
*/
|
|
152
|
+
fd;
|
|
153
|
+
/** Not part of the default implementation, used internal by gjsify */
|
|
154
|
+
static getInstance(fd) {
|
|
155
|
+
const instance = FileHandle.instances[fd];
|
|
156
|
+
if (!instance) {
|
|
157
|
+
throw new Error("No instance found for fd!");
|
|
158
|
+
}
|
|
159
|
+
return FileHandle.instances[fd];
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Alias of `filehandle.writeFile()`.
|
|
163
|
+
*
|
|
164
|
+
* When operating on file handles, the mode cannot be changed from what it was set
|
|
165
|
+
* to with `fsPromises.open()`. Therefore, this is equivalent to `filehandle.writeFile()`.
|
|
166
|
+
* @since v10.0.0
|
|
167
|
+
* @return Fulfills with `undefined` upon success.
|
|
168
|
+
*/
|
|
169
|
+
async appendFile(data, options) {
|
|
170
|
+
const encoding = getEncodingFromOptions(options);
|
|
171
|
+
if (typeof data === "string") {
|
|
172
|
+
data = Buffer.from(data);
|
|
173
|
+
}
|
|
174
|
+
if (encoding) this._file.set_encoding(encoding);
|
|
175
|
+
const [status, written] = this._file.write_chars(data, data.length);
|
|
176
|
+
if (status === GLib.IOStatus.ERROR) {
|
|
177
|
+
throw new Error("Error on append to file!");
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Changes the ownership of the file. A wrapper for [`chown(2)`](http://man7.org/linux/man-pages/man2/chown.2.html).
|
|
182
|
+
* @since v10.0.0
|
|
183
|
+
* @param uid The file's new owner's user id.
|
|
184
|
+
* @param gid The file's new group's group id.
|
|
185
|
+
* @return Fulfills with `undefined` upon success.
|
|
186
|
+
*/
|
|
187
|
+
async chown(uid, gid) {
|
|
188
|
+
chownSync(normalizePath(this.options.path), uid, gid);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Modifies the permissions on the file. See [`chmod(2)`](http://man7.org/linux/man-pages/man2/chmod.2.html).
|
|
192
|
+
* @since v10.0.0
|
|
193
|
+
* @param mode the file mode bit mask.
|
|
194
|
+
* @return Fulfills with `undefined` upon success.
|
|
195
|
+
*/
|
|
196
|
+
async chmod(mode) {
|
|
197
|
+
chmodSync(normalizePath(this.options.path), mode);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Unlike the 16 kb default `highWaterMark` for a `stream.Readable`, the stream
|
|
201
|
+
* returned by this method has a default `highWaterMark` of 64 kb.
|
|
202
|
+
*
|
|
203
|
+
* `options` can include `start` and `end` values to read a range of bytes from
|
|
204
|
+
* the file instead of the entire file. Both `start` and `end` are inclusive and
|
|
205
|
+
* start counting at 0, allowed values are in the
|
|
206
|
+
* \[0, [`Number.MAX_SAFE_INTEGER`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER)\] range. If `start` is
|
|
207
|
+
* omitted or `undefined`, `filehandle.createReadStream()` reads sequentially from
|
|
208
|
+
* the current file position. The `encoding` can be any one of those accepted by `Buffer`.
|
|
209
|
+
*
|
|
210
|
+
* If the `FileHandle` points to a character device that only supports blocking
|
|
211
|
+
* reads (such as keyboard or sound card), read operations do not finish until data
|
|
212
|
+
* is available. This can prevent the process from exiting and the stream from
|
|
213
|
+
* closing naturally.
|
|
214
|
+
*
|
|
215
|
+
* By default, the stream will emit a `'close'` event after it has been
|
|
216
|
+
* destroyed. Set the `emitClose` option to `false` to change this behavior.
|
|
217
|
+
*
|
|
218
|
+
* ```js
|
|
219
|
+
* import { open } from 'node:fs/promises';
|
|
220
|
+
*
|
|
221
|
+
* const fd = await open('/dev/input/event0');
|
|
222
|
+
* // Create a stream from some character device.
|
|
223
|
+
* const stream = fd.createReadStream();
|
|
224
|
+
* setTimeout(() => {
|
|
225
|
+
* stream.close(); // This may not close the stream.
|
|
226
|
+
* // Artificially marking end-of-stream, as if the underlying resource had
|
|
227
|
+
* // indicated end-of-file by itself, allows the stream to close.
|
|
228
|
+
* // This does not cancel pending read operations, and if there is such an
|
|
229
|
+
* // operation, the process may still not be able to exit successfully
|
|
230
|
+
* // until it finishes.
|
|
231
|
+
* stream.push(null);
|
|
232
|
+
* stream.read(0);
|
|
233
|
+
* }, 100);
|
|
234
|
+
* ```
|
|
235
|
+
*
|
|
236
|
+
* If `autoClose` is false, then the file descriptor won't be closed, even if
|
|
237
|
+
* there's an error. It is the application's responsibility to close it and make
|
|
238
|
+
* sure there's no file descriptor leak. If `autoClose` is set to true (default
|
|
239
|
+
* behavior), on `'error'` or `'end'` the file descriptor will be closed
|
|
240
|
+
* automatically.
|
|
241
|
+
*
|
|
242
|
+
* An example to read the last 10 bytes of a file which is 100 bytes long:
|
|
243
|
+
*
|
|
244
|
+
* ```js
|
|
245
|
+
* import { open } from 'node:fs/promises';
|
|
246
|
+
*
|
|
247
|
+
* const fd = await open('sample.txt');
|
|
248
|
+
* fd.createReadStream({ start: 90, end: 99 });
|
|
249
|
+
* ```
|
|
250
|
+
* @since v16.11.0
|
|
251
|
+
*/
|
|
252
|
+
createReadStream(options) {
|
|
253
|
+
return new ReadStream(this.options.path, options);
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* `options` may also include a `start` option to allow writing data at some
|
|
257
|
+
* position past the beginning of the file, allowed values are in the
|
|
258
|
+
* \[0, [`Number.MAX_SAFE_INTEGER`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER)\] range. Modifying a file rather than
|
|
259
|
+
* replacing it may require the `flags` `open` option to be set to `r+` rather than
|
|
260
|
+
* the default `r`. The `encoding` can be any one of those accepted by `Buffer`.
|
|
261
|
+
*
|
|
262
|
+
* If `autoClose` is set to true (default behavior) on `'error'` or `'finish'`the file descriptor will be closed automatically. If `autoClose` is false,
|
|
263
|
+
* then the file descriptor won't be closed, even if there's an error.
|
|
264
|
+
* It is the application's responsibility to close it and make sure there's no
|
|
265
|
+
* file descriptor leak.
|
|
266
|
+
*
|
|
267
|
+
* By default, the stream will emit a `'close'` event after it has been
|
|
268
|
+
* destroyed. Set the `emitClose` option to `false` to change this behavior.
|
|
269
|
+
* @since v16.11.0
|
|
270
|
+
*/
|
|
271
|
+
createWriteStream(options) {
|
|
272
|
+
return new WriteStream(this.options.path, options);
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Forces all currently queued I/O operations associated with the file to the
|
|
276
|
+
* operating system's synchronized I/O completion state. Refer to the POSIX [`fdatasync(2)`](http://man7.org/linux/man-pages/man2/fdatasync.2.html) documentation for details.
|
|
277
|
+
*
|
|
278
|
+
* Unlike `filehandle.sync` this method does not flush modified metadata.
|
|
279
|
+
* @since v10.0.0
|
|
280
|
+
* @return Fulfills with `undefined` upon success.
|
|
281
|
+
*/
|
|
282
|
+
async datasync() {
|
|
283
|
+
this._file.flush();
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Request that all data for the open file descriptor is flushed to the storage
|
|
287
|
+
* device. The specific implementation is operating system and device specific.
|
|
288
|
+
* Refer to the POSIX [`fsync(2)`](http://man7.org/linux/man-pages/man2/fsync.2.html) documentation for more detail.
|
|
289
|
+
* @since v10.0.0
|
|
290
|
+
* @return Fufills with `undefined` upon success.
|
|
291
|
+
*/
|
|
292
|
+
async sync() {
|
|
293
|
+
this._file.flush();
|
|
294
|
+
}
|
|
295
|
+
async read(...args) {
|
|
296
|
+
let buffer;
|
|
297
|
+
let offset;
|
|
298
|
+
let length;
|
|
299
|
+
let position;
|
|
300
|
+
if (typeof args[0] === "object" && !(args[0] instanceof Uint8Array) && !(args[0] instanceof Buffer)) {
|
|
301
|
+
const options = args[0];
|
|
302
|
+
buffer = options.buffer;
|
|
303
|
+
offset = options.offset;
|
|
304
|
+
length = options.length;
|
|
305
|
+
position = options.position;
|
|
306
|
+
} else {
|
|
307
|
+
buffer = args[0];
|
|
308
|
+
offset = args[1];
|
|
309
|
+
length = args[2];
|
|
310
|
+
position = args[3];
|
|
311
|
+
}
|
|
312
|
+
const bufView = buffer;
|
|
313
|
+
const bufOffset = offset ?? 0;
|
|
314
|
+
const readLength = length ?? bufView?.byteLength ?? 65536;
|
|
315
|
+
const startPos = position ?? 0;
|
|
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 {
|
|
326
|
+
bytesRead,
|
|
327
|
+
buffer
|
|
328
|
+
};
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Returns a `ReadableStream` that may be used to read the files data.
|
|
333
|
+
*
|
|
334
|
+
* An error will be thrown if this method is called more than once or is called after the `FileHandle` is closed
|
|
335
|
+
* or closing.
|
|
336
|
+
*
|
|
337
|
+
* ```js
|
|
338
|
+
* import { open } from 'node:fs/promises';
|
|
339
|
+
*
|
|
340
|
+
* const file = await open('./some/file/to/read');
|
|
341
|
+
*
|
|
342
|
+
* for await (const chunk of file.readableWebStream())
|
|
343
|
+
* console.log(chunk);
|
|
344
|
+
*
|
|
345
|
+
* await file.close();
|
|
346
|
+
* ```
|
|
347
|
+
*
|
|
348
|
+
* While the `ReadableStream` will read the file to completion, it will not close the `FileHandle` automatically. User code must still call the `fileHandle.close()` method.
|
|
349
|
+
*
|
|
350
|
+
* @since v17.0.0
|
|
351
|
+
* @experimental
|
|
352
|
+
*/
|
|
353
|
+
readableWebStream() {
|
|
354
|
+
const Ctor = globalThis.ReadableStream;
|
|
355
|
+
if (typeof Ctor !== "function") {
|
|
356
|
+
throw new Error("readableWebStream() requires a global ReadableStream. Import \"node:stream/web\" or \"@gjsify/streams\" before calling this method.");
|
|
357
|
+
}
|
|
358
|
+
return new Ctor();
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Asynchronously reads the entire contents of a file. The underlying file will _not_ be closed automatically.
|
|
362
|
+
* The `FileHandle` must have been opened for reading.
|
|
363
|
+
* @param options An object that may contain an optional flag.
|
|
364
|
+
* If a flag is not provided, it defaults to `'r'`.
|
|
365
|
+
*/
|
|
366
|
+
async readFile(options) {
|
|
367
|
+
const encoding = getEncodingFromOptions(options, "buffer");
|
|
368
|
+
if (encoding) this._file.set_encoding(encoding);
|
|
369
|
+
const [status, buf] = this._file.read_to_end();
|
|
370
|
+
if (status === GLib.IOStatus.ERROR) {
|
|
371
|
+
throw new Error("Error on read from file!");
|
|
372
|
+
}
|
|
373
|
+
const res = encodeUint8Array(encoding, buf);
|
|
374
|
+
return res;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Convenience method to create a `readline` interface and stream over the file. For example:
|
|
378
|
+
*
|
|
379
|
+
* ```js
|
|
380
|
+
* import { open } from 'node:fs/promises';
|
|
381
|
+
*
|
|
382
|
+
* const file = await open('./some/file/to/read');
|
|
383
|
+
*
|
|
384
|
+
* for await (const line of file.readLines()) {
|
|
385
|
+
* console.log(line);
|
|
386
|
+
* }
|
|
387
|
+
* ```
|
|
388
|
+
*
|
|
389
|
+
* @since v18.11.0
|
|
390
|
+
* @param options See `filehandle.createReadStream()` for the options.
|
|
391
|
+
*/
|
|
392
|
+
readLines(options) {
|
|
393
|
+
return createInterface({
|
|
394
|
+
input: this.createReadStream(options),
|
|
395
|
+
crlfDelay: Infinity
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
async stat(opts) {
|
|
399
|
+
const info = await new Promise((resolve, reject) => {
|
|
400
|
+
this._gFile.query_info_async(STAT_ATTRIBUTES, Gio.FileQueryInfoFlags.NONE, GLib.PRIORITY_DEFAULT, null, (_s, res) => {
|
|
401
|
+
try {
|
|
402
|
+
resolve(this._gFile.query_info_finish(res));
|
|
403
|
+
} catch (e) {
|
|
404
|
+
reject(e);
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
const pathStr = normalizePath(this.options.path);
|
|
409
|
+
return opts?.bigint ? new BigIntStats(info, pathStr) : new Stats(info, pathStr);
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Truncates the file.
|
|
413
|
+
*
|
|
414
|
+
* If the file was larger than `len` bytes, only the first `len` bytes will be
|
|
415
|
+
* retained in the file.
|
|
416
|
+
*
|
|
417
|
+
* The following example retains only the first four bytes of the file:
|
|
418
|
+
*
|
|
419
|
+
* ```js
|
|
420
|
+
* import { open } from 'node:fs/promises';
|
|
421
|
+
*
|
|
422
|
+
* let filehandle = null;
|
|
423
|
+
* try {
|
|
424
|
+
* filehandle = await open('temp.txt', 'r+');
|
|
425
|
+
* await filehandle.truncate(4);
|
|
426
|
+
* } finally {
|
|
427
|
+
* await filehandle?.close();
|
|
428
|
+
* }
|
|
429
|
+
* ```
|
|
430
|
+
*
|
|
431
|
+
* If the file previously was shorter than `len` bytes, it is extended, and the
|
|
432
|
+
* extended part is filled with null bytes (`'\0'`):
|
|
433
|
+
*
|
|
434
|
+
* If `len` is negative then `0` will be used.
|
|
435
|
+
* @since v10.0.0
|
|
436
|
+
* @param [len=0]
|
|
437
|
+
* @return Fulfills with `undefined` upon success.
|
|
438
|
+
*/
|
|
439
|
+
async truncate(len = 0) {
|
|
440
|
+
const effectiveLen = Math.max(0, len);
|
|
441
|
+
this._file.flush();
|
|
442
|
+
const out = this._getWriteStream().get_output_stream();
|
|
443
|
+
out.truncate(effectiveLen, null);
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Change the file system timestamps of the object referenced by the `FileHandle` then resolves the promise with no arguments upon success.
|
|
447
|
+
* @since v10.0.0
|
|
448
|
+
*/
|
|
449
|
+
async utimes(atime, mtime) {
|
|
450
|
+
const { utimesSync } = await import("./utimes.js");
|
|
451
|
+
utimesSync(normalizePath(this.options.path), atime, mtime);
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Asynchronously writes data to a file, replacing the file if it already exists.`data` can be a string, a buffer, an
|
|
455
|
+
* [AsyncIterable](https://tc39.github.io/ecma262/#sec-asynciterable-interface) or
|
|
456
|
+
* [Iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#The_iterable_protocol) object.
|
|
457
|
+
* The promise is resolved with no arguments upon success.
|
|
458
|
+
*
|
|
459
|
+
* If `options` is a string, then it specifies the `encoding`.
|
|
460
|
+
*
|
|
461
|
+
* The `FileHandle` has to support writing.
|
|
462
|
+
*
|
|
463
|
+
* It is unsafe to use `filehandle.writeFile()` multiple times on the same file
|
|
464
|
+
* without waiting for the promise to be resolved (or rejected).
|
|
465
|
+
*
|
|
466
|
+
* If one or more `filehandle.write()` calls are made on a file handle and then a`filehandle.writeFile()` call is made, the data will be written from the
|
|
467
|
+
* current position till the end of the file. It doesn't always write from the
|
|
468
|
+
* beginning of the file.
|
|
469
|
+
* @since v10.0.0
|
|
470
|
+
*/
|
|
471
|
+
async writeFile(data, options) {
|
|
472
|
+
const encoding = getEncodingFromOptions(options);
|
|
473
|
+
let buf;
|
|
474
|
+
if (typeof data === "string") {
|
|
475
|
+
buf = Buffer.from(data, encoding || "utf8");
|
|
476
|
+
} else {
|
|
477
|
+
buf = data;
|
|
478
|
+
}
|
|
479
|
+
this._file.seek_position(0, GLib.SeekType.SET);
|
|
480
|
+
const [status] = this._file.write_chars(buf, buf.length);
|
|
481
|
+
if (status === GLib.IOStatus.ERROR) {
|
|
482
|
+
throw new Error("Error writing to file!");
|
|
483
|
+
}
|
|
484
|
+
this._file.flush();
|
|
485
|
+
}
|
|
486
|
+
async write(data, ...args) {
|
|
487
|
+
let position = null;
|
|
488
|
+
let encoding = null;
|
|
489
|
+
let offset = null;
|
|
490
|
+
let length = null;
|
|
491
|
+
if (typeof data === "string") {
|
|
492
|
+
position = args[0];
|
|
493
|
+
encoding = args[1];
|
|
494
|
+
} else {
|
|
495
|
+
offset = args[0];
|
|
496
|
+
length = args[1];
|
|
497
|
+
position = args[2];
|
|
498
|
+
}
|
|
499
|
+
encoding = getEncodingFromOptions(encoding, typeof data === "string" ? "utf8" : null);
|
|
500
|
+
let writeBuf;
|
|
501
|
+
if (typeof data === "string") {
|
|
502
|
+
writeBuf = new TextEncoder().encode(data);
|
|
503
|
+
} else {
|
|
504
|
+
writeBuf = data;
|
|
505
|
+
}
|
|
506
|
+
const bufOffset = offset ?? 0;
|
|
507
|
+
const writeLength = length ?? writeBuf.byteLength - bufOffset;
|
|
508
|
+
const writeSlice = writeBuf.slice(bufOffset, bufOffset + writeLength);
|
|
509
|
+
const writePos = position ?? 0;
|
|
510
|
+
const bytesWritten = await this._serialize(async () => {
|
|
511
|
+
const stream = this._getWriteStream();
|
|
512
|
+
stream.seek(BigInt(writePos), GLib.SeekType.SET, null);
|
|
513
|
+
const output = stream.get_output_stream();
|
|
514
|
+
const written = await new Promise((resolve, reject) => {
|
|
515
|
+
output.write_bytes_async(new GLib.Bytes(writeSlice), GLib.PRIORITY_DEFAULT, null, (_source, asyncResult) => {
|
|
516
|
+
try {
|
|
517
|
+
resolve(output.write_bytes_finish(asyncResult));
|
|
518
|
+
} catch (err) {
|
|
519
|
+
reject(err);
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
await new Promise((resolve, reject) => {
|
|
524
|
+
output.flush_async(GLib.PRIORITY_DEFAULT, null, (_source, asyncResult) => {
|
|
525
|
+
try {
|
|
526
|
+
output.flush_finish(asyncResult);
|
|
527
|
+
resolve();
|
|
528
|
+
} catch (err) {
|
|
529
|
+
reject(err);
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
return written;
|
|
534
|
+
});
|
|
535
|
+
return {
|
|
536
|
+
bytesWritten,
|
|
537
|
+
buffer: data
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Write an array of [ArrayBufferView](https://developer.mozilla.org/en-US/docs/Web/API/ArrayBufferView) s to the file.
|
|
542
|
+
*
|
|
543
|
+
* The promise is resolved with an object containing a two properties:
|
|
544
|
+
*
|
|
545
|
+
* It is unsafe to call `writev()` multiple times on the same file without waiting
|
|
546
|
+
* for the promise to be resolved (or rejected).
|
|
547
|
+
*
|
|
548
|
+
* On Linux, positional writes don't work when the file is opened in append mode.
|
|
549
|
+
* The kernel ignores the position argument and always appends the data to
|
|
550
|
+
* the end of the file.
|
|
551
|
+
* @since v12.9.0
|
|
552
|
+
* @param position The offset from the beginning of the file where the data from `buffers` should be written. If `position` is not a `number`, the data will be written at the current
|
|
553
|
+
* position.
|
|
554
|
+
*/
|
|
555
|
+
async writev(buffers, position) {
|
|
556
|
+
let bytesWritten = 0;
|
|
557
|
+
for (const buf of buffers) {
|
|
558
|
+
const b = Buffer.from(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
559
|
+
const res = await this.write(b, 0, b.byteLength, position != null ? position + bytesWritten : null);
|
|
560
|
+
bytesWritten += res.bytesWritten;
|
|
561
|
+
}
|
|
562
|
+
return {
|
|
563
|
+
bytesWritten,
|
|
564
|
+
buffers
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Read from a file and write to an array of [ArrayBufferView](https://developer.mozilla.org/en-US/docs/Web/API/ArrayBufferView) s
|
|
569
|
+
* @since v13.13.0, v12.17.0
|
|
570
|
+
* @param position The offset from the beginning of the file where the data should be read from. If `position` is not a `number`, the data will be read from the current position.
|
|
571
|
+
* @return Fulfills upon success an object containing two properties:
|
|
572
|
+
*/
|
|
573
|
+
async readv(buffers, position) {
|
|
574
|
+
let bytesRead = 0;
|
|
575
|
+
for (const buf of buffers) {
|
|
576
|
+
const res = await this.read({
|
|
577
|
+
buffer: Buffer.from(buf.buffer, buf.byteOffset, buf.byteLength),
|
|
578
|
+
position: position != null ? position + bytesRead : null
|
|
579
|
+
});
|
|
580
|
+
bytesRead += res.bytesRead;
|
|
581
|
+
if (res.bytesRead < buf.byteLength) break;
|
|
582
|
+
}
|
|
583
|
+
return {
|
|
584
|
+
bytesRead,
|
|
585
|
+
buffers
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
/** @internal */ _flushSync() {
|
|
589
|
+
this._file.flush();
|
|
590
|
+
}
|
|
591
|
+
/** @internal */ _closeSync() {
|
|
592
|
+
try {
|
|
593
|
+
this._ioStream?.close(null);
|
|
594
|
+
} catch {}
|
|
595
|
+
try {
|
|
596
|
+
this._readStream?.close(null);
|
|
597
|
+
} catch {}
|
|
598
|
+
this._ioStream = null;
|
|
599
|
+
this._readStream = null;
|
|
600
|
+
try {
|
|
601
|
+
this._file.shutdown(true);
|
|
602
|
+
} catch {}
|
|
603
|
+
delete FileHandle.instances[this.fd];
|
|
604
|
+
}
|
|
605
|
+
/** @internal */ _readSync(buffer, offset, length, position) {
|
|
606
|
+
const stream = this._gFile.read(null);
|
|
607
|
+
try {
|
|
608
|
+
if (position !== null && position >= 0) {
|
|
609
|
+
stream.seek(position, GLib.SeekType.SET, null);
|
|
610
|
+
}
|
|
611
|
+
const bytes = stream.read_bytes(length, null);
|
|
612
|
+
const arr = bytes.get_data();
|
|
613
|
+
new Uint8Array(buffer.buffer, buffer.byteOffset + offset).set(arr.subarray(0, arr.length));
|
|
614
|
+
return arr.length;
|
|
615
|
+
} finally {
|
|
616
|
+
stream.close(null);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
/** @internal */ _writeSync(data, position) {
|
|
620
|
+
const stream = this._gFile.open_readwrite(null);
|
|
621
|
+
try {
|
|
622
|
+
if (position !== null && position >= 0) {
|
|
623
|
+
stream.seek(position, GLib.SeekType.SET, null);
|
|
624
|
+
}
|
|
625
|
+
return stream.get_output_stream().write_bytes(GLib.Bytes.new(data), null);
|
|
626
|
+
} finally {
|
|
627
|
+
stream.close(null);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Closes the file handle after waiting for any pending operation on the handle to
|
|
632
|
+
* complete.
|
|
633
|
+
*
|
|
634
|
+
* ```js
|
|
635
|
+
* import { open } from 'node:fs/promises';
|
|
636
|
+
*
|
|
637
|
+
* let filehandle;
|
|
638
|
+
* try {
|
|
639
|
+
* filehandle = await open('thefile.txt', 'r');
|
|
640
|
+
* } finally {
|
|
641
|
+
* await filehandle?.close();
|
|
642
|
+
* }
|
|
643
|
+
* ```
|
|
644
|
+
* @since v10.0.0
|
|
645
|
+
* @return Fulfills with `undefined` upon success.
|
|
646
|
+
*/
|
|
647
|
+
async close() {
|
|
648
|
+
try {
|
|
649
|
+
this._ioStream?.close(null);
|
|
650
|
+
} catch {}
|
|
651
|
+
try {
|
|
652
|
+
this._readStream?.close(null);
|
|
653
|
+
} catch {}
|
|
654
|
+
this._ioStream = null;
|
|
655
|
+
this._readStream = null;
|
|
656
|
+
try {
|
|
657
|
+
this._file.shutdown(true);
|
|
658
|
+
} catch {}
|
|
659
|
+
}
|
|
660
|
+
async [Symbol.asyncDispose]() {
|
|
661
|
+
await this.close();
|
|
662
|
+
}
|
|
671
663
|
};
|
|
664
|
+
|
|
665
|
+
//#endregion
|
|
666
|
+
export { FileHandle };
|