Skip to content

Commit 92b02af

Browse files
authored
Merge pull request #1903 from paulvanbrenk/fileWatcherUpdate
File watcher update
2 parents c7fc560 + 86fab03 commit 92b02af

File tree

9 files changed

+140
-142
lines changed

9 files changed

+140
-142
lines changed

Nodejs/Product/Nodejs/Nodejs.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@
8484
<Reference Include="envdte80, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
8585
<EmbedInteropTypes>False</EmbedInteropTypes>
8686
</Reference>
87+
<Reference Include="Microsoft.VisualStudio.Shell.Interop.15.7.DesignTime, Version=15.7.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
88+
<EmbedInteropTypes>True</EmbedInteropTypes>
89+
</Reference>
8790
<Reference Include="microsoft.visualstudio.textmanager.interop.11.0, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
8891
<EmbedInteropTypes>True</EmbedInteropTypes>
8992
</Reference>

Nodejs/Product/Nodejs/SharedProject/AssemblyReferenceNode.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ protected virtual void OnAssemblyReferenceChangedOnDisk(object sender, FileChang
400400
}
401401

402402
// We only care about file deletes, so check for one before enumerating references.
403-
if ((e.FileChangeFlag & _VSFILECHANGEFLAGS.VSFILECHG_Del) == 0)
403+
if (e.FileChange != WatcherChangeTypes.Deleted)
404404
{
405405
return;
406406
}

Nodejs/Product/Nodejs/SharedProject/CommonProjectNode.cs

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public CommonProjectNode(IServiceProvider serviceProvider, ImageList imageList)
8080
this.idleManager.OnIdle += this.OnIdle;
8181

8282
this.fileWatcher = new FileChangeManager(serviceProvider);
83-
this.fileWatcher.FolderChangedOnDisk += this.FolderChangedOnDisk;
83+
this.fileWatcher.FileChangedOnDisk += this.FileChangedOnDisk;
8484
}
8585

8686
public override int QueryService(ref Guid guidService, out object result)
@@ -462,7 +462,7 @@ protected override void Dispose(bool disposing)
462462
this.idleManager.OnIdle -= this.OnIdle;
463463
this.idleManager.Dispose();
464464

465-
this.fileWatcher.FolderChangedOnDisk -= this.FolderChangedOnDisk;
465+
this.fileWatcher.FileChangedOnDisk -= this.FileChangedOnDisk;
466466
this.fileWatcher.Dispose();
467467
}
468468

@@ -728,17 +728,10 @@ protected override ReferenceContainerNode CreateReferenceContainerNode()
728728
return new CommonReferenceContainerNode(this);
729729
}
730730

731-
private void FolderChangedOnDisk(object sender, FolderChangedEventArgs e)
731+
private void FileChangedOnDisk(object sender, FileChangedOnDiskEventArgs e)
732732
{
733733
var file = e.FileName;
734-
if (File.Exists(file) || Directory.Exists(file))
735-
{
736-
this.QueueFileSystemChanges(new FileSystemChange(this, WatcherChangeTypes.Changed, file));
737-
}
738-
else
739-
{
740-
this.QueueFileSystemChanges(new FileSystemChange(this, WatcherChangeTypes.Deleted, file));
741-
}
734+
this.QueueFileSystemChanges(new FileSystemChange(this, e.FileChange, file));
742735
}
743736

744737
private void QueueFileSystemChanges(params FileSystemChange[] changes)

Nodejs/Product/Nodejs/SharedProject/FileChangeManager.cs

Lines changed: 41 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,12 @@
99

