11using System ;
22using System . Diagnostics ;
33using System . IO ;
4+ using System . Linq ;
45using System . Threading . Tasks ;
56
67namespace Microsoft . AspNetCore . NodeServices . HostingModels
@@ -18,17 +19,24 @@ namespace Microsoft.AspNetCore.NodeServices.HostingModels
1819 public abstract class OutOfProcessNodeInstance : INodeInstance
1920 {
2021 private const string ConnectionEstablishedMessage = "[Microsoft.AspNetCore.NodeServices:Listening]" ;
21- private const string NeedsRestartMessage = "[Microsoft.AspNetCore.NodeServices:Restart]" ;
2222 private readonly TaskCompletionSource < object > _connectionIsReadySource = new TaskCompletionSource < object > ( ) ;
2323 private bool _disposed ;
2424 private readonly StringAsTempFile _entryPointScript ;
25+ private FileSystemWatcher _fileSystemWatcher ;
2526 private readonly Process _nodeProcess ;
2627 private bool _nodeProcessNeedsRestart ;
28+ private readonly string [ ] _watchFileExtensions ;
2729
28- public OutOfProcessNodeInstance ( string entryPointScript , string projectPath , string commandLineArguments = null )
30+ public OutOfProcessNodeInstance (
31+ string entryPointScript ,
32+ string projectPath ,
33+ string [ ] watchFileExtensions ,
34+ string commandLineArguments )
2935 {
3036 _entryPointScript = new StringAsTempFile ( entryPointScript ) ;
3137 _nodeProcess = LaunchNodeProcess ( _entryPointScript . FileName , projectPath , commandLineArguments ) ;
38+ _watchFileExtensions = watchFileExtensions ;
39+ _fileSystemWatcher = BeginFileWatcher ( projectPath ) ;
3240 ConnectToInputOutputStreams ( ) ;
3341 }
3442
@@ -80,6 +88,7 @@ protected virtual void Dispose(bool disposing)
8088 if ( disposing )
8189 {
8290 _entryPointScript . Dispose ( ) ;
91+ EnsureFileSystemWatcherIsDisposed ( ) ;
8392 }
8493
8594 // Make sure the Node process is finished
@@ -93,6 +102,15 @@ protected virtual void Dispose(bool disposing)
93102 }
94103 }
95104
105+ private void EnsureFileSystemWatcherIsDisposed ( )
106+ {
107+ if ( _fileSystemWatcher != null )
108+ {
109+ _fileSystemWatcher . Dispose ( ) ;
110+ _fileSystemWatcher = null ;
111+ }
112+ }
113+
96114 private static Process LaunchNodeProcess ( string entryPointFilename , string projectPath , string commandLineArguments )
97115 {
98116 var startInfo = new ProcessStartInfo ( "node" )
@@ -143,13 +161,6 @@ private void ConnectToInputOutputStreams()
143161 _connectionIsReadySource . SetResult ( null ) ;
144162 initializationIsCompleted = true ;
145163 }
146- else if ( evt . Data == NeedsRestartMessage )
147- {
148- // Temporarily, the file-watching logic is in Node, so look out for the
149- // signal that we need to restart. This can be removed once the file-watching
150- // logic is moved over to the .NET side.
151- _nodeProcessNeedsRestart = true ;
152- }
153164 else if ( evt . Data != null )
154165 {
155166 OnOutputDataReceived ( evt . Data ) ;
@@ -177,6 +188,69 @@ private void ConnectToInputOutputStreams()
177188 _nodeProcess . BeginErrorReadLine ( ) ;
178189 }
179190
191+ private FileSystemWatcher BeginFileWatcher ( string rootDir )
192+ {
193+ if ( _watchFileExtensions == null || _watchFileExtensions . Length == 0 )
194+ {
195+ // Nothing to watch
196+ return null ;
197+ }
198+
199+ var watcher = new FileSystemWatcher ( rootDir )
200+ {
201+ IncludeSubdirectories = true ,
202+ NotifyFilter = NotifyFilters . LastWrite | NotifyFilters . FileName | NotifyFilters . DirectoryName
203+ } ;
204+ watcher . Changed += OnFileChanged ;
205+ watcher . Created += OnFileChanged ;
206+ watcher . Deleted += OnFileChanged ;
207+ watcher . Renamed += OnFileRenamed ;
208+ watcher . EnableRaisingEvents = true ;
209+ return watcher ;
210+ }
211+
212+ private void OnFileChanged ( object source , FileSystemEventArgs e )
213+ {
214+ if ( IsFilenameBeingWatched ( e . FullPath ) )
215+ {
216+ RestartDueToFileChange ( e . FullPath ) ;
217+ }
218+ }
219+
220+ private void OnFileRenamed ( object source , RenamedEventArgs e )
221+ {
222+ if ( IsFilenameBeingWatched ( e . OldFullPath ) || IsFilenameBeingWatched ( e . FullPath ) )
223+ {
224+ RestartDueToFileChange ( e . OldFullPath ) ;
225+ }
226+ }
227+
228+ private bool IsFilenameBeingWatched ( string fullPath )
229+ {
230+ if ( string . IsNullOrEmpty ( fullPath ) )
231+ {
232+ return false ;
233+ }
234+ else
235+ {
236+ var actualExtension = Path . GetExtension ( fullPath ) ?? string . Empty ;
237+ return _watchFileExtensions . Any ( actualExtension . Equals ) ;
238+ }
239+ }
240+
241+ private void RestartDueToFileChange ( string fullPath )
242+ {
243+ // TODO: Use proper logger
244+ Console . WriteLine ( $ "Node will restart because file changed: { fullPath } ") ;
245+
246+ _nodeProcessNeedsRestart = true ;
247+
248+ // There's no need to watch for any more changes, since we're already restarting, and if the
249+ // restart takes some time (e.g., due to connection draining), we could end up getting duplicate
250+ // notifications.
251+ EnsureFileSystemWatcherIsDisposed ( ) ;
252+ }
253+
180254 ~ OutOfProcessNodeInstance ( )
181255 {
182256 Dispose ( false ) ;
0 commit comments