|
3 | 3 | const { |
4 | 4 | ObjectDefineProperty, |
5 | 5 | ObjectSetPrototypeOf, |
| 6 | + Promise, |
6 | 7 | Symbol, |
7 | 8 | } = primordials; |
8 | 9 |
|
9 | | -const errors = require('internal/errors'); |
| 10 | +const { |
| 11 | + AbortError, |
| 12 | + uvException, |
| 13 | + codes: { |
| 14 | + ERR_INVALID_ARG_VALUE, |
| 15 | + }, |
| 16 | +} = require('internal/errors'); |
| 17 | + |
10 | 18 | const { |
11 | 19 | kFsStatsFieldsNumber, |
12 | 20 | StatWatcher: _StatWatcher |
13 | 21 | } = internalBinding('fs'); |
| 22 | + |
14 | 23 | const { FSEvent } = internalBinding('fs_event_wrap'); |
15 | 24 | const { UV_ENOSPC } = internalBinding('uv'); |
16 | 25 | const { EventEmitter } = require('events'); |
| 26 | + |
17 | 27 | const { |
18 | 28 | getStatsFromBinding, |
19 | 29 | getValidatedPath |
20 | 30 | } = require('internal/fs/utils'); |
| 31 | + |
21 | 32 | const { |
22 | 33 | defaultTriggerAsyncIdScope, |
23 | 34 | symbols: { owner_symbol } |
24 | 35 | } = require('internal/async_hooks'); |
| 36 | + |
25 | 37 | const { toNamespacedPath } = require('path'); |
26 | | -const { validateUint32 } = require('internal/validators'); |
| 38 | + |
| 39 | +const { |
| 40 | + validateAbortSignal, |
| 41 | + validateBoolean, |
| 42 | + validateObject, |
| 43 | + validateUint32, |
| 44 | +} = require('internal/validators'); |
| 45 | + |
| 46 | +const { |
| 47 | + Buffer: { |
| 48 | + isEncoding, |
| 49 | + }, |
| 50 | +} = require('buffer'); |
| 51 | + |
27 | 52 | const assert = require('internal/assert'); |
28 | 53 |
|
29 | 54 | const kOldStatus = Symbol('kOldStatus'); |
@@ -90,7 +115,7 @@ StatWatcher.prototype[kFSStatWatcherStart] = function(filename, |
90 | 115 | validateUint32(interval, 'interval'); |
91 | 116 | const err = this._handle.start(toNamespacedPath(filename), interval); |
92 | 117 | if (err) { |
93 | | - const error = errors.uvException({ |
| 118 | + const error = uvException({ |
94 | 119 | errno: err, |
95 | 120 | syscall: 'watch', |
96 | 121 | path: filename |
@@ -175,7 +200,7 @@ function FSWatcher() { |
175 | 200 | this._handle.close(); |
176 | 201 | this._handle = null; // Make the handle garbage collectable. |
177 | 202 | } |
178 | | - const error = errors.uvException({ |
| 203 | + const error = uvException({ |
179 | 204 | errno: status, |
180 | 205 | syscall: 'watch', |
181 | 206 | path: filename |
@@ -215,7 +240,7 @@ FSWatcher.prototype[kFSWatchStart] = function(filename, |
215 | 240 | recursive, |
216 | 241 | encoding); |
217 | 242 | if (err) { |
218 | | - const error = errors.uvException({ |
| 243 | + const error = uvException({ |
219 | 244 | errno: err, |
220 | 245 | syscall: 'watch', |
221 | 246 | path: filename, |
@@ -269,10 +294,94 @@ ObjectDefineProperty(FSEvent.prototype, 'owner', { |
269 | 294 | set(v) { return this[owner_symbol] = v; } |
270 | 295 | }); |
271 | 296 |
|
| 297 | +async function* watch(filename, options = {}) { |
| 298 | + const path = toNamespacedPath(getValidatedPath(filename)); |
| 299 | + validateObject(options, 'options'); |
| 300 | + |
| 301 | + const { |
| 302 | + persistent = true, |
| 303 | + recursive = false, |
| 304 | + encoding = 'utf8', |
| 305 | + signal, |
| 306 | + } = options; |
| 307 | + |
| 308 | + validateBoolean(persistent, 'options.persistent'); |
| 309 | + validateBoolean(recursive, 'options.recursive'); |
| 310 | + validateAbortSignal(signal, 'options.signal'); |
| 311 | + |
| 312 | + if (encoding && !isEncoding(encoding)) { |
| 313 | + const reason = 'is invalid encoding'; |
| 314 | + throw new ERR_INVALID_ARG_VALUE(encoding, 'encoding', reason); |
| 315 | + } |
| 316 | + |
| 317 | + if (signal?.aborted) |
| 318 | + throw new AbortError(); |
| 319 | + |
| 320 | + const handle = new FSEvent(); |
| 321 | + let res; |
| 322 | + let rej; |
| 323 | + const oncancel = () => { |
| 324 | + handle.close(); |
| 325 | + rej(new AbortError()); |
| 326 | + }; |
| 327 | + |
| 328 | + try { |
| 329 | + signal?.addEventListener('abort', oncancel, { once: true }); |
| 330 | + |
| 331 | + let promise = new Promise((resolve, reject) => { |
| 332 | + res = resolve; |
| 333 | + rej = reject; |
| 334 | + }); |
| 335 | + |
| 336 | + handle.onchange = (status, eventType, filename) => { |
| 337 | + if (status < 0) { |
| 338 | + const error = uvException({ |
| 339 | + errno: status, |
| 340 | + syscall: 'watch', |
| 341 | + path: filename |
| 342 | + }); |
| 343 | + error.filename = filename; |
| 344 | + handle.close(); |
| 345 | + rej(error); |
| 346 | + return; |
| 347 | + } |
| 348 | + |
| 349 | + res({ eventType, filename }); |
| 350 | + }; |
| 351 | + |
| 352 | + const err = handle.start(path, persistent, recursive, encoding); |
| 353 | + if (err) { |
| 354 | + const error = uvException({ |
| 355 | + errno: err, |
| 356 | + syscall: 'watch', |
| 357 | + path: filename, |
| 358 | + message: err === UV_ENOSPC ? |
| 359 | + 'System limit for number of file watchers reached' : '' |
| 360 | + }); |
| 361 | + error.filename = filename; |
| 362 | + handle.close(); |
| 363 | + throw error; |
| 364 | + } |
| 365 | + |
| 366 | + while (!signal?.aborted) { |
| 367 | + yield await promise; |
| 368 | + promise = new Promise((resolve, reject) => { |
| 369 | + res = resolve; |
| 370 | + rej = reject; |
| 371 | + }); |
| 372 | + } |
| 373 | + throw new AbortError(); |
| 374 | + } finally { |
| 375 | + handle.close(); |
| 376 | + signal?.removeEventListener('abort', oncancel); |
| 377 | + } |
| 378 | +} |
| 379 | + |
272 | 380 | module.exports = { |
273 | 381 | FSWatcher, |
274 | 382 | StatWatcher, |
275 | 383 | kFSWatchStart, |
276 | 384 | kFSStatWatcherStart, |
277 | 385 | kFSStatWatcherAddOrCleanRef, |
| 386 | + watch, |
278 | 387 | }; |
0 commit comments