@gjsify/fs 0.1.15 → 0.3.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/src/promises.ts
CHANGED
|
@@ -6,8 +6,26 @@ import GLib from '@girs/glib-2.0';
|
|
|
6
6
|
import { join, dirname } from 'node:path';
|
|
7
7
|
import { getEncodingFromOptions, encodeUint8Array, decode } from './encoding.js';
|
|
8
8
|
import { realpathSync, readdirSync as readdirSyncFn, renameSync, copyFileSync, accessSync, appendFileSync, readlinkSync, truncateSync, chmodSync, chownSync, linkSync } from './sync.js';
|
|
9
|
+
import { cpAsync } from './cp.js';
|
|
10
|
+
import { opendirAsync, Dir } from './dir.js';
|
|
11
|
+
import { globAsync } from './glob.js';
|
|
12
|
+
import { watchAsync } from './fs-watcher.js';
|
|
13
|
+
import { statfsAsync } from './statfs.js';
|
|
14
|
+
import { utimesAsync, lutimesAsync, lchownAsync, lchmodAsync } from './utimes.js';
|
|
15
|
+
import {
|
|
16
|
+
fstatAsync,
|
|
17
|
+
ftruncateAsync,
|
|
18
|
+
fdatasyncAsync,
|
|
19
|
+
fsyncAsync,
|
|
20
|
+
fchmodAsync,
|
|
21
|
+
fchownAsync,
|
|
22
|
+
futimesAsync,
|
|
23
|
+
readvAsync,
|
|
24
|
+
writevAsync,
|
|
25
|
+
openAsBlob,
|
|
26
|
+
} from './fd-ops.js';
|
|
9
27
|
import { FileHandle } from './file-handle.js';
|
|
10
|
-
import { tempDirPath } from './utils.js';
|
|
28
|
+
import { tempDirPath, normalizePath } from './utils.js';
|
|
11
29
|
import { Dirent } from './dirent.js';
|
|
12
30
|
import { Stats, BigIntStats, STAT_ATTRIBUTES } from './stats.js';
|
|
13
31
|
import { createNodeError } from './errors.js';
|
|
@@ -77,7 +95,7 @@ async function mkdir(path: PathLike, options?: Mode | MakeDirectoryOptions | nul
|
|
|
77
95
|
_mode = options;
|
|
78
96
|
}
|
|
79
97
|
|
|
80
|
-
const pathStr = path
|
|
98
|
+
const pathStr = normalizePath(path);
|
|
81
99
|
|
|
82
100
|
if (recursive) {
|
|
83
101
|
return mkdirRecursiveAsync(pathStr);
|
|
@@ -90,7 +108,7 @@ async function mkdir(path: PathLike, options?: Mode | MakeDirectoryOptions | nul
|
|
|
90
108
|
file.make_directory_finish(res);
|
|
91
109
|
resolve(undefined);
|
|
92
110
|
} catch (err: unknown) {
|
|
93
|
-
reject(createNodeError(err, 'mkdir',
|
|
111
|
+
reject(createNodeError(err, 'mkdir', pathStr));
|
|
94
112
|
}
|
|
95
113
|
});
|
|
96
114
|
});
|
|
@@ -152,7 +170,8 @@ async function mkdirRecursiveAsync(pathStr: string): Promise<string | undefined>
|
|
|
152
170
|
}
|
|
153
171
|
|
|
154
172
|
async function readFile(path: PathLike | FileHandle, options: ReadOptions = { encoding: null, flag: 'r' }) {
|
|
155
|
-
const
|
|
173
|
+
const pathStr = normalizePath(path as PathLike);
|
|
174
|
+
const file = Gio.File.new_for_path(pathStr);
|
|
156
175
|
|
|
157
176
|
let ok: boolean;
|
|
158
177
|
let data: Uint8Array;
|
|
@@ -167,11 +186,11 @@ async function readFile(path: PathLike | FileHandle, options: ReadOptions = { en
|
|
|
167
186
|
});
|
|
168
187
|
});
|
|
169
188
|
} catch (error) {
|
|
170
|
-
throw createNodeError(error, 'open',
|
|
189
|
+
throw createNodeError(error, 'open', pathStr);
|
|
171
190
|
}
|
|
172
191
|
|
|
173
192
|
if (!ok) {
|
|
174
|
-
throw createNodeError(new Error('failed to read file'), 'open',
|
|
193
|
+
throw createNodeError(new Error('failed to read file'), 'open', pathStr);
|
|
175
194
|
}
|
|
176
195
|
|
|
177
196
|
return encodeUint8Array(getEncodingFromOptions(options, 'buffer'), data);
|
|
@@ -230,8 +249,9 @@ async function mkdtemp(prefix: string, options?: BufferEncodingOption | ObjectEn
|
|
|
230
249
|
return decode(path, encoding);
|
|
231
250
|
}
|
|
232
251
|
|
|
233
|
-
async function writeFile(path:
|
|
234
|
-
const
|
|
252
|
+
async function writeFile(path: PathLike, data: string | Uint8Array | unknown) {
|
|
253
|
+
const pathStr = normalizePath(path);
|
|
254
|
+
const file = Gio.File.new_for_path(pathStr);
|
|
235
255
|
|
|
236
256
|
// Convert data to Uint8Array if it's a string
|
|
237
257
|
let bytes: Uint8Array;
|
|
@@ -250,7 +270,7 @@ async function writeFile(path: string, data: string | Uint8Array | unknown) {
|
|
|
250
270
|
try {
|
|
251
271
|
resolve(file.replace_finish(res));
|
|
252
272
|
} catch (err: unknown) {
|
|
253
|
-
reject(createNodeError(err, 'open',
|
|
273
|
+
reject(createNodeError(err, 'open', pathStr));
|
|
254
274
|
}
|
|
255
275
|
});
|
|
256
276
|
});
|
|
@@ -264,7 +284,7 @@ async function writeFile(path: string, data: string | Uint8Array | unknown) {
|
|
|
264
284
|
outputStream.write_bytes_finish(res);
|
|
265
285
|
resolve();
|
|
266
286
|
} catch (err: unknown) {
|
|
267
|
-
reject(createNodeError(err, 'write',
|
|
287
|
+
reject(createNodeError(err, 'write', pathStr));
|
|
268
288
|
}
|
|
269
289
|
});
|
|
270
290
|
});
|
|
@@ -277,7 +297,7 @@ async function writeFile(path: string, data: string | Uint8Array | unknown) {
|
|
|
277
297
|
outputStream.close_finish(res);
|
|
278
298
|
resolve();
|
|
279
299
|
} catch (err: unknown) {
|
|
280
|
-
reject(createNodeError(err, 'close',
|
|
300
|
+
reject(createNodeError(err, 'close', pathStr));
|
|
281
301
|
}
|
|
282
302
|
});
|
|
283
303
|
});
|
|
@@ -294,20 +314,21 @@ async function writeFile(path: string, data: string | Uint8Array | unknown) {
|
|
|
294
314
|
* @return Fulfills with `undefined` upon success.
|
|
295
315
|
*/
|
|
296
316
|
async function rmdir(path: PathLike, _options?: RmDirOptions): Promise<void> {
|
|
297
|
-
const
|
|
317
|
+
const pathStr = normalizePath(path);
|
|
318
|
+
const file = Gio.File.new_for_path(pathStr);
|
|
298
319
|
// Check if it's a directory
|
|
299
320
|
const info = await new Promise<Gio.FileInfo>((resolve, reject) => {
|
|
300
321
|
file.query_info_async('standard::type', Gio.FileQueryInfoFlags.NONE, GLib.PRIORITY_DEFAULT, null, (_s: unknown, res: Gio.AsyncResult) => {
|
|
301
322
|
try {
|
|
302
323
|
resolve(file.query_info_finish(res));
|
|
303
324
|
} catch (err: unknown) {
|
|
304
|
-
reject(createNodeError(err, 'rmdir',
|
|
325
|
+
reject(createNodeError(err, 'rmdir', pathStr));
|
|
305
326
|
}
|
|
306
327
|
});
|
|
307
328
|
});
|
|
308
329
|
if (info.get_file_type() !== Gio.FileType.DIRECTORY) {
|
|
309
330
|
const err = Object.assign(new Error(), { code: 4 }); // Gio.IOErrorEnum.NOT_DIRECTORY
|
|
310
|
-
throw createNodeError(err, 'rmdir',
|
|
331
|
+
throw createNodeError(err, 'rmdir', pathStr);
|
|
311
332
|
}
|
|
312
333
|
// Check if empty
|
|
313
334
|
const children = await new Promise<Gio.FileEnumerator>((resolve, reject) => {
|
|
@@ -315,14 +336,14 @@ async function rmdir(path: PathLike, _options?: RmDirOptions): Promise<void> {
|
|
|
315
336
|
try {
|
|
316
337
|
resolve(file.enumerate_children_finish(res));
|
|
317
338
|
} catch (err: unknown) {
|
|
318
|
-
reject(createNodeError(err, 'rmdir',
|
|
339
|
+
reject(createNodeError(err, 'rmdir', pathStr));
|
|
319
340
|
}
|
|
320
341
|
});
|
|
321
342
|
});
|
|
322
343
|
const firstChild = children.next_file(null);
|
|
323
344
|
if (firstChild !== null) {
|
|
324
345
|
const err = Object.assign(new Error(), { code: 5 }); // Gio.IOErrorEnum.NOT_EMPTY
|
|
325
|
-
throw createNodeError(err, 'rmdir',
|
|
346
|
+
throw createNodeError(err, 'rmdir', pathStr);
|
|
326
347
|
}
|
|
327
348
|
// Delete the empty directory
|
|
328
349
|
await new Promise<void>((resolve, reject) => {
|
|
@@ -331,29 +352,30 @@ async function rmdir(path: PathLike, _options?: RmDirOptions): Promise<void> {
|
|
|
331
352
|
file.delete_finish(res);
|
|
332
353
|
resolve();
|
|
333
354
|
} catch (err: unknown) {
|
|
334
|
-
reject(createNodeError(err, 'rmdir',
|
|
355
|
+
reject(createNodeError(err, 'rmdir', pathStr));
|
|
335
356
|
}
|
|
336
357
|
});
|
|
337
358
|
});
|
|
338
359
|
}
|
|
339
360
|
|
|
340
|
-
async function unlink(path:
|
|
341
|
-
const
|
|
361
|
+
async function unlink(path: PathLike): Promise<void> {
|
|
362
|
+
const pathStr = normalizePath(path);
|
|
363
|
+
const file = Gio.File.new_for_path(pathStr);
|
|
342
364
|
await new Promise<void>((resolve, reject) => {
|
|
343
365
|
file.delete_async(GLib.PRIORITY_DEFAULT, null, (_s: unknown, res: Gio.AsyncResult) => {
|
|
344
366
|
try {
|
|
345
367
|
file.delete_finish(res);
|
|
346
368
|
resolve();
|
|
347
369
|
} catch (err: unknown) {
|
|
348
|
-
reject(createNodeError(err, 'unlink',
|
|
370
|
+
reject(createNodeError(err, 'unlink', pathStr));
|
|
349
371
|
}
|
|
350
372
|
});
|
|
351
373
|
});
|
|
352
374
|
}
|
|
353
375
|
|
|
354
|
-
async function open(path: PathLike, flags?: OpenFlags, mode?: Mode): Promise<FileHandle> {
|
|
376
|
+
async function open(path: PathLike, flags?: OpenFlags | number, mode?: Mode): Promise<FileHandle> {
|
|
355
377
|
// FileHandle constructor maps GLib.FileError to NodeJS.ErrnoException on failure.
|
|
356
|
-
return new FileHandle({ path, flags, mode });
|
|
378
|
+
return new FileHandle({ path, flags: flags as OpenFlags | undefined, mode });
|
|
357
379
|
}
|
|
358
380
|
|
|
359
381
|
async function write<TBuffer extends Uint8Array>(
|
|
@@ -425,14 +447,15 @@ async function _writeStr(
|
|
|
425
447
|
// --- helpers ---
|
|
426
448
|
|
|
427
449
|
function queryInfoAsync(path: PathLike, flags: Gio.FileQueryInfoFlags, syscall: string, options?: { bigint?: boolean }): Promise<Stats | BigIntStats> {
|
|
450
|
+
const pathStr = normalizePath(path);
|
|
428
451
|
return new Promise((resolve, reject) => {
|
|
429
|
-
const file = Gio.File.new_for_path(
|
|
452
|
+
const file = Gio.File.new_for_path(pathStr);
|
|
430
453
|
file.query_info_async(STAT_ATTRIBUTES, flags, GLib.PRIORITY_DEFAULT, null, (_s: unknown, res: Gio.AsyncResult) => {
|
|
431
454
|
try {
|
|
432
455
|
const info = file.query_info_finish(res);
|
|
433
|
-
resolve(options?.bigint ? new BigIntStats(info,
|
|
456
|
+
resolve(options?.bigint ? new BigIntStats(info, pathStr) : new Stats(info, pathStr));
|
|
434
457
|
} catch (err: unknown) {
|
|
435
|
-
reject(createNodeError(err, syscall,
|
|
458
|
+
reject(createNodeError(err, syscall, pathStr));
|
|
436
459
|
}
|
|
437
460
|
});
|
|
438
461
|
});
|
|
@@ -463,14 +486,16 @@ async function realpath(path: PathLike): Promise<string> {
|
|
|
463
486
|
}
|
|
464
487
|
|
|
465
488
|
async function symlink(target: PathLike, path: PathLike, _type?: string): Promise<void> {
|
|
489
|
+
const pathStr = normalizePath(path);
|
|
490
|
+
const targetStr = normalizePath(target);
|
|
466
491
|
return new Promise((resolve, reject) => {
|
|
467
|
-
const file = Gio.File.new_for_path(
|
|
468
|
-
file.make_symbolic_link_async(
|
|
492
|
+
const file = Gio.File.new_for_path(pathStr);
|
|
493
|
+
file.make_symbolic_link_async(targetStr, GLib.PRIORITY_DEFAULT, null, (_s: unknown, res: Gio.AsyncResult) => {
|
|
469
494
|
try {
|
|
470
495
|
file.make_symbolic_link_finish(res);
|
|
471
496
|
resolve();
|
|
472
497
|
} catch (err: unknown) {
|
|
473
|
-
reject(createNodeError(err, 'symlink',
|
|
498
|
+
reject(createNodeError(err, 'symlink', targetStr, pathStr));
|
|
474
499
|
}
|
|
475
500
|
});
|
|
476
501
|
});
|
|
@@ -482,7 +507,7 @@ async function symlink(target: PathLike, path: PathLike, _type?: string): Promis
|
|
|
482
507
|
* @return Fulfills with `undefined` upon success.
|
|
483
508
|
*/
|
|
484
509
|
async function rm(path: PathLike, options?: RmOptions): Promise<void> {
|
|
485
|
-
const pathStr = path
|
|
510
|
+
const pathStr = normalizePath(path);
|
|
486
511
|
const file = Gio.File.new_for_path(pathStr);
|
|
487
512
|
const recursive = options?.recursive || false;
|
|
488
513
|
const force = options?.force || false;
|
|
@@ -568,6 +593,8 @@ async function link(existingPath: PathLike, newPath: PathLike): Promise<void> {
|
|
|
568
593
|
linkSync(existingPath, newPath);
|
|
569
594
|
}
|
|
570
595
|
|
|
596
|
+
export type { Dir };
|
|
597
|
+
|
|
571
598
|
export {
|
|
572
599
|
readFile,
|
|
573
600
|
mkdir,
|
|
@@ -592,6 +619,25 @@ export {
|
|
|
592
619
|
chmod,
|
|
593
620
|
chown,
|
|
594
621
|
link,
|
|
622
|
+
cpAsync as cp,
|
|
623
|
+
opendirAsync as opendir,
|
|
624
|
+
globAsync as glob,
|
|
625
|
+
watchAsync as watch,
|
|
626
|
+
statfsAsync as statfs,
|
|
627
|
+
utimesAsync as utimes,
|
|
628
|
+
lutimesAsync as lutimes,
|
|
629
|
+
lchownAsync as lchown,
|
|
630
|
+
lchmodAsync as lchmod,
|
|
631
|
+
fstatAsync as fstat,
|
|
632
|
+
ftruncateAsync as ftruncate,
|
|
633
|
+
fdatasyncAsync as fdatasync,
|
|
634
|
+
fsyncAsync as fsync,
|
|
635
|
+
fchmodAsync as fchmod,
|
|
636
|
+
fchownAsync as fchown,
|
|
637
|
+
futimesAsync as futimes,
|
|
638
|
+
readvAsync as readv,
|
|
639
|
+
writevAsync as writev,
|
|
640
|
+
openAsBlob,
|
|
595
641
|
};
|
|
596
642
|
|
|
597
643
|
export default {
|
|
@@ -618,4 +664,23 @@ export default {
|
|
|
618
664
|
chmod,
|
|
619
665
|
chown,
|
|
620
666
|
link,
|
|
667
|
+
cp: cpAsync,
|
|
668
|
+
opendir: opendirAsync,
|
|
669
|
+
glob: globAsync,
|
|
670
|
+
watch: watchAsync,
|
|
671
|
+
statfs: statfsAsync,
|
|
672
|
+
utimes: utimesAsync,
|
|
673
|
+
lutimes: lutimesAsync,
|
|
674
|
+
lchown: lchownAsync,
|
|
675
|
+
lchmod: lchmodAsync,
|
|
676
|
+
fstat: fstatAsync,
|
|
677
|
+
ftruncate: ftruncateAsync,
|
|
678
|
+
fdatasync: fdatasyncAsync,
|
|
679
|
+
fsync: fsyncAsync,
|
|
680
|
+
fchmod: fchmodAsync,
|
|
681
|
+
fchown: fchownAsync,
|
|
682
|
+
futimes: futimesAsync,
|
|
683
|
+
readv: readvAsync,
|
|
684
|
+
writev: writevAsync,
|
|
685
|
+
openAsBlob,
|
|
621
686
|
};
|
package/src/read-stream.ts
CHANGED
|
@@ -6,7 +6,7 @@ import Gio from '@girs/gio-2.0';
|
|
|
6
6
|
import GLib from '@girs/glib-2.0';
|
|
7
7
|
import { Buffer } from "node:buffer";
|
|
8
8
|
import { Readable } from "node:stream";
|
|
9
|
-
import {
|
|
9
|
+
import { normalizePath } from './utils.js';
|
|
10
10
|
|
|
11
11
|
import type { CreateReadStreamOptions } from 'node:fs/promises';
|
|
12
12
|
import type { PathLike, ReadStream as IReadStream } from 'node:fs';
|
|
@@ -19,13 +19,15 @@ export class ReadStream extends Readable implements IReadStream {
|
|
|
19
19
|
|
|
20
20
|
private _gioFile: Gio.File;
|
|
21
21
|
private _inputStream: Gio.FileInputStream | null = null;
|
|
22
|
+
private _cancellable = new Gio.Cancellable();
|
|
22
23
|
private _start: number;
|
|
23
24
|
private _end: number;
|
|
24
25
|
private _pos: number;
|
|
25
26
|
|
|
26
27
|
close(callback?: (err?: NodeJS.ErrnoException | null) => void): void {
|
|
28
|
+
this._cancellable.cancel();
|
|
27
29
|
if (this._inputStream) {
|
|
28
|
-
|
|
30
|
+
this._inputStream.close_async(GLib.PRIORITY_DEFAULT, null, () => {});
|
|
29
31
|
this._inputStream = null;
|
|
30
32
|
}
|
|
31
33
|
this.destroy();
|
|
@@ -33,9 +35,7 @@ export class ReadStream extends Readable implements IReadStream {
|
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
constructor(path: PathLike, opts?: CreateReadStreamOptions) {
|
|
36
|
-
|
|
37
|
-
path = fileURLToPath(path);
|
|
38
|
-
}
|
|
38
|
+
const pathStr = normalizePath(path);
|
|
39
39
|
|
|
40
40
|
super({
|
|
41
41
|
highWaterMark: opts?.highWaterMark ?? 64 * 1024,
|
|
@@ -43,40 +43,41 @@ export class ReadStream extends Readable implements IReadStream {
|
|
|
43
43
|
objectMode: false,
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
-
this.path =
|
|
47
|
-
this._gioFile = Gio.File.new_for_path(
|
|
46
|
+
this.path = pathStr;
|
|
47
|
+
this._gioFile = Gio.File.new_for_path(pathStr);
|
|
48
48
|
this._start = (opts?.start as number) ?? 0;
|
|
49
49
|
this._end = (opts?.end as number) ?? Infinity;
|
|
50
50
|
this._pos = this._start;
|
|
51
|
+
}
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
this.
|
|
53
|
+
// Use _construct() for async file opening so the stream machinery defers
|
|
54
|
+
// _read() until the file is open. This avoids the fragile _pendingReadSize
|
|
55
|
+
// pattern and correctly handles backpressure via the constructed flag.
|
|
56
|
+
override _construct(callback: (err?: Error | null) => void): void {
|
|
57
|
+
this._gioFile.read_async(GLib.PRIORITY_DEFAULT, this._cancellable, (_source, asyncResult) => {
|
|
58
|
+
if (this.destroyed) { callback(); return; }
|
|
59
|
+
try {
|
|
60
|
+
this._inputStream = this._gioFile.read_finish(asyncResult);
|
|
61
|
+
this.pending = false;
|
|
62
|
+
this.emit('open', 0);
|
|
63
|
+
this.emit('ready');
|
|
64
|
+
if (this._start > 0 && this._inputStream!.can_seek()) {
|
|
65
|
+
this._inputStream!.seek(this._start, GLib.SeekType.SET, null);
|
|
66
|
+
}
|
|
67
|
+
callback();
|
|
68
|
+
} catch (err) {
|
|
69
|
+
if (!this._cancellable.is_cancelled()) {
|
|
70
|
+
callback(err as Error);
|
|
65
71
|
}
|
|
66
72
|
}
|
|
67
73
|
});
|
|
68
74
|
}
|
|
69
75
|
|
|
70
76
|
override _read(size: number): void {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (this.destroyed) return;
|
|
74
|
-
// Retry on next tick (constructor's async open hasn't completed yet)
|
|
75
|
-
Promise.resolve().then(() => this._read(size));
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
77
|
+
this._doRead(size);
|
|
78
|
+
}
|
|
78
79
|
|
|
79
|
-
|
|
80
|
+
private _doRead(size: number): void {
|
|
80
81
|
let toRead = size;
|
|
81
82
|
if (this._end !== Infinity) {
|
|
82
83
|
const remaining = this._end - this._pos + 1;
|
|
@@ -87,27 +88,32 @@ export class ReadStream extends Readable implements IReadStream {
|
|
|
87
88
|
toRead = Math.min(size, remaining);
|
|
88
89
|
}
|
|
89
90
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
const stream = this._inputStream!;
|
|
92
|
+
stream.read_bytes_async(toRead, GLib.PRIORITY_DEFAULT, this._cancellable, (_source, asyncResult) => {
|
|
93
|
+
try {
|
|
94
|
+
const gbytes = stream.read_bytes_finish(asyncResult);
|
|
95
|
+
const data = gbytes.get_data();
|
|
93
96
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
97
|
+
if (!data || data.length === 0) {
|
|
98
|
+
this.push(null);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
99
101
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
102
|
+
this.bytesRead += data.length;
|
|
103
|
+
this._pos += data.length;
|
|
104
|
+
this.push(Buffer.from(data));
|
|
105
|
+
} catch (err) {
|
|
106
|
+
if (!this._cancellable.is_cancelled()) {
|
|
107
|
+
this.destroy(err as Error);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
});
|
|
106
111
|
}
|
|
107
112
|
|
|
108
113
|
override _destroy(error: Error | null, callback: (error?: Error | null) => void): void {
|
|
114
|
+
this._cancellable.cancel();
|
|
109
115
|
if (this._inputStream) {
|
|
110
|
-
|
|
116
|
+
this._inputStream.close_async(GLib.PRIORITY_DEFAULT, null, () => {});
|
|
111
117
|
this._inputStream = null;
|
|
112
118
|
}
|
|
113
119
|
callback(error);
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// Reference: Node.js lib/internal/fs/watchers.js (StatWatcher)
|
|
2
|
+
// Reimplemented for GJS using setInterval polling
|
|
3
|
+
|
|
4
|
+
import { EventEmitter } from 'node:events';
|
|
5
|
+
import { statSync } from './sync.js';
|
|
6
|
+
|
|
7
|
+
import type { PathLike, Stats } from 'node:fs';
|
|
8
|
+
|
|
9
|
+
function zeroedStat(): Stats {
|
|
10
|
+
return {
|
|
11
|
+
dev: 0, ino: 0, mode: 0, nlink: 0, uid: 0, gid: 0, rdev: 0,
|
|
12
|
+
size: 0, blksize: 0, blocks: 0,
|
|
13
|
+
atimeMs: 0, mtimeMs: 0, ctimeMs: 0, birthtimeMs: 0,
|
|
14
|
+
atime: new Date(0), mtime: new Date(0), ctime: new Date(0), birthtime: new Date(0),
|
|
15
|
+
isFile: () => false, isDirectory: () => false, isBlockDevice: () => false,
|
|
16
|
+
isCharacterDevice: () => false, isSymbolicLink: () => false, isFIFO: () => false,
|
|
17
|
+
isSocket: () => false,
|
|
18
|
+
} as unknown as Stats;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class StatWatcher extends EventEmitter {
|
|
22
|
+
private _path: string;
|
|
23
|
+
private _interval: number;
|
|
24
|
+
private _timerId: ReturnType<typeof setInterval> | null = null;
|
|
25
|
+
private _prev: Stats;
|
|
26
|
+
private _changeCount = 0;
|
|
27
|
+
|
|
28
|
+
constructor(path: string, interval: number) {
|
|
29
|
+
super();
|
|
30
|
+
this._path = path;
|
|
31
|
+
this._interval = interval;
|
|
32
|
+
this._prev = zeroedStat();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
start(): void {
|
|
36
|
+
try { this._prev = statSync(this._path) as unknown as Stats; } catch {}
|
|
37
|
+
this._timerId = setInterval(() => {
|
|
38
|
+
let curr: Stats;
|
|
39
|
+
try { curr = statSync(this._path) as unknown as Stats; } catch { curr = zeroedStat(); }
|
|
40
|
+
const prev = this._prev;
|
|
41
|
+
if (curr.mtimeMs !== prev.mtimeMs || curr.size !== prev.size || curr.ino !== prev.ino) {
|
|
42
|
+
this._prev = curr;
|
|
43
|
+
this.emit('change', curr, prev);
|
|
44
|
+
}
|
|
45
|
+
}, this._interval);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
stop(): void {
|
|
49
|
+
if (this._timerId !== null) {
|
|
50
|
+
clearInterval(this._timerId);
|
|
51
|
+
this._timerId = null;
|
|
52
|
+
}
|
|
53
|
+
this.emit('stop');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
addChangeListener(listener: (curr: Stats, prev: Stats) => void): void {
|
|
57
|
+
this._changeCount++;
|
|
58
|
+
this.on('change', listener);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
removeChangeListener(listener: (curr: Stats, prev: Stats) => void): void {
|
|
62
|
+
this._changeCount--;
|
|
63
|
+
this.removeListener('change', listener);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
removeAllChangeListeners(): void {
|
|
67
|
+
this._changeCount = 0;
|
|
68
|
+
this.removeAllListeners('change');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
get changeListenerCount(): number {
|
|
72
|
+
return this._changeCount;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const statWatchers = new Map<string, StatWatcher>();
|
|
77
|
+
|
|
78
|
+
export function watchFile(
|
|
79
|
+
filename: PathLike,
|
|
80
|
+
options: { persistent?: boolean; interval?: number } | ((curr: Stats, prev: Stats) => void),
|
|
81
|
+
listener?: (curr: Stats, prev: Stats) => void,
|
|
82
|
+
): StatWatcher {
|
|
83
|
+
if (typeof options === 'function') {
|
|
84
|
+
listener = options;
|
|
85
|
+
options = {};
|
|
86
|
+
}
|
|
87
|
+
const interval = (options as { interval?: number }).interval ?? 5007;
|
|
88
|
+
const resolved = filename.toString();
|
|
89
|
+
|
|
90
|
+
let watcher = statWatchers.get(resolved);
|
|
91
|
+
if (!watcher) {
|
|
92
|
+
watcher = new StatWatcher(resolved, interval);
|
|
93
|
+
watcher.start();
|
|
94
|
+
statWatchers.set(resolved, watcher);
|
|
95
|
+
}
|
|
96
|
+
if (listener) watcher.addChangeListener(listener);
|
|
97
|
+
return watcher;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function unwatchFile(
|
|
101
|
+
filename: PathLike,
|
|
102
|
+
listener?: (curr: Stats, prev: Stats) => void,
|
|
103
|
+
): void {
|
|
104
|
+
const resolved = filename.toString();
|
|
105
|
+
const watcher = statWatchers.get(resolved);
|
|
106
|
+
if (!watcher) return;
|
|
107
|
+
if (listener) {
|
|
108
|
+
watcher.removeChangeListener(listener);
|
|
109
|
+
} else {
|
|
110
|
+
watcher.removeAllChangeListeners();
|
|
111
|
+
}
|
|
112
|
+
if (watcher.changeListenerCount === 0) {
|
|
113
|
+
watcher.stop();
|
|
114
|
+
statWatchers.delete(resolved);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Ported from refs/node-test/parallel/test-fs-statfs.js (behavior)
|
|
2
|
+
// Original: MIT, Node.js contributors.
|
|
3
|
+
// Rewritten for @gjsify/unit — behavior preserved, assertion dialect adapted.
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from '@gjsify/unit';
|
|
6
|
+
import { statfsSync, statfs, promises } from 'node:fs';
|
|
7
|
+
import { tmpdir } from 'node:os';
|
|
8
|
+
|
|
9
|
+
const TMP = tmpdir();
|
|
10
|
+
|
|
11
|
+
export default async () => {
|
|
12
|
+
await describe('fs.statfs / fs.promises.statfs', async () => {
|
|
13
|
+
await it('statfsSync returns object with expected shape', async () => {
|
|
14
|
+
const result = statfsSync(TMP);
|
|
15
|
+
expect(typeof result.type).toBe('number');
|
|
16
|
+
expect(typeof result.bsize).toBe('number');
|
|
17
|
+
expect(typeof result.blocks).toBe('number');
|
|
18
|
+
expect(typeof result.bfree).toBe('number');
|
|
19
|
+
expect(typeof result.bavail).toBe('number');
|
|
20
|
+
expect(typeof result.files).toBe('number');
|
|
21
|
+
expect(typeof result.ffree).toBe('number');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
await it('statfsSync returns plausible values', async () => {
|
|
25
|
+
const result = statfsSync(TMP);
|
|
26
|
+
expect(result.bsize).toBe(4096);
|
|
27
|
+
expect(result.blocks).toBeGreaterThan(0);
|
|
28
|
+
expect(result.bfree).toBeGreaterThanOrEqual(0);
|
|
29
|
+
expect(result.bavail).toBeGreaterThanOrEqual(0);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
await it('statfs callback form returns same shape', async () => {
|
|
33
|
+
const result = await new Promise<any>((resolve, reject) => {
|
|
34
|
+
statfs(TMP, (err, stats) => {
|
|
35
|
+
if (err) reject(err);
|
|
36
|
+
else resolve(stats);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
expect(typeof result.type).toBe('number');
|
|
40
|
+
expect(typeof result.bsize).toBe('number');
|
|
41
|
+
expect(result.bsize).toBe(4096);
|
|
42
|
+
expect(result.blocks).toBeGreaterThan(0);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
await it('promises.statfs returns same shape', async () => {
|
|
46
|
+
const result = await promises.statfs(TMP);
|
|
47
|
+
expect(typeof result.type).toBe('number');
|
|
48
|
+
expect(typeof result.bsize).toBe('number');
|
|
49
|
+
expect(result.bsize).toBe(4096);
|
|
50
|
+
expect(result.blocks).toBeGreaterThan(0);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
await it('statfsSync with bigint:true returns bigint fields', async () => {
|
|
54
|
+
const result = statfsSync(TMP, { bigint: true });
|
|
55
|
+
expect(typeof result.type).toBe('bigint');
|
|
56
|
+
expect(typeof result.bsize).toBe('bigint');
|
|
57
|
+
expect(typeof result.blocks).toBe('bigint');
|
|
58
|
+
expect(typeof result.bfree).toBe('bigint');
|
|
59
|
+
expect(result.bsize).toBe(4096n);
|
|
60
|
+
expect(result.blocks > 0n).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
await it('statfsSync throws on non-existent path', async () => {
|
|
64
|
+
expect(() => statfsSync('/nonexistent-gjsify-test-path-xyz')).toThrow();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
};
|