1
1
'use strict' ;
2
2
3
3
const {
4
- ArrayPrototypePush,
5
- SafePromiseAllReturnVoid,
6
4
Promise,
7
- PromisePrototypeThen,
8
5
SafeMap,
9
6
SafeSet,
10
7
StringPrototypeStartsWith,
@@ -31,47 +28,19 @@ const {
31
28
} = require ( 'path' ) ;
32
29
33
30
let internalSync ;
34
- let internalPromises ;
35
-
36
- function lazyLoadFsPromises ( ) {
37
- internalPromises ??= require ( 'fs/promises' ) ;
38
- return internalPromises ;
39
- }
40
31
41
32
function lazyLoadFsSync ( ) {
42
33
internalSync ??= require ( 'fs' ) ;
43
34
return internalSync ;
44
35
}
45
- let kResistStopPropagation ;
46
-
47
- async function traverse ( dir , files = new SafeMap ( ) , symbolicLinks = new SafeSet ( ) ) {
48
- const { opendir } = lazyLoadFsPromises ( ) ;
49
-
50
- const filenames = await opendir ( dir ) ;
51
- const subdirectories = [ ] ;
52
-
53
- for await ( const file of filenames ) {
54
- const f = pathJoin ( dir , file . name ) ;
55
-
56
- files . set ( f , file ) ;
57
-
58
- // Do not follow symbolic links
59
- if ( file . isSymbolicLink ( ) ) {
60
- symbolicLinks . add ( f ) ;
61
- } else if ( file . isDirectory ( ) ) {
62
- ArrayPrototypePush ( subdirectories , traverse ( f , files ) ) ;
63
- }
64
- }
65
-
66
- await SafePromiseAllReturnVoid ( subdirectories ) ;
67
36
68
- return files ;
69
- }
37
+ let kResistStopPropagation ;
70
38
71
39
class FSWatcher extends EventEmitter {
72
40
#options = null ;
73
41
#closed = false ;
74
42
#files = new SafeMap ( ) ;
43
+ #watchers = new SafeMap ( ) ;
75
44
#symbolicFiles = new SafeSet ( ) ;
76
45
#rootPath = pathResolve ( ) ;
77
46
#watchingFile = false ;
@@ -111,11 +80,11 @@ class FSWatcher extends EventEmitter {
111
80
return ;
112
81
}
113
82
114
- const { unwatchFile } = lazyLoadFsSync ( ) ;
115
83
this . #closed = true ;
116
84
117
85
for ( const file of this . #files. keys ( ) ) {
118
- unwatchFile ( file ) ;
86
+ this . #watchers. get ( file ) . close ( ) ;
87
+ this . #watchers. delete ( file ) ;
119
88
}
120
89
121
90
this . #files. clear ( ) ;
@@ -124,24 +93,26 @@ class FSWatcher extends EventEmitter {
124
93
}
125
94
126
95
#unwatchFiles( file ) {
127
- const { unwatchFile } = lazyLoadFsSync ( ) ;
128
-
129
96
this . #symbolicFiles. delete ( file ) ;
130
97
131
98
for ( const filename of this . #files. keys ( ) ) {
132
99
if ( StringPrototypeStartsWith ( filename , file ) ) {
133
- unwatchFile ( filename ) ;
100
+ this . #files. delete ( filename ) ;
101
+ this . #watchers. get ( filename ) . close ( ) ;
102
+ this . #watchers. delete ( filename ) ;
134
103
}
135
104
}
136
105
}
137
106
138
- async #watchFolder( folder ) {
139
- const { opendir } = lazyLoadFsPromises ( ) ;
107
+ #watchFolder( folder ) {
108
+ const { readdirSync } = lazyLoadFsSync ( ) ;
140
109
141
110
try {
142
- const files = await opendir ( folder ) ;
111
+ const files = readdirSync ( folder , {
112
+ withFileTypes : true ,
113
+ } ) ;
143
114
144
- for await ( const file of files ) {
115
+ for ( const file of files ) {
145
116
if ( this . #closed) {
146
117
break ;
147
118
}
@@ -155,11 +126,9 @@ class FSWatcher extends EventEmitter {
155
126
this . #symbolicFiles. add ( f ) ;
156
127
}
157
128
158
- this . #files. set ( f , file ) ;
159
- if ( file . isFile ( ) ) {
160
- this . #watchFile( f ) ;
161
- } else if ( file . isDirectory ( ) && ! file . isSymbolicLink ( ) ) {
162
- await this . #watchFolder( f ) ;
129
+ this . #watchFile( f ) ;
130
+ if ( file . isDirectory ( ) && ! file . isSymbolicLink ( ) ) {
131
+ this . #watchFolder( f ) ;
163
132
}
164
133
}
165
134
}
@@ -173,22 +142,30 @@ class FSWatcher extends EventEmitter {
173
142
return ;
174
143
}
175
144
176
- const { watchFile } = lazyLoadFsSync ( ) ;
177
- const existingStat = this . #files. get ( file ) ;
145
+ const { watch, statSync } = lazyLoadFsSync ( ) ;
146
+
147
+ if ( this . #files. has ( file ) ) {
148
+ return ;
149
+ }
150
+
151
+ {
152
+ const existingStat = statSync ( file ) ;
153
+ this . #files. set ( file , existingStat ) ;
154
+ }
178
155
179
- watchFile ( file , {
156
+ const watcher = watch ( file , {
180
157
persistent : this . #options. persistent ,
181
- } , ( currentStats , previousStats ) => {
182
- if ( existingStat && ! existingStat . isDirectory ( ) &&
183
- currentStats . nlink !== 0 && existingStat . mtimeMs === currentStats . mtimeMs ) {
184
- return ;
185
- }
158
+ } , ( eventType , filename ) => {
159
+ const existingStat = this . #files. get ( file ) ;
160
+ const currentStats = statSync ( file ) ;
186
161
187
162
this . #files. set ( file , currentStats ) ;
188
163
189
- if ( currentStats . birthtimeMs === 0 && previousStats . birthtimeMs !== 0 ) {
164
+ if ( currentStats . birthtimeMs === 0 && existingStat . birthtimeMs !== 0 ) {
190
165
// The file is now deleted
191
166
this . #files. delete ( file ) ;
167
+ this . #watchers. delete ( file ) ;
168
+ watcher . close ( ) ;
192
169
this . emit ( 'change' , 'rename' , pathRelative ( this . #rootPath, file ) ) ;
193
170
this . #unwatchFiles( file ) ;
194
171
} else if ( file === this . #rootPath && this . #watchingFile) {
@@ -205,6 +182,7 @@ class FSWatcher extends EventEmitter {
205
182
this . emit ( 'change' , 'change' , pathRelative ( this . #rootPath, file ) ) ;
206
183
}
207
184
} ) ;
185
+ this . #watchers. set ( file , watcher ) ;
208
186
}
209
187
210
188
[ kFSWatchStart ] ( filename ) {
@@ -217,19 +195,9 @@ class FSWatcher extends EventEmitter {
217
195
this . #closed = false ;
218
196
this . #watchingFile = file . isFile ( ) ;
219
197
198
+ this . #watchFile( filename ) ;
220
199
if ( file . isDirectory ( ) ) {
221
- this . #files. set ( filename , file ) ;
222
-
223
- PromisePrototypeThen (
224
- traverse ( filename , this . #files, this . #symbolicFiles) ,
225
- ( ) => {
226
- for ( const f of this . #files. keys ( ) ) {
227
- this . #watchFile( f ) ;
228
- }
229
- } ,
230
- ) ;
231
- } else {
232
- this . #watchFile( filename ) ;
200
+ this . #watchFolder( filename ) ;
233
201
}
234
202
} catch ( error ) {
235
203
if ( error . code === 'ENOENT' ) {
@@ -264,7 +232,10 @@ class FSWatcher extends EventEmitter {
264
232
resolve ( { __proto__ : null , value : { eventType, filename } } ) ;
265
233
} ) ;
266
234
} : ( resolve , reject ) => {
267
- const onAbort = ( ) => reject ( new AbortError ( undefined , { cause : signal . reason } ) ) ;
235
+ const onAbort = ( ) => {
236
+ this . close ( ) ;
237
+ reject ( new AbortError ( undefined , { cause : signal . reason } ) ) ;
238
+ } ;
268
239
if ( signal . aborted ) return onAbort ( ) ;
269
240
kResistStopPropagation ??= require ( 'internal/event_target' ) . kResistStopPropagation ;
270
241
signal . addEventListener ( 'abort' , onAbort , { __proto__ : null , once : true , [ kResistStopPropagation ] : true } ) ;
@@ -277,6 +248,10 @@ class FSWatcher extends EventEmitter {
277
248
next : ( ) => ( this . #closed ?
278
249
{ __proto__ : null , done : true } :
279
250
new Promise ( promiseExecutor ) ) ,
251
+ return : ( ) => {
252
+ this . close ( ) ;
253
+ return { __proto__ : null , done : true } ;
254
+ } ,
280
255
[ SymbolAsyncIterator ] ( ) { return this ; } ,
281
256
} ;
282
257
}
0 commit comments