1010
namespace Microsoft.VisualStudioTools.Project
1111
{
12-
public sealed class FolderChangedEventArgs : EventArgs
13-
{
14-
public readonly string FolderName;
15-
public readonly string FileName;
16-
public _VSFILECHANGEFLAGS FileChangeFlag;
17-
18-
public FolderChangedEventArgs(string folderName, string fileName, _VSFILECHANGEFLAGS fileChangeFlag)
19-
{
20-
this.FolderName = folderName;
21-
this.FileName = fileName;
22-
this.FileChangeFlag = fileChangeFlag;
23-
}
24-
}
25-
2612
/// <summary>
2713
/// This object is in charge of watching for changes to files and folders.
2814
/// </summary>
2915
internal sealed class FileChangeManager
3016
{
31-
private sealed class FileChangeEvents : IVsFreeThreadedFileChangeEvents
17+
private sealed class FileChangeEvents : IVsFreeThreadedFileChangeEvents2
3218
{
3319
private readonly FileChangeManager fileChangeManager;
3420

@@ -56,58 +42,65 @@ public int FilesChanged(uint numberOfFilesChanged, string[] filesChanged, uint[]
5642
throw new ArgumentNullException(nameof(flags));
5743
}
5844

59-
for (var i = 0; i < numberOfFilesChanged; i++)
60-
{
61-
var fullFileName = Utilities.CanonicalizeFileName(filesChanged[i]);
62-
if (this.fileChangeManager.observedFiles.TryGetValue(fullFileName, out var value))
63-
{
64-
var (ItemID, FileChangeCookie) = value;
65-
this.fileChangeManager.FileChangedOnDisk?.Invoke(this, new FileChangedOnDiskEventArgs(fullFileName, ItemID, (_VSFILECHANGEFLAGS)flags[i]));
66-
}
67-
}
45+
Debug.Assert(numberOfFilesChanged == filesChanged.Length && numberOfFilesChanged == flags.Length, "number of files changed doesn't match actual files reported.");
46+
47+
ProcessFileChanges(filesChanged, flags);
6848

6949
return VSConstants.S_OK;
7050
}
7151

72-
/// <summary>
73-
/// Notifies clients of changes made to a directory.
74-
/// </summary>
75-
/// <param name="directory">Name of the directory that had a change.</param>
76-
/// <returns>If the method succeeds, it returns S_OK. If it fails, it returns an error code. </returns>
7752
public int DirectoryChanged(string directory)
7853
{
79-
return VSConstants.S_OK;
54+
// not called since we implement DirectoryChangedEx2
55+
return VSConstants.E_NOTIMPL;
8056
}
8157

8258
public int DirectoryChangedEx(string pszDirectory, string pszFile)
8359
{
84-
if (pszDirectory == null)
60+
// not called since we implement DirectoryChangedEx2
61+
return VSConstants.E_NOTIMPL;
62+
}
63+
64+
/// <summary>
65+
/// Notifies clients of changes made to a directory.
66+
/// </summary>
67+
/// <param name="directory">Name of the directory that had a change.</param>
68+
/// <param name="numberOfFilesChanged">Number of files changed.</param>
69+
/// <param name="filesChanged">Array of file names.</param>
70+
/// <param name="flags">Array of flags indicating the type of changes. See _VSFILECHANGEFLAGS.</param>
71+
public int DirectoryChangedEx2(string directory, uint numberOfFilesChanged, string[] filesChanged, uint[] flags)
72+
{
73+
if (filesChanged == null)
8574
{
86-
throw new ArgumentNullException(nameof(pszDirectory));
75+
throw new ArgumentNullException(nameof(filesChanged));
8776
}
8877

89-
if (pszFile == null)
78+
if (flags == null)
9079
{
91-
throw new ArgumentNullException(nameof(pszFile));
80+
throw new ArgumentNullException(nameof(flags));
9281
}
9382

94-
this.fileChangeManager.FolderChangedOnDisk?.Invoke(this, new FolderChangedEventArgs(pszDirectory, pszFile,
95-
(_VSFILECHANGEFLAGS)0 /* default for now, until VS implements API that returns actual change */));
83+
Debug.Assert(numberOfFilesChanged == filesChanged.Length && numberOfFilesChanged == flags.Length, "number of files changed doesn't match actual files reported.");
84+
85+
ProcessFileChanges(filesChanged, flags);
9686

9787
return VSConstants.S_OK;
9888
}
89+
90+
private void ProcessFileChanges(string[] filesChanged, uint[] flags)
91+
{
92+
for (var i = 0; i < filesChanged.Length; i++)
93+
{
94+
this.fileChangeManager.FileChangedOnDisk?.Invoke(this, new FileChangedOnDiskEventArgs(filesChanged[i], (_VSFILECHANGEFLAGS)flags[i]));
95+
}
96+
}
9997
}
10098

10199
/// <summary>
102100
/// Event that is raised when one of the observed files have changed on disk.
103101
/// </summary>
104102
public event EventHandler<FileChangedOnDiskEventArgs> FileChangedOnDisk;
105103

106-
/// <summary>
107-
/// Event that is raised when one of the observed folders have changed on disk.
108-
/// </summary>
109-
public event EventHandler<FolderChangedEventArgs> FolderChangedOnDisk;
110-
111104
/// <summary>
112105
/// Reference to the FileChange service.
113106
/// </summary>
@@ -117,7 +110,7 @@ public int DirectoryChangedEx(string pszDirectory, string pszFile)
117110
/// Maps between the observed file identified by its filename (in canonicalized form) and the cookie used for subscribing
118111
/// to the events.
119112
/// </summary>
120-
private readonly ConcurrentDictionary<string, (uint ItemID, uint FileChangeCookie)> observedFiles = new ConcurrentDictionary<string, (uint, uint)>();
113+
private readonly ConcurrentDictionary<string, uint> observedFiles = new ConcurrentDictionary<string, uint>();
121114

122115
/// <summary>
123116
/// Maps between the observer folder identified by its foldername (in canonicalized form) and the cookie used for subscribing
@@ -171,9 +164,9 @@ public void Dispose()
171164
this.disposed = true;
172165

173166
// Unsubscribe from the observed source files.
174-
foreach (var (ItemID, FileChangeCookie) in this.observedFiles.Values)
167+
foreach (var fileChangeCookie in this.observedFiles.Values)
175168
{
176-
var hr = this.fileChangeService.UnadviseFileChange(FileChangeCookie);
169+
var hr = this.fileChangeService.UnadviseFileChange(fileChangeCookie);
177170
// don't want to crash VS during cleanup
178171
Debug.Assert(ErrorHandler.Succeeded(hr), "UnadviseFileChange failed");
179172
if (ErrorHandler.Failed(hr)) { break; }
@@ -195,23 +188,12 @@ public void Dispose()
195188
this.observedFolders.Clear();
196189
}
197190

198-
/// <summary>
199-
/// Observe when the given file is updated on disk. In this case we do not care about the item id that represents the file in the hierarchy.
200-
/// </summary>
201-
/// <param name="fileName">File to observe.</param>
202-
public void ObserveFile(string fileName)
203-
{
204-
this.CheckDisposed();
205-
206-
this.ObserveFile(fileName, VSConstants.VSITEMID_NIL);
207-
}
208-
209191
/// <summary>
210192
/// Observe when the given file is updated on disk.
211193
/// </summary>
212194
/// <param name="fileName">File to observe.</param>
213195
/// <param name="id">The item id of the item to observe.</param>
214-
public void ObserveFile(string fileName, uint id)
196+
public void ObserveFile(string fileName)
215197
{
216198
this.CheckDisposed();
217199

@@ -227,7 +209,7 @@ public void ObserveFile(string fileName, uint id)
227209
ErrorHandler.ThrowOnFailure(this.fileChangeService.AdviseFileChange(fullFileName, (uint)(_VSFILECHANGEFLAGS.VSFILECHG_Time | _VSFILECHANGEFLAGS.VSFILECHG_Del), this.fileChangeEvents, out var fileChangeCookie));
228210

229211
// Remember that we're observing this file (used in FilesChanged event handler)
230-
this.observedFiles.TryAdd(fullFileName, (id, fileChangeCookie));
212+
this.observedFiles.TryAdd(fullFileName, fileChangeCookie);
231213
}
232214
}
233215

