2
2
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3
3
4
4
using System ;
5
+ using System . Diagnostics ;
5
6
using System . IO ;
7
+ using System . Threading ;
6
8
using Microsoft . Extensions . FileProviders . Internal ;
7
9
using Microsoft . Extensions . FileProviders . Physical ;
8
10
using Microsoft . Extensions . FileProviders . Physical . Internal ;
@@ -20,19 +22,25 @@ namespace Microsoft.Extensions.FileProviders
20
22
public class PhysicalFileProvider : IFileProvider , IDisposable
21
23
{
22
24
private const string PollingEnvironmentKey = "DOTNET_USE_POLLING_FILE_WATCHER" ;
23
-
24
25
private static readonly char [ ] _pathSeparators = new [ ]
25
26
{ Path . DirectorySeparatorChar , Path . AltDirectorySeparatorChar } ;
26
27
27
- private readonly PhysicalFilesWatcher _filesWatcher ;
28
28
private readonly ExclusionFilters _filters ;
29
29
30
+ private readonly Func < PhysicalFilesWatcher > _fileWatcherFactory ;
31
+ private PhysicalFilesWatcher _fileWatcher ;
32
+ private bool _fileWatcherInitialized ;
33
+ private object _fileWatcherLock = new object ( ) ;
34
+
35
+ private bool ? _usePollingFileWatcher ;
36
+ private bool ? _useActivePolling ;
37
+
30
38
/// <summary>
31
39
/// Initializes a new instance of a PhysicalFileProvider at the given root directory.
32
40
/// </summary>
33
41
/// <param name="root">The root directory. This should be an absolute path.</param>
34
42
public PhysicalFileProvider ( string root )
35
- : this ( root , CreateFileWatcher ( root , ExclusionFilters . Sensitive ) , ExclusionFilters . Sensitive )
43
+ : this ( root , ExclusionFilters . Sensitive )
36
44
{
37
45
}
38
46
@@ -42,20 +50,12 @@ public PhysicalFileProvider(string root)
42
50
/// <param name="root">The root directory. This should be an absolute path.</param>
43
51
/// <param name="filters">Specifies which files or directories are excluded.</param>
44
52
public PhysicalFileProvider ( string root , ExclusionFilters filters )
45
- : this ( root , CreateFileWatcher ( root , filters ) , filters )
46
- { }
47
-
48
- // for testing
49
- internal PhysicalFileProvider ( string root , PhysicalFilesWatcher physicalFilesWatcher )
50
- : this ( root , physicalFilesWatcher , ExclusionFilters . Sensitive )
51
- { }
52
-
53
- private PhysicalFileProvider ( string root , PhysicalFilesWatcher physicalFilesWatcher , ExclusionFilters filters )
54
53
{
55
54
if ( ! Path . IsPathRooted ( root ) )
56
55
{
57
56
throw new ArgumentException ( "The path must be absolute." , nameof ( root ) ) ;
58
57
}
58
+
59
59
var fullRoot = Path . GetFullPath ( root ) ;
60
60
// When we do matches in GetFullPath, we want to only match full directory names.
61
61
Root = PathUtils . EnsureTrailingSlash ( fullRoot ) ;
@@ -64,28 +64,130 @@ private PhysicalFileProvider(string root, PhysicalFilesWatcher physicalFilesWatc
64
64
throw new DirectoryNotFoundException ( Root ) ;
65
65
}
66
66
67
- _filesWatcher = physicalFilesWatcher ;
68
67
_filters = filters ;
68
+ _fileWatcherFactory = ( ) => CreateFileWatcher ( ) ;
69
+ }
70
+
71
+ /// <summary>
72
+ /// Gets or sets a value that determines if this instance of <see cref="PhysicalFileProvider"/>
73
+ /// uses polling to determine file changes.
74
+ /// <para>
75
+ /// By default, <see cref="PhysicalFileProvider"/> uses <see cref="FileSystemWatcher"/> to listen to file change events
76
+ /// for <see cref="Watch(string)"/>. <see cref="FileSystemWatcher"/> is ineffective in some scenarios such as mounted drives.
77
+ /// Polling is required to effectively watch for file changes.
78
+ /// </para>
79
+ /// <seealso cref="UseActivePolling"/>.
80
+ /// </summary>
81
+ /// <value>
82
+ /// The default value of this property is determined by the value of environment variable named <c>DOTNET_USE_POLLING_FILE_WATCHER</c>.
83
+ /// When <c>true</c> or <c>1</c>, this property defaults to <c>true</c>; otherwise false.
84
+ /// </value>
85
+ public bool UsePollingFileWatcher
86
+ {
87
+ get
88
+ {
89
+ if ( _fileWatcher != null )
90
+ {
91
+ throw new InvalidOperationException ( $ "Cannot modify { nameof ( UsePollingFileWatcher ) } once file watcher has been initialized.") ;
92
+ }
93
+
94
+ if ( _usePollingFileWatcher == null )
95
+ {
96
+ ReadPollingEnvironmentVariables ( ) ;
97
+ }
98
+
99
+ return _usePollingFileWatcher . Value ;
100
+ }
101
+ set => _usePollingFileWatcher = value ;
102
+ }
103
+
104
+ /// <summary>
105
+ /// Gets or sets a value that determines if this instance of <see cref="PhysicalFileProvider"/>
106
+ /// actively polls for file changes.
107
+ /// <para>
108
+ /// When <see langword="true"/>, <see cref="IChangeToken"/> returned by <see cref="Watch(string)"/> will actively poll for file changes
109
+ /// (<see cref="IChangeToken.ActiveChangeCallbacks"/> will be <see langword="true"/>) instead of being passive.
110
+ /// </para>
111
+ /// <para>
112
+ /// This property is only effective when <see cref="UsePollingFileWatcher"/> is set.
113
+ /// </para>
114
+ /// </summary>
115
+ /// <value>
116
+ /// The default value of this property is determined by the value of environment variable named <c>DOTNET_USE_POLLING_FILE_WATCHER</c>.
117
+ /// When <c>true</c> or <c>1</c>, this property defaults to <c>true</c>; otherwise false.
118
+ /// </value>
119
+ public bool UseActivePolling
120
+ {
121
+ get
122
+ {
123
+ if ( _useActivePolling == null )
124
+ {
125
+ ReadPollingEnvironmentVariables ( ) ;
126
+ }
127
+
128
+ return _useActivePolling . Value ;
129
+ }
130
+
131
+ set => _useActivePolling = value ;
132
+ }
133
+
134
+ internal PhysicalFilesWatcher FileWatcher
135
+ {
136
+ get
137
+ {
138
+ return LazyInitializer . EnsureInitialized (
139
+ ref _fileWatcher ,
140
+ ref _fileWatcherInitialized ,
141
+ ref _fileWatcherLock ,
142
+ _fileWatcherFactory ) ;
143
+ }
144
+ set
145
+ {
146
+ Debug . Assert ( ! _fileWatcherInitialized ) ;
147
+
148
+ _fileWatcherInitialized = true ;
149
+ _fileWatcher = value ;
150
+ }
151
+ }
152
+
153
+ internal PhysicalFilesWatcher CreateFileWatcher ( )
154
+ {
155
+ var root = PathUtils . EnsureTrailingSlash ( Path . GetFullPath ( Root ) ) ;
156
+ return new PhysicalFilesWatcher ( root , new FileSystemWatcher ( root ) , UsePollingFileWatcher , _filters )
157
+ {
158
+ UseActivePolling = UseActivePolling ,
159
+ } ;
69
160
}
70
161
71
- private static PhysicalFilesWatcher CreateFileWatcher ( string root , ExclusionFilters filters )
162
+ private void ReadPollingEnvironmentVariables ( )
72
163
{
73
164
var environmentValue = Environment . GetEnvironmentVariable ( PollingEnvironmentKey ) ;
74
165
var pollForChanges = string . Equals ( environmentValue , "1" , StringComparison . Ordinal ) ||
75
- string . Equals ( environmentValue , "true" , StringComparison . OrdinalIgnoreCase ) ;
166
+ string . Equals ( environmentValue , "true" , StringComparison . OrdinalIgnoreCase ) ;
76
167
77
- root = PathUtils . EnsureTrailingSlash ( Path . GetFullPath ( root ) ) ;
78
- return new PhysicalFilesWatcher ( root , new FileSystemWatcher ( root ) , pollForChanges , filters ) ;
168
+ _usePollingFileWatcher = pollForChanges ;
169
+ _useActivePolling = pollForChanges ;
79
170
}
80
171
81
172
/// <summary>
82
173
/// Disposes the provider. Change tokens may not trigger after the provider is disposed.
83
174
/// </summary>
84
- public void Dispose ( )
175
+ public void Dispose ( ) => Dispose ( true ) ;
176
+
177
+ /// <summary>
178
+ /// Disposes the provider.
179
+ /// </summary>
180
+ /// <param name="disposing"><c>true</c> is invoked from <see cref="IDisposable.Dispose"/>.</param>
181
+ protected virtual void Dispose ( bool disposing )
85
182
{
86
- _filesWatcher . Dispose ( ) ;
183
+ _fileWatcher ? . Dispose ( ) ;
87
184
}
88
185
186
+ /// <summary>
187
+ /// Destructor for <see cref="PhysicalFileProvider"/>.
188
+ /// </summary>
189
+ ~ PhysicalFileProvider ( ) => Dispose ( false ) ;
190
+
89
191
/// <summary>
90
192
/// The root directory for this instance.
91
193
/// </summary>
@@ -225,7 +327,7 @@ public IChangeToken Watch(string filter)
225
327
// Relative paths starting with leading slashes are okay
226
328
filter = filter . TrimStart ( _pathSeparators ) ;
227
329
228
- return _filesWatcher . CreateFileChangeToken ( filter ) ;
330
+ return FileWatcher . CreateFileChangeToken ( filter ) ;
229
331
}
230
332
}
231
333
}
0 commit comments