@@ -291,13 +273,10 @@ public bool StopObservingFile(string fileName)
291273
// Remove the file from our observed list. It's important that this is done before the call to
292274
// UnadviseFileChange, because for some reason, the call to UnadviseFileChange can trigger a
293275
// FilesChanged event, and we want to be able to filter that event away.
294-
if (this.observedFiles.TryRemove(fullFileName, out var value))
276+
if (this.observedFiles.TryRemove(fullFileName, out var fileChangeCookie))
295277
{
296-
// Get the cookie that was used for this.observedItems to this file.
297-
var (ItemID, FileChangeCookie) = value;
298-
299278
// Stop observing the file
300-
return ErrorHandler.Succeeded(this.fileChangeService.UnadviseFileChange(FileChangeCookie));
279+
return ErrorHandler.Succeeded(this.fileChangeService.UnadviseFileChange(fileChangeCookie));
301280
}
302281
return false;
303282
}

Nodejs/Product/Nodejs/SharedProject/StructuresEnums.cs

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
22

33
using System;
4+
using System.Diagnostics;
5+
using System.IO;
46
using System.Runtime.InteropServices;
57
using Microsoft.VisualStudio.Shell.Interop;
68

@@ -296,26 +298,39 @@ public sealed class FileChangedOnDiskEventArgs : EventArgs
296298
/// </summary>
297299
public readonly string FileName;
298300

299-
/// <summary>
300-
/// The item ide of the file that has changed.
301-
/// </summary>
302-
public readonly uint ItemID;
303-
304301
/// <summary>
305302
/// The reason the file has changed on disk.
306303
/// </summary>
307-
public readonly _VSFILECHANGEFLAGS FileChangeFlag;
304+
public readonly WatcherChangeTypes FileChange;
308305

309306
/// <summary>
310307
/// Constructs a new event args.
311308
/// </summary>
312309
/// <param name="fileName">File name that was changed on disk.</param>
313310
/// <param name="id">The item id of the file that was changed on disk.</param>
314-
internal FileChangedOnDiskEventArgs(string fileName, uint id, _VSFILECHANGEFLAGS flag)
311+
internal FileChangedOnDiskEventArgs(string fileName, _VSFILECHANGEFLAGS flag)
315312
{
316313
this.FileName = fileName;
317-
this.ItemID = id;
318-
this.FileChangeFlag = flag;
314+
this.FileChange = ConvertVSFILECHANGEFLAGS((uint)flag);
315+
}
316+
317+
private static WatcherChangeTypes ConvertVSFILECHANGEFLAGS(uint flag)
318+
{
319+
if ((flag & (uint)(_VSFILECHANGEFLAGS.VSFILECHG_Size | _VSFILECHANGEFLAGS.VSFILECHG_Time | _VSFILECHANGEFLAGS.VSFILECHG_Attr)) != 0)
320+
{
321+
return WatcherChangeTypes.Changed;
322+
}
323+
if ((flag & (uint)_VSFILECHANGEFLAGS.VSFILECHG_Add) != 0)
324+
{
325+
return WatcherChangeTypes.Created;
326+
}
327+
if ((flag & (uint)_VSFILECHANGEFLAGS.VSFILECHG_Del) != 0)
328+
{
329+
return WatcherChangeTypes.Deleted;
330+
}
331+
332+
Debug.Fail($"Unexpected value for the file changed event: \'{flag}\'");
333+
return WatcherChangeTypes.Changed;
319334
}
320335
}
321336
}

Nodejs/Product/TestAdapterImpl/TestAdapterImpl.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@
4141
<Reference Include="envdte90, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
4242
<EmbedInteropTypes>False</EmbedInteropTypes>
4343
</Reference>
44+
<Reference Include="Microsoft.VisualStudio.Shell.Interop.15.0.DesignTime, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
45+
<EmbedInteropTypes>True</EmbedInteropTypes>
46+
</Reference>
47+
<Reference Include="Microsoft.VisualStudio.Shell.Interop.15.7.DesignTime, Version=15.7.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
48+
<EmbedInteropTypes>True</EmbedInteropTypes>
49+
</Reference>
4450
<Reference Include="Microsoft.VisualStudio.TestPlatform.ObjectModel">
4551
<HintPath>$(DevEnvDir)CommonExtensions\Microsoft\TestWindow\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll</HintPath>
4652
<Private>False</Private>

Nodejs/Product/TestAdapterImpl/TestContainerDiscoverer.cs

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -360,13 +360,7 @@ public IEnumerable<ITestContainer> GetTestContainers(IVsProject project)
360360
var ft = File.GetLastWriteTimeUtc(filePath);
361361
return (ft > latest) ? ft : latest;
362362
}
363-
catch (UnauthorizedAccessException)
364-
{
365-
}
366-
catch (ArgumentException)
367-
{
368-
}
369-
catch (IOException)
363+
catch (Exception exc) when (exc is UnauthorizedAccessException || exc is ArgumentException || exc is IOException)
370364
{
371365
}
372366
return latest;
@@ -530,13 +524,13 @@ private void OnProjectItemChanged(object sender, TestFileChangedEventArgs e)
530524
if (e != null && ShouldDiscover(e.File))
531525
{
532526
string root = null;
527+
var project = e.Project ?? GetTestProjectFromFile(e.File);
533528
switch (e.ChangedReason)
534529
{
535-
case TestFileChangedReason.Added:
536-
Debug.Assert(e.Project != null);
537-
if (e.Project.IsTestProject(Guids.NodejsBaseProjectFactory))
530+
case WatcherChangeTypes.Created:
531+
if (project.IsTestProject(Guids.NodejsBaseProjectFactory))
538532
{
539-
root = e.Project.GetProjectHome();
533+
root = project.GetProjectHome();
540534

541535
if (!string.IsNullOrEmpty(root) && CommonUtils.IsSubpathOf(root, e.File))
542536
{
@@ -548,12 +542,10 @@ private void OnProjectItemChanged(object sender, TestFileChangedEventArgs e)
548542
this.testFilesUpdateWatcher.AddFileWatch(e.File);
549543
}
550544

551-
OnTestContainersChanged(e.Project);
545+
OnTestContainersChanged(project);
552546
}
553547
break;
554-
case TestFileChangedReason.Removed:
555-
Debug.Assert(e.Project != null);
556-
548+
case WatcherChangeTypes.Deleted:
557549
if (this.fileRootMap.TryGetValue(e.File, out root))
558550
{
559551
this.fileRootMap.Remove(e.File);
@@ -571,15 +563,15 @@ private void OnProjectItemChanged(object sender, TestFileChangedEventArgs e)
571563
// track the last delete as an update as our file system scan won't see it
572564
this.lastWrite = DateTime.Now.ToUniversalTime();
573565

574-
OnTestContainersChanged(e.Project);
566+
OnTestContainersChanged(project);
575567
break;
576568

577569
// Dev12 renames files instead of overwriting them when
578570
// saving, so we need to listen for renames where the new
579571
// path is part of the project.
580-
case TestFileChangedReason.Renamed:
581-
case TestFileChangedReason.Changed:
582-
OnTestContainersChanged(GetTestProjectFromFile(e.File));
572+
case WatcherChangeTypes.Renamed:
573+
case WatcherChangeTypes.Changed:
574+
OnTestContainersChanged(project);
583575
break;
584576
}
585577
}
@@ -600,7 +592,7 @@ private IVsProject GetTestProjectFromFile(string filename)
600592
CommonUtils.IsSamePath(projectPath, filename) ||
601593
(hierarchy != null &&
602594
project.IsTestProject(Guids.NodejsBaseProjectFactory) &&
603-
ErrorHandler.Succeeded(hierarchy.ParseCanonicalName(filename, out var itemid))))
595+
ErrorHandler.Succeeded(hierarchy.ParseCanonicalName(filename, out _))))
604596
{
605597
return project;
606598
}

0 commit comments

Comments
 (0)