Merge branch 'upstream'
Former-commit-id: 6deb37c607997fde2434a2952d1b62471153bb20
This commit is contained in:
commit
2ad705512d
@ -1 +1 @@
|
|||||||
780e08f1c84044d68b8cf86772043410d3356b47
|
e3fddfdc54d7204c3e52ef2273ea8a313426467c
|
@ -1 +1 @@
|
|||||||
2092fc41e2cc7db82ed0569a412bdf7e09f30285
|
f4f0b4d1402862ad9ca37078d37fedfb7dd03bed
|
@ -1 +1 @@
|
|||||||
0a306482eac9dc254a050e40a50147c9f8a8f319
|
a4d41f54aa4e797510b7305e90672d0f0b716d01
|
@ -1 +1 @@
|
|||||||
f1f7d7c12ac0ebb5ae0ba105d8a40604bbe64b2a
|
e7cc3043af28842bbfcbfcc3e778a58f8ee6e205
|
@ -1 +1 @@
|
|||||||
47154482003e653af7b9a47e14d653dd2a666e8d
|
b081ba1d1e3552cb0f915ccd31f8797bf18c73b5
|
@ -82,12 +82,12 @@ internal static partial class Interop
|
|||||||
/// <param name="eventFlags">The events for the corresponding path.</param>
|
/// <param name="eventFlags">The events for the corresponding path.</param>
|
||||||
/// <param name="eventIds">The machine-and-disk-drive-unique Event ID for the specific event.</param>
|
/// <param name="eventIds">The machine-and-disk-drive-unique Event ID for the specific event.</param>
|
||||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
internal delegate void FSEventStreamCallback(
|
internal unsafe delegate void FSEventStreamCallback(
|
||||||
FSEventStreamRef streamReference,
|
FSEventStreamRef streamReference,
|
||||||
IntPtr clientCallBackInfo,
|
IntPtr clientCallBackInfo,
|
||||||
size_t numEvents,
|
size_t numEvents,
|
||||||
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)]
|
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)]
|
||||||
String[] eventPaths,
|
byte** eventPaths,
|
||||||
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)]
|
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)]
|
||||||
FSEventStreamEventFlags[] eventFlags,
|
FSEventStreamEventFlags[] eventFlags,
|
||||||
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)]
|
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)]
|
||||||
|
@ -15,7 +15,7 @@ internal partial class Interop
|
|||||||
internal static extern unsafe bool ReadDirectoryChangesW(
|
internal static extern unsafe bool ReadDirectoryChangesW(
|
||||||
SafeFileHandle hDirectory,
|
SafeFileHandle hDirectory,
|
||||||
byte[] lpBuffer,
|
byte[] lpBuffer,
|
||||||
int nBufferLength,
|
uint nBufferLength,
|
||||||
[MarshalAs(UnmanagedType.Bool)] bool bWatchSubtree,
|
[MarshalAs(UnmanagedType.Bool)] bool bWatchSubtree,
|
||||||
int dwNotifyFilter,
|
int dwNotifyFilter,
|
||||||
out int lpBytesReturned,
|
out int lpBytesReturned,
|
||||||
|
@ -2,10 +2,12 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Buffers;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
using CFStringRef = System.IntPtr;
|
using CFStringRef = System.IntPtr;
|
||||||
using FSEventStreamRef = System.IntPtr;
|
using FSEventStreamRef = System.IntPtr;
|
||||||
@ -146,8 +148,6 @@ namespace System.IO
|
|||||||
// The EventStream to listen for events on
|
// The EventStream to listen for events on
|
||||||
private SafeEventStreamHandle _eventStream;
|
private SafeEventStreamHandle _eventStream;
|
||||||
|
|
||||||
// A reference to the RunLoop that we can use to start or stop a Watcher
|
|
||||||
private CFRunLoopRef _watcherRunLoop;
|
|
||||||
|
|
||||||
// Callback delegate for the EventStream events
|
// Callback delegate for the EventStream events
|
||||||
private Interop.EventStream.FSEventStreamCallback _callback;
|
private Interop.EventStream.FSEventStreamCallback _callback;
|
||||||
@ -158,7 +158,8 @@ namespace System.IO
|
|||||||
|
|
||||||
// Calling RunLoopStop multiple times SegFaults so protect the call to it
|
// Calling RunLoopStop multiple times SegFaults so protect the call to it
|
||||||
private bool _stopping;
|
private bool _stopping;
|
||||||
private object StopLock => this;
|
|
||||||
|
private ExecutionContext _context;
|
||||||
|
|
||||||
internal RunningInstance(
|
internal RunningInstance(
|
||||||
FileSystemWatcher watcher,
|
FileSystemWatcher watcher,
|
||||||
@ -171,7 +172,6 @@ namespace System.IO
|
|||||||
Debug.Assert(!cancelToken.IsCancellationRequested);
|
Debug.Assert(!cancelToken.IsCancellationRequested);
|
||||||
|
|
||||||
_weakWatcher = new WeakReference<FileSystemWatcher>(watcher);
|
_weakWatcher = new WeakReference<FileSystemWatcher>(watcher);
|
||||||
_watcherRunLoop = IntPtr.Zero;
|
|
||||||
_fullDirectory = System.IO.Path.GetFullPath(directory);
|
_fullDirectory = System.IO.Path.GetFullPath(directory);
|
||||||
_includeChildren = includeChildren;
|
_includeChildren = includeChildren;
|
||||||
_filterFlags = filter;
|
_filterFlags = filter;
|
||||||
@ -180,21 +180,111 @@ namespace System.IO
|
|||||||
_stopping = false;
|
_stopping = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CancellationCallback()
|
private static class StaticWatcherRunLoopManager
|
||||||
{
|
{
|
||||||
lock (StopLock)
|
// A reference to the RunLoop that we can use to start or stop a Watcher
|
||||||
{
|
private static CFRunLoopRef s_watcherRunLoop = IntPtr.Zero;
|
||||||
if (!_stopping && _watcherRunLoop != IntPtr.Zero)
|
|
||||||
{
|
|
||||||
_stopping = true;
|
|
||||||
|
|
||||||
// Stop the FS event message pump
|
private static int s_scheduledStreamsCount = 0;
|
||||||
Interop.RunLoop.CFRunLoopStop(_watcherRunLoop);
|
|
||||||
|
private static readonly object s_lockObject = new object();
|
||||||
|
|
||||||
|
public static void ScheduleEventStream(SafeEventStreamHandle eventStream)
|
||||||
|
{
|
||||||
|
lock (s_lockObject)
|
||||||
|
{
|
||||||
|
if (s_watcherRunLoop != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
// Schedule the EventStream to run on the thread's RunLoop
|
||||||
|
s_scheduledStreamsCount++;
|
||||||
|
Interop.EventStream.FSEventStreamScheduleWithRunLoop(eventStream, s_watcherRunLoop, Interop.RunLoop.kCFRunLoopDefaultMode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Assert(s_scheduledStreamsCount == 0);
|
||||||
|
s_scheduledStreamsCount = 1;
|
||||||
|
var runLoopStarted = new ManualResetEventSlim();
|
||||||
|
new Thread(WatchForFileSystemEventsThreadStart) { IsBackground = true }.Start(new object[] { runLoopStarted, eventStream });
|
||||||
|
runLoopStarted.Wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void UnscheduleFromRunLoop(SafeEventStreamHandle eventStream)
|
||||||
|
{
|
||||||
|
Debug.Assert(s_watcherRunLoop != IntPtr.Zero);
|
||||||
|
lock (s_lockObject)
|
||||||
|
{
|
||||||
|
if (s_watcherRunLoop != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
// Always unschedule the RunLoop before cleaning up
|
||||||
|
Interop.EventStream.FSEventStreamUnscheduleFromRunLoop(eventStream, s_watcherRunLoop, Interop.RunLoop.kCFRunLoopDefaultMode);
|
||||||
|
s_scheduledStreamsCount--;
|
||||||
|
|
||||||
|
if (s_scheduledStreamsCount == 0)
|
||||||
|
{
|
||||||
|
// Stop the FS event message pump
|
||||||
|
Interop.RunLoop.CFRunLoopStop(s_watcherRunLoop);
|
||||||
|
s_watcherRunLoop = IntPtr.Zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WatchForFileSystemEventsThreadStart(object args)
|
||||||
|
{
|
||||||
|
var inputArgs = (object[])args;
|
||||||
|
var runLoopStarted = (ManualResetEventSlim)inputArgs[0];
|
||||||
|
var _eventStream = (SafeEventStreamHandle)inputArgs[1];
|
||||||
|
// Get this thread's RunLoop
|
||||||
|
IntPtr runLoop = Interop.RunLoop.CFRunLoopGetCurrent();
|
||||||
|
s_watcherRunLoop = runLoop;
|
||||||
|
Debug.Assert(s_watcherRunLoop != IntPtr.Zero);
|
||||||
|
|
||||||
|
// Retain the RunLoop so that it doesn't get moved or cleaned up before we're done with it.
|
||||||
|
IntPtr retainResult = Interop.CoreFoundation.CFRetain(runLoop);
|
||||||
|
Debug.Assert(retainResult == runLoop, "CFRetain is supposed to return the input value");
|
||||||
|
|
||||||
|
// Schedule the EventStream to run on the thread's RunLoop
|
||||||
|
Interop.EventStream.FSEventStreamScheduleWithRunLoop(_eventStream, runLoop, Interop.RunLoop.kCFRunLoopDefaultMode);
|
||||||
|
|
||||||
|
runLoopStarted.Set();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Start the OS X RunLoop (a blocking call) that will pump file system changes into the callback function
|
||||||
|
Interop.RunLoop.CFRunLoopRun();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
lock (s_lockObject)
|
||||||
|
{
|
||||||
|
#if MONO
|
||||||
|
s_watcherRunLoop = IntPtr.Zero;
|
||||||
|
#endif
|
||||||
|
Interop.CoreFoundation.CFRelease(runLoop);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Start()
|
private void CancellationCallback()
|
||||||
|
{
|
||||||
|
if (!_stopping && _eventStream != null)
|
||||||
|
{
|
||||||
|
_stopping = true;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// When we get here, we've requested to stop so cleanup the EventStream and unschedule from the RunLoop
|
||||||
|
Interop.EventStream.FSEventStreamStop(_eventStream);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
StaticWatcherRunLoopManager.UnscheduleFromRunLoop(_eventStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal unsafe void Start()
|
||||||
{
|
{
|
||||||
// Make sure _fullPath doesn't contain a link or alias
|
// Make sure _fullPath doesn't contain a link or alias
|
||||||
// since the OS will give back the actual, non link'd or alias'd paths
|
// since the OS will give back the actual, non link'd or alias'd paths
|
||||||
@ -233,6 +323,8 @@ namespace System.IO
|
|||||||
_callback = new Interop.EventStream.FSEventStreamCallback(FileSystemEventCallback);
|
_callback = new Interop.EventStream.FSEventStreamCallback(FileSystemEventCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_context = ExecutionContext.Capture();
|
||||||
|
|
||||||
// Make sure the OS file buffer(s) are fully flushed so we don't get events from cached I/O
|
// Make sure the OS file buffer(s) are fully flushed so we don't get events from cached I/O
|
||||||
Interop.Sys.Sync();
|
Interop.Sys.Sync();
|
||||||
|
|
||||||
@ -250,82 +342,32 @@ namespace System.IO
|
|||||||
throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo(), _fullDirectory, true);
|
throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo(), _fullDirectory, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and start our watcher thread then wait for the thread to initialize and start
|
StaticWatcherRunLoopManager.ScheduleEventStream(_eventStream);
|
||||||
// the RunLoop. We wait for that to prevent this function from returning before the RunLoop
|
|
||||||
// has a chance to start so that any callers won't race with the background thread's initialization
|
|
||||||
// and calling Stop, which would attempt to stop a RunLoop that hasn't started yet.
|
|
||||||
var runLoopStarted = new ManualResetEventSlim();
|
|
||||||
new Thread(WatchForFileSystemEventsThreadStart) { IsBackground = true }.Start(runLoopStarted);
|
|
||||||
runLoopStarted.Wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WatchForFileSystemEventsThreadStart(object arg)
|
bool started = Interop.EventStream.FSEventStreamStart(_eventStream);
|
||||||
{
|
if (!started)
|
||||||
var runLoopStarted = (ManualResetEventSlim)arg;
|
{
|
||||||
|
// Try to get the Watcher to raise the error event; if we can't do that, just silently exit since the watcher is gone anyway
|
||||||
// Get this thread's RunLoop
|
FileSystemWatcher watcher;
|
||||||
_watcherRunLoop = Interop.RunLoop.CFRunLoopGetCurrent();
|
if (_weakWatcher.TryGetTarget(out watcher))
|
||||||
Debug.Assert(_watcherRunLoop != IntPtr.Zero);
|
|
||||||
|
|
||||||
// Retain the RunLoop so that it doesn't get moved or cleaned up before we're done with it.
|
|
||||||
IntPtr retainResult = Interop.CoreFoundation.CFRetain(_watcherRunLoop);
|
|
||||||
Debug.Assert(retainResult == _watcherRunLoop, "CFRetain is supposed to return the input value");
|
|
||||||
|
|
||||||
// Schedule the EventStream to run on the thread's RunLoop
|
|
||||||
Interop.EventStream.FSEventStreamScheduleWithRunLoop(_eventStream, _watcherRunLoop, Interop.RunLoop.kCFRunLoopDefaultMode);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
bool started = Interop.EventStream.FSEventStreamStart(_eventStream);
|
|
||||||
|
|
||||||
// Notify the StartRaisingEvents call that we are initialized and about to start
|
|
||||||
// so that it can return and avoid a race-condition around multiple threads calling Stop and Start
|
|
||||||
runLoopStarted.Set();
|
|
||||||
|
|
||||||
if (started)
|
|
||||||
{
|
{
|
||||||
// Start the OS X RunLoop (a blocking call) that will pump file system changes into the callback function
|
// An error occurred while trying to start the run loop so fail out
|
||||||
Interop.RunLoop.CFRunLoopRun();
|
watcher.OnError(new ErrorEventArgs(new IOException(SR.EventStream_FailedToStart, Marshal.GetLastWin32Error())));
|
||||||
|
|
||||||
// When we get here, we've requested to stop so cleanup the EventStream and unschedule from the RunLoop
|
|
||||||
Interop.EventStream.FSEventStreamStop(_eventStream);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Try to get the Watcher to raise the error event; if we can't do that, just silently exist since the watcher is gone anyway
|
|
||||||
FileSystemWatcher watcher;
|
|
||||||
if (_weakWatcher.TryGetTarget(out watcher))
|
|
||||||
{
|
|
||||||
// An error occurred while trying to start the run loop so fail out
|
|
||||||
watcher.OnError(new ErrorEventArgs(new IOException(SR.EventStream_FailedToStart, Marshal.GetLastWin32Error())));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
// Always unschedule the RunLoop before cleaning up
|
|
||||||
Interop.EventStream.FSEventStreamUnscheduleFromRunLoop(_eventStream, _watcherRunLoop, Interop.RunLoop.kCFRunLoopDefaultMode);
|
|
||||||
|
|
||||||
// Release the WatcherLoop Core Foundation object.
|
|
||||||
lock (StopLock)
|
|
||||||
{
|
|
||||||
Interop.CoreFoundation.CFRelease(_watcherRunLoop);
|
|
||||||
_watcherRunLoop = IntPtr.Zero;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FileSystemEventCallback(
|
private unsafe void FileSystemEventCallback(
|
||||||
FSEventStreamRef streamRef,
|
FSEventStreamRef streamRef,
|
||||||
IntPtr clientCallBackInfo,
|
IntPtr clientCallBackInfo,
|
||||||
size_t numEvents,
|
size_t numEvents,
|
||||||
String[] eventPaths,
|
byte** eventPaths,
|
||||||
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)]
|
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)]
|
||||||
Interop.EventStream.FSEventStreamEventFlags[] eventFlags,
|
Interop.EventStream.FSEventStreamEventFlags[] eventFlags,
|
||||||
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)]
|
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)]
|
||||||
FSEventStreamEventId[] eventIds)
|
FSEventStreamEventId[] eventIds)
|
||||||
{
|
{
|
||||||
Debug.Assert((numEvents.ToInt32() == eventPaths.Length) && (numEvents.ToInt32() == eventFlags.Length) && (numEvents.ToInt32() == eventIds.Length));
|
Debug.Assert((eventPaths != null) && (numEvents.ToInt32() == eventFlags.Length) && (numEvents.ToInt32() == eventIds.Length));
|
||||||
|
|
||||||
// Try to get the actual watcher from our weak reference. We maintain a weak reference most of the time
|
// Try to get the actual watcher from our weak reference. We maintain a weak reference most of the time
|
||||||
// so as to avoid a rooted cycle that would prevent our processing loop from ever ending
|
// so as to avoid a rooted cycle that would prevent our processing loop from ever ending
|
||||||
@ -338,18 +380,40 @@ namespace System.IO
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ExecutionContext context = _context;
|
||||||
|
if (context is null)
|
||||||
|
{
|
||||||
|
// Flow suppressed, just run here
|
||||||
|
ProcessEvents(numEvents.ToInt32(), eventPaths, eventFlags, eventIds, watcher);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ExecutionContext.Run(
|
||||||
|
context,
|
||||||
|
(object o) => ((RunningInstance)o).ProcessEvents(numEvents.ToInt32(), eventPaths, eventFlags, eventIds, watcher),
|
||||||
|
this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void ProcessEvents(int numEvents,
|
||||||
|
byte** eventPaths,
|
||||||
|
Interop.EventStream.FSEventStreamEventFlags[] eventFlags,
|
||||||
|
FSEventStreamEventId[] eventIds,
|
||||||
|
FileSystemWatcher watcher)
|
||||||
|
{
|
||||||
// Since renames come in pairs, when we find the first we need to search for the next one. Once we find it, we'll add it to this
|
// Since renames come in pairs, when we find the first we need to search for the next one. Once we find it, we'll add it to this
|
||||||
// list so when the for-loop comes across it, we'll skip it since it's already been processed as part of the original of the pair.
|
// list so when the for-loop comes across it, we'll skip it since it's already been processed as part of the original of the pair.
|
||||||
List<FSEventStreamEventId> handledRenameEvents = null;
|
List<FSEventStreamEventId> handledRenameEvents = null;
|
||||||
|
Memory<char>[] events = new Memory<char>[numEvents];
|
||||||
|
ParseEvents();
|
||||||
|
|
||||||
for (long i = 0; i < numEvents.ToInt32(); i++)
|
for (long i = 0; i < numEvents; i++)
|
||||||
{
|
{
|
||||||
Debug.Assert(eventPaths[i].Length > 0, "Empty events are not supported");
|
ReadOnlySpan<char> path = events[i].Span;
|
||||||
Debug.Assert(eventPaths[i][eventPaths[i].Length - 1] != '/', "Trailing slashes on events is not supported");
|
Debug.Assert(path[path.Length - 1] != '/', "Trailing slashes on events is not supported");
|
||||||
|
|
||||||
// Match Windows and don't notify us about changes to the Root folder
|
// Match Windows and don't notify us about changes to the Root folder
|
||||||
string path = eventPaths[i];
|
if (_fullDirectory.Length >= path.Length && path.Equals(_fullDirectory.AsSpan(0, path.Length), StringComparison.OrdinalIgnoreCase))
|
||||||
if (string.Compare(path, 0, _fullDirectory, 0, path.Length, StringComparison.OrdinalIgnoreCase) == 0)
|
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -366,15 +430,15 @@ namespace System.IO
|
|||||||
// If this event is the second in a rename pair then skip it
|
// If this event is the second in a rename pair then skip it
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
else if (CheckIfPathIsNested(path) && ((eventType = FilterEvents(eventFlags[i], path)) != 0))
|
else if (CheckIfPathIsNested(path) && ((eventType = FilterEvents(eventFlags[i])) != 0))
|
||||||
{
|
{
|
||||||
// The base FileSystemWatcher does a match check against the relative path before combining with
|
// The base FileSystemWatcher does a match check against the relative path before combining with
|
||||||
// the root dir; however, null is special cased to signify the root dir, so check if we should use that.
|
// the root dir; however, null is special cased to signify the root dir, so check if we should use that.
|
||||||
string relativePath = null;
|
ReadOnlySpan<char> relativePath = ReadOnlySpan<char>.Empty;
|
||||||
if (path.Equals(_fullDirectory, StringComparison.OrdinalIgnoreCase) == false)
|
if (!path.Equals(_fullDirectory, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
// Remove the root directory to get the relative path
|
// Remove the root directory to get the relative path
|
||||||
relativePath = path.Remove(0, _fullDirectory.Length);
|
relativePath = path.Slice(_fullDirectory.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Raise a notification for the event
|
// Raise a notification for the event
|
||||||
@ -393,7 +457,7 @@ namespace System.IO
|
|||||||
if (((eventType & WatcherChangeTypes.Renamed) > 0))
|
if (((eventType & WatcherChangeTypes.Renamed) > 0))
|
||||||
{
|
{
|
||||||
// Find the rename that is paired to this rename, which should be the next rename in the list
|
// Find the rename that is paired to this rename, which should be the next rename in the list
|
||||||
long pairedId = FindRenameChangePairedChange(i, eventPaths, eventFlags, eventIds);
|
long pairedId = FindRenameChangePairedChange(i, eventFlags);
|
||||||
if (pairedId == long.MinValue)
|
if (pairedId == long.MinValue)
|
||||||
{
|
{
|
||||||
// Getting here means we have a rename without a pair, meaning it should be a create for the
|
// Getting here means we have a rename without a pair, meaning it should be a create for the
|
||||||
@ -416,7 +480,7 @@ namespace System.IO
|
|||||||
{
|
{
|
||||||
// Remove the base directory prefix and add the paired event to the list of
|
// Remove the base directory prefix and add the paired event to the list of
|
||||||
// events to skip and notify the user of the rename
|
// events to skip and notify the user of the rename
|
||||||
string newPathRelativeName = eventPaths[pairedId].Remove(0, _fullDirectory.Length);
|
ReadOnlySpan<char> newPathRelativeName = events[pairedId].Span.Slice(_fullDirectory.Length);
|
||||||
watcher.NotifyRenameEventArgs(WatcherChangeTypes.Renamed, newPathRelativeName, relativePath);
|
watcher.NotifyRenameEventArgs(WatcherChangeTypes.Renamed, newPathRelativeName, relativePath);
|
||||||
|
|
||||||
// Create a new list, if necessary, and add the event
|
// Create a new list, if necessary, and add the event
|
||||||
@ -428,6 +492,37 @@ namespace System.IO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ArraySegment<char> underlyingArray;
|
||||||
|
if (MemoryMarshal.TryGetArray(events[i], out underlyingArray))
|
||||||
|
ArrayPool<char>.Shared.Return(underlyingArray.Array);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._context = ExecutionContext.Capture();
|
||||||
|
|
||||||
|
void ParseEvents()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < events.Length; i++)
|
||||||
|
{
|
||||||
|
int byteCount = 0;
|
||||||
|
Debug.Assert(eventPaths[i] != null);
|
||||||
|
byte* temp = eventPaths[i];
|
||||||
|
|
||||||
|
// Finds the position of null character.
|
||||||
|
while (*temp != 0)
|
||||||
|
{
|
||||||
|
temp++;
|
||||||
|
byteCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Assert(byteCount > 0, "Empty events are not supported");
|
||||||
|
events[i] = new Memory<char>(ArrayPool<char>.Shared.Rent(Encoding.UTF8.GetMaxCharCount(byteCount)));
|
||||||
|
int charCount;
|
||||||
|
|
||||||
|
// Converting an array of bytes to UTF-8 char array
|
||||||
|
charCount = Encoding.UTF8.GetChars(new ReadOnlySpan<byte>(eventPaths[i], byteCount), events[i].Span);
|
||||||
|
events[i] = events[i].Slice(0, charCount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -435,7 +530,7 @@ namespace System.IO
|
|||||||
/// Compares the given event flags to the filter flags and returns which event (if any) corresponds
|
/// Compares the given event flags to the filter flags and returns which event (if any) corresponds
|
||||||
/// to those flags.
|
/// to those flags.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private WatcherChangeTypes FilterEvents(Interop.EventStream.FSEventStreamEventFlags eventFlags, string fullPath)
|
private WatcherChangeTypes FilterEvents(Interop.EventStream.FSEventStreamEventFlags eventFlags)
|
||||||
{
|
{
|
||||||
const Interop.EventStream.FSEventStreamEventFlags changedFlags = Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemInodeMetaMod |
|
const Interop.EventStream.FSEventStreamEventFlags changedFlags = Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemInodeMetaMod |
|
||||||
Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemFinderInfoMod |
|
Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemFinderInfoMod |
|
||||||
@ -487,30 +582,24 @@ namespace System.IO
|
|||||||
IsFlagSet(flags, Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagUnmount));
|
IsFlagSet(flags, Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagUnmount));
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CheckIfPathIsNested(string eventPath)
|
private bool CheckIfPathIsNested(ReadOnlySpan<char> eventPath)
|
||||||
{
|
{
|
||||||
bool doesPathPass = true;
|
|
||||||
|
|
||||||
// If we shouldn't include subdirectories, check if this path's parent is the watch directory
|
// If we shouldn't include subdirectories, check if this path's parent is the watch directory
|
||||||
if (_includeChildren == false)
|
// Check if the parent is the root. If so, then we'll continue processing based on the name.
|
||||||
{
|
// If it isn't, then this will be set to false and we'll skip the name processing since it's irrelevant.
|
||||||
// Check if the parent is the root. If so, then we'll continue processing based on the name.
|
#if MONO
|
||||||
// If it isn't, then this will be set to false and we'll skip the name processing since it's irrelevant.
|
return _includeChildren || _fullDirectory.AsSpan().StartsWith(System.IO.Path.GetDirectoryName(eventPath.ToString()), StringComparison.OrdinalIgnoreCase);
|
||||||
string parent = System.IO.Path.GetDirectoryName(eventPath);
|
#else
|
||||||
doesPathPass = (string.Compare(parent, 0, _fullDirectory, 0, parent.Length, StringComparison.OrdinalIgnoreCase) == 0);
|
return _includeChildren || _fullDirectory.AsSpan().StartsWith(System.IO.Path.GetDirectoryName(eventPath), StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
#endif
|
||||||
|
|
||||||
return doesPathPass;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private long FindRenameChangePairedChange(
|
private long FindRenameChangePairedChange(
|
||||||
long currentIndex,
|
long currentIndex,
|
||||||
String[] eventPaths,
|
Interop.EventStream.FSEventStreamEventFlags[] eventFlags)
|
||||||
Interop.EventStream.FSEventStreamEventFlags[] eventFlags,
|
|
||||||
FSEventStreamEventId[] eventIds)
|
|
||||||
{
|
{
|
||||||
// Start at one past the current index and try to find the next Rename item, which should be the old path.
|
// Start at one past the current index and try to find the next Rename item, which should be the old path.
|
||||||
for (long i = currentIndex + 1; i < eventPaths.Length; i++)
|
for (long i = currentIndex + 1; i < eventFlags.Length; i++)
|
||||||
{
|
{
|
||||||
if (IsFlagSet(eventFlags[i], Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemRenamed))
|
if (IsFlagSet(eventFlags[i], Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemRenamed))
|
||||||
{
|
{
|
||||||
@ -527,12 +616,26 @@ namespace System.IO
|
|||||||
return (value & flags) == value;
|
return (value & flags) == value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool DoesItemExist(string path, bool isFile)
|
private static bool DoesItemExist(ReadOnlySpan<char> path, bool isFile)
|
||||||
{
|
{
|
||||||
if (isFile)
|
if (path.IsEmpty || path.Length == 0)
|
||||||
return File.Exists(path);
|
return false;
|
||||||
else
|
|
||||||
return Directory.Exists(path);
|
#if MONO
|
||||||
|
if (!isFile)
|
||||||
|
return Directory.Exists(path.ToString());
|
||||||
|
|
||||||
|
return path[path.Length - 1] == '/'
|
||||||
|
? false
|
||||||
|
: File.Exists(path.ToString());
|
||||||
|
#else
|
||||||
|
if (!isFile)
|
||||||
|
return FileSystem.DirectoryExists(path);
|
||||||
|
|
||||||
|
return PathInternal.IsDirectorySeparator(path[path.Length - 1])
|
||||||
|
? false
|
||||||
|
: FileSystem.FileExists(path);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,9 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
@ -25,15 +28,12 @@ namespace System.IO
|
|||||||
/// </devdoc>
|
/// </devdoc>
|
||||||
public partial class FileSystemWatcher : Component, ISupportInitialize
|
public partial class FileSystemWatcher : Component, ISupportInitialize
|
||||||
{
|
{
|
||||||
/// <devdoc>
|
// Filters collection
|
||||||
/// Private instance variables
|
private readonly NormalizedFilterCollection _filters = new NormalizedFilterCollection();
|
||||||
/// </devdoc>
|
|
||||||
// Directory being monitored
|
// Directory being monitored
|
||||||
private string _directory;
|
private string _directory;
|
||||||
|
|
||||||
// Filter for name matching
|
|
||||||
private string _filter;
|
|
||||||
|
|
||||||
// The watch filter for the API call.
|
// The watch filter for the API call.
|
||||||
private const NotifyFilters c_defaultNotifyFilters = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
|
private const NotifyFilters c_defaultNotifyFilters = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
|
||||||
private NotifyFilters _notifyFilters = c_defaultNotifyFilters;
|
private NotifyFilters _notifyFilters = c_defaultNotifyFilters;
|
||||||
@ -48,11 +48,10 @@ namespace System.IO
|
|||||||
private bool _initializing = false;
|
private bool _initializing = false;
|
||||||
|
|
||||||
// Buffer size
|
// Buffer size
|
||||||
private int _internalBufferSize = 8192;
|
private uint _internalBufferSize = 8192;
|
||||||
|
|
||||||
// Used for synchronization
|
// Used for synchronization
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
private ISynchronizeInvoke _synchronizingObject;
|
|
||||||
|
|
||||||
// Event handlers
|
// Event handlers
|
||||||
private FileSystemEventHandler _onChangedHandler = null;
|
private FileSystemEventHandler _onChangedHandler = null;
|
||||||
@ -89,15 +88,16 @@ namespace System.IO
|
|||||||
public FileSystemWatcher()
|
public FileSystemWatcher()
|
||||||
{
|
{
|
||||||
_directory = string.Empty;
|
_directory = string.Empty;
|
||||||
_filter = "*";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <devdoc>
|
/// <devdoc>
|
||||||
/// Initializes a new instance of the <see cref='System.IO.FileSystemWatcher'/> class,
|
/// Initializes a new instance of the <see cref='System.IO.FileSystemWatcher'/> class,
|
||||||
/// given the specified directory to monitor.
|
/// given the specified directory to monitor.
|
||||||
/// </devdoc>
|
/// </devdoc>
|
||||||
public FileSystemWatcher(string path) : this(path, "*")
|
public FileSystemWatcher(string path)
|
||||||
{
|
{
|
||||||
|
CheckPathValidity(path);
|
||||||
|
_directory = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <devdoc>
|
/// <devdoc>
|
||||||
@ -106,21 +106,9 @@ namespace System.IO
|
|||||||
/// </devdoc>
|
/// </devdoc>
|
||||||
public FileSystemWatcher(string path, string filter)
|
public FileSystemWatcher(string path, string filter)
|
||||||
{
|
{
|
||||||
if (path == null)
|
CheckPathValidity(path);
|
||||||
throw new ArgumentNullException(nameof(path));
|
|
||||||
|
|
||||||
// Early check for directory parameter so that an exception can be thrown as early as possible.
|
|
||||||
if (path.Length == 0)
|
|
||||||
throw new ArgumentException(SR.Format(SR.InvalidDirName, path), nameof(path));
|
|
||||||
|
|
||||||
if (!Directory.Exists(path))
|
|
||||||
throw new ArgumentException(SR.Format(SR.InvalidDirName_NotExists, path), nameof(path));
|
|
||||||
|
|
||||||
_directory = path;
|
_directory = path;
|
||||||
_filter = filter ?? throw new ArgumentNullException(nameof(filter));
|
Filter = filter ?? throw new ArgumentNullException(nameof(filter));
|
||||||
|
|
||||||
if (_filter == "*.*")
|
|
||||||
_filter = "*";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <devdoc>
|
/// <devdoc>
|
||||||
@ -146,6 +134,8 @@ namespace System.IO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Collection<string> Filters => _filters;
|
||||||
|
|
||||||
/// <devdoc>
|
/// <devdoc>
|
||||||
/// Gets or sets a value indicating whether the component is enabled.
|
/// Gets or sets a value indicating whether the component is enabled.
|
||||||
/// </devdoc>
|
/// </devdoc>
|
||||||
@ -187,20 +177,12 @@ namespace System.IO
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return _filter;
|
return Filters.Count == 0 ? "*" : Filters[0];
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(value))
|
Filters.Clear();
|
||||||
{
|
Filters.Add(value);
|
||||||
// Skip the string compare for "*" since it has no case-insensitive representation that differs from
|
|
||||||
// the case-sensitive representation.
|
|
||||||
_filter = "*";
|
|
||||||
}
|
|
||||||
else if (!string.Equals(_filter, value, PathInternal.StringComparison))
|
|
||||||
{
|
|
||||||
_filter = value == "*.*" ? "*" : value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,7 +213,7 @@ namespace System.IO
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return _internalBufferSize;
|
return (int)_internalBufferSize;
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
@ -243,7 +225,7 @@ namespace System.IO
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_internalBufferSize = value;
|
_internalBufferSize = (uint)value;
|
||||||
}
|
}
|
||||||
|
|
||||||
Restart();
|
Restart();
|
||||||
@ -284,7 +266,7 @@ namespace System.IO
|
|||||||
|
|
||||||
if (!Directory.Exists(value))
|
if (!Directory.Exists(value))
|
||||||
throw new ArgumentException(SR.Format(SR.InvalidDirName_NotExists, value), nameof(Path));
|
throw new ArgumentException(SR.Format(SR.InvalidDirName_NotExists, value), nameof(Path));
|
||||||
|
|
||||||
_directory = value;
|
_directory = value;
|
||||||
Restart();
|
Restart();
|
||||||
}
|
}
|
||||||
@ -367,8 +349,6 @@ namespace System.IO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <devdoc>
|
|
||||||
/// </devdoc>
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -398,66 +378,100 @@ namespace System.IO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <devdoc>
|
private static void CheckPathValidity(string path)
|
||||||
/// Sees if the name given matches the name filter we have.
|
|
||||||
/// </devdoc>
|
|
||||||
/// <internalonly/>
|
|
||||||
private bool MatchPattern(string relativePath)
|
|
||||||
{
|
{
|
||||||
ReadOnlySpan<char> name = IO.Path.GetFileName(relativePath.AsSpan());
|
if (path == null)
|
||||||
return name.Length > 0
|
throw new ArgumentNullException(nameof(path));
|
||||||
? FileSystemName.MatchesSimpleExpression(_filter, name, ignoreCase: !PathInternal.IsCaseSensitive)
|
|
||||||
: false;
|
// Early check for directory parameter so that an exception can be thrown as early as possible.
|
||||||
|
if (path.Length == 0)
|
||||||
|
throw new ArgumentException(SR.Format(SR.InvalidDirName, path), nameof(path));
|
||||||
|
|
||||||
|
if (!Directory.Exists(path))
|
||||||
|
throw new ArgumentException(SR.Format(SR.InvalidDirName_NotExists, path), nameof(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <devdoc>
|
/// <summary>
|
||||||
/// Raises the event to each handler in the list.
|
/// Sees if the name given matches the name filter we have.
|
||||||
/// </devdoc>
|
/// </summary>
|
||||||
/// <internalonly/>
|
private bool MatchPattern(ReadOnlySpan<char> relativePath)
|
||||||
|
{
|
||||||
|
ReadOnlySpan<char> name = IO.Path.GetFileName(relativePath);
|
||||||
|
if (name.Length == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
string[] filters = _filters.GetFilters();
|
||||||
|
if (filters.Length == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
foreach (string filter in filters)
|
||||||
|
{
|
||||||
|
if (FileSystemName.MatchesSimpleExpression(filter, name, ignoreCase: !PathInternal.IsCaseSensitive))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raises the event to each handler in the list.
|
||||||
|
/// </summary>
|
||||||
private void NotifyInternalBufferOverflowEvent()
|
private void NotifyInternalBufferOverflowEvent()
|
||||||
{
|
{
|
||||||
_onErrorHandler?.Invoke(this, new ErrorEventArgs(
|
_onErrorHandler?.Invoke(this, new ErrorEventArgs(
|
||||||
new InternalBufferOverflowException(SR.Format(SR.FSW_BufferOverflow, _directory))));
|
new InternalBufferOverflowException(SR.Format(SR.FSW_BufferOverflow, _directory))));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <devdoc>
|
/// <summary>
|
||||||
/// Raises the event to each handler in the list.
|
/// Raises the event to each handler in the list.
|
||||||
/// </devdoc>
|
/// </summary>
|
||||||
/// <internalonly/>
|
private void NotifyRenameEventArgs(WatcherChangeTypes action, ReadOnlySpan<char> name, ReadOnlySpan<char> oldName)
|
||||||
private void NotifyRenameEventArgs(WatcherChangeTypes action, string name, string oldName)
|
|
||||||
{
|
{
|
||||||
// filter if there's no handler or neither new name or old name match a specified pattern
|
// filter if there's no handler or neither new name or old name match a specified pattern
|
||||||
RenamedEventHandler handler = _onRenamedHandler;
|
RenamedEventHandler handler = _onRenamedHandler;
|
||||||
if (handler != null &&
|
if (handler != null &&
|
||||||
(MatchPattern(name) || MatchPattern(oldName)))
|
(MatchPattern(name) || MatchPattern(oldName)))
|
||||||
{
|
{
|
||||||
handler(this, new RenamedEventArgs(action, _directory, name, oldName));
|
handler(this, new RenamedEventArgs(action, _directory, name.IsEmpty ? null : name.ToString(), oldName.IsEmpty ? null : oldName.ToString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <devdoc>
|
private FileSystemEventHandler GetHandler(WatcherChangeTypes changeType)
|
||||||
/// Raises the event to each handler in the list.
|
|
||||||
/// </devdoc>
|
|
||||||
/// <internalonly/>
|
|
||||||
private void NotifyFileSystemEventArgs(WatcherChangeTypes changeType, string name)
|
|
||||||
{
|
{
|
||||||
FileSystemEventHandler handler = null;
|
|
||||||
switch (changeType)
|
switch (changeType)
|
||||||
{
|
{
|
||||||
case WatcherChangeTypes.Created:
|
case WatcherChangeTypes.Created:
|
||||||
handler = _onCreatedHandler;
|
return _onCreatedHandler;
|
||||||
break;
|
|
||||||
case WatcherChangeTypes.Deleted:
|
case WatcherChangeTypes.Deleted:
|
||||||
handler = _onDeletedHandler;
|
return _onDeletedHandler;
|
||||||
break;
|
|
||||||
case WatcherChangeTypes.Changed:
|
case WatcherChangeTypes.Changed:
|
||||||
handler = _onChangedHandler;
|
return _onChangedHandler;
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Debug.Fail("Unknown FileSystemEvent change type! Value: " + changeType);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Debug.Fail("Unknown FileSystemEvent change type! Value: " + changeType);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raises the event to each handler in the list.
|
||||||
|
/// </summary>
|
||||||
|
private void NotifyFileSystemEventArgs(WatcherChangeTypes changeType, ReadOnlySpan<char> name)
|
||||||
|
{
|
||||||
|
FileSystemEventHandler handler = GetHandler(changeType);
|
||||||
|
|
||||||
|
if (handler != null && MatchPattern(name.IsEmpty ? _directory : name))
|
||||||
|
{
|
||||||
|
handler(this, new FileSystemEventArgs(changeType, _directory, name.IsEmpty ? null : name.ToString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raises the event to each handler in the list.
|
||||||
|
/// </summary>
|
||||||
|
private void NotifyFileSystemEventArgs(WatcherChangeTypes changeType, string name)
|
||||||
|
{
|
||||||
|
FileSystemEventHandler handler = GetHandler(changeType);
|
||||||
|
|
||||||
if (handler != null && MatchPattern(string.IsNullOrEmpty(name) ? _directory : name))
|
if (handler != null && MatchPattern(string.IsNullOrEmpty(name) ? _directory : name))
|
||||||
{
|
{
|
||||||
handler(this, new FileSystemEventArgs(changeType, _directory, name));
|
handler(this, new FileSystemEventArgs(changeType, _directory, name));
|
||||||
@ -560,9 +574,12 @@ namespace System.IO
|
|||||||
tcs.TrySetResult(new WaitForChangedResult(e.ChangeType, e.Name, oldName: null, timedOut: false));
|
tcs.TrySetResult(new WaitForChangedResult(e.ChangeType, e.Name, oldName: null, timedOut: false));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if ((changeType & WatcherChangeTypes.Created) != 0) Created += fseh;
|
if ((changeType & WatcherChangeTypes.Created) != 0)
|
||||||
if ((changeType & WatcherChangeTypes.Deleted) != 0) Deleted += fseh;
|
Created += fseh;
|
||||||
if ((changeType & WatcherChangeTypes.Changed) != 0) Changed += fseh;
|
if ((changeType & WatcherChangeTypes.Deleted) != 0)
|
||||||
|
Deleted += fseh;
|
||||||
|
if ((changeType & WatcherChangeTypes.Changed) != 0)
|
||||||
|
Changed += fseh;
|
||||||
}
|
}
|
||||||
if ((changeType & WatcherChangeTypes.Renamed) != 0)
|
if ((changeType & WatcherChangeTypes.Renamed) != 0)
|
||||||
{
|
{
|
||||||
@ -600,9 +617,12 @@ namespace System.IO
|
|||||||
}
|
}
|
||||||
if (fseh != null)
|
if (fseh != null)
|
||||||
{
|
{
|
||||||
if ((changeType & WatcherChangeTypes.Changed) != 0) Changed -= fseh;
|
if ((changeType & WatcherChangeTypes.Changed) != 0)
|
||||||
if ((changeType & WatcherChangeTypes.Deleted) != 0) Deleted -= fseh;
|
Changed -= fseh;
|
||||||
if ((changeType & WatcherChangeTypes.Created) != 0) Created -= fseh;
|
if ((changeType & WatcherChangeTypes.Deleted) != 0)
|
||||||
|
Deleted -= fseh;
|
||||||
|
if ((changeType & WatcherChangeTypes.Created) != 0)
|
||||||
|
Created -= fseh;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -656,17 +676,7 @@ namespace System.IO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ISynchronizeInvoke SynchronizingObject
|
public ISynchronizeInvoke SynchronizingObject { get; set; }
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _synchronizingObject;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_synchronizingObject = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void BeginInit()
|
public void BeginInit()
|
||||||
{
|
{
|
||||||
@ -688,5 +698,101 @@ namespace System.IO
|
|||||||
{
|
{
|
||||||
return _initializing || DesignMode;
|
return _initializing || DesignMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class NormalizedFilterCollection : Collection<string>
|
||||||
|
{
|
||||||
|
internal NormalizedFilterCollection() : base(new ImmutableStringList())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void InsertItem(int index, string item)
|
||||||
|
{
|
||||||
|
base.InsertItem(index, string.IsNullOrEmpty(item) || item == "*.*" ? "*" : item);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void SetItem(int index, string item)
|
||||||
|
{
|
||||||
|
base.SetItem(index, string.IsNullOrEmpty(item) || item == "*.*" ? "*" : item);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal string[] GetFilters() => ((ImmutableStringList)Items).Items;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List that maintains its underlying data in an immutable array, such that the list
|
||||||
|
/// will never modify an array returned from its Items property. This is to allow
|
||||||
|
/// the array to be enumerated safely while another thread might be concurrently mutating
|
||||||
|
/// the collection.
|
||||||
|
/// </summary>
|
||||||
|
private sealed class ImmutableStringList : IList<string>
|
||||||
|
{
|
||||||
|
public string[] Items = Array.Empty<string>();
|
||||||
|
|
||||||
|
public string this[int index]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
string[] items = Items;
|
||||||
|
if ((uint)index >= (uint)items.Length)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(index));
|
||||||
|
}
|
||||||
|
return items[index];
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
string[] clone = (string[])Items.Clone();
|
||||||
|
clone[index] = value;
|
||||||
|
Items = clone;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count => Items.Length;
|
||||||
|
|
||||||
|
public bool IsReadOnly => false;
|
||||||
|
|
||||||
|
public void Add(string item)
|
||||||
|
{
|
||||||
|
// Collection<T> doesn't use this method.
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear() => Items = Array.Empty<string>();
|
||||||
|
|
||||||
|
public bool Contains(string item) => Array.IndexOf(Items, item) != -1;
|
||||||
|
|
||||||
|
public void CopyTo(string[] array, int arrayIndex) => Items.CopyTo(array, arrayIndex);
|
||||||
|
|
||||||
|
public IEnumerator<string> GetEnumerator() => ((IEnumerable<string>)Items).GetEnumerator();
|
||||||
|
|
||||||
|
public int IndexOf(string item) => Array.IndexOf(Items, item);
|
||||||
|
|
||||||
|
public void Insert(int index, string item)
|
||||||
|
{
|
||||||
|
string[] items = Items;
|
||||||
|
string[] newItems = new string[items.Length + 1];
|
||||||
|
items.AsSpan(0, index).CopyTo(newItems);
|
||||||
|
items.AsSpan(index).CopyTo(newItems.AsSpan(index + 1));
|
||||||
|
newItems[index] = item;
|
||||||
|
Items = newItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove(string item)
|
||||||
|
{
|
||||||
|
// Collection<T> doesn't use this method.
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveAt(int index)
|
||||||
|
{
|
||||||
|
string[] items = Items;
|
||||||
|
string[] newItems = new string[items.Length - 1];
|
||||||
|
items.AsSpan(0, index).CopyTo(newItems);
|
||||||
|
items.AsSpan(index + 1).CopyTo(newItems.AsSpan(index));
|
||||||
|
Items = newItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -26,6 +26,45 @@ namespace System.IO.Tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[OuterLoop]
|
||||||
|
public void FileSystemWatcher_File_Create_EnablingDisablingNotAffectRaisingEvent()
|
||||||
|
{
|
||||||
|
ExecuteWithRetry(() =>
|
||||||
|
{
|
||||||
|
using (var testDirectory = new TempDirectory(GetTestFilePath()))
|
||||||
|
using (var watcher = new FileSystemWatcher(testDirectory.Path))
|
||||||
|
{
|
||||||
|
string fileName = Path.Combine(testDirectory.Path, "file");
|
||||||
|
watcher.Filter = Path.GetFileName(fileName);
|
||||||
|
|
||||||
|
int numberOfRaisedEvents = 0;
|
||||||
|
AutoResetEvent autoResetEvent = new AutoResetEvent(false);
|
||||||
|
FileSystemEventHandler handler = (o, e) =>
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref numberOfRaisedEvents);
|
||||||
|
autoResetEvent.Set();
|
||||||
|
};
|
||||||
|
|
||||||
|
watcher.Created += handler;
|
||||||
|
|
||||||
|
for (int i = 0; i < 100; i++)
|
||||||
|
{
|
||||||
|
watcher.EnableRaisingEvents = true;
|
||||||
|
watcher.EnableRaisingEvents = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
watcher.EnableRaisingEvents = true;
|
||||||
|
|
||||||
|
// this should raise one and only one event
|
||||||
|
File.Create(fileName).Dispose();
|
||||||
|
Assert.True(autoResetEvent.WaitOne(WaitForExpectedEventTimeout_NoRetry));
|
||||||
|
Assert.False(autoResetEvent.WaitOne(SubsequentExpectedWait));
|
||||||
|
Assert.True(numberOfRaisedEvents == 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void FileSystemWatcher_File_Create_ForcedRestart()
|
public void FileSystemWatcher_File_Create_ForcedRestart()
|
||||||
{
|
{
|
||||||
|
351
external/corefx/src/System.IO.FileSystem.Watcher/tests/FileSystemWatcher.MultipleWatchers.cs
vendored
Normal file
351
external/corefx/src/System.IO.FileSystem.Watcher/tests/FileSystemWatcher.MultipleWatchers.cs
vendored
Normal file
@ -0,0 +1,351 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace System.IO.Tests
|
||||||
|
{
|
||||||
|
public class FileSystemWatcher_Multiple_Test : FileSystemWatcherTest
|
||||||
|
{
|
||||||
|
[SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "#34017")]
|
||||||
|
[OuterLoop]
|
||||||
|
[Fact]
|
||||||
|
public void FileSystemWatcher_File_Create_ExecutionContextFlowed()
|
||||||
|
{
|
||||||
|
ExecuteWithRetry(() =>
|
||||||
|
{
|
||||||
|
using (var watcher1 = new FileSystemWatcher(TestDirectory))
|
||||||
|
using (var watcher2 = new FileSystemWatcher(TestDirectory))
|
||||||
|
{
|
||||||
|
string fileName = Path.Combine(TestDirectory, "file");
|
||||||
|
watcher1.Filter = Path.GetFileName(fileName);
|
||||||
|
watcher2.Filter = Path.GetFileName(fileName);
|
||||||
|
|
||||||
|
var local = new AsyncLocal<int>();
|
||||||
|
|
||||||
|
var tcs1 = new TaskCompletionSource<int>();
|
||||||
|
var tcs2 = new TaskCompletionSource<int>();
|
||||||
|
watcher1.Created += (s, e) => tcs1.SetResult(local.Value);
|
||||||
|
watcher2.Created += (s, e) => tcs2.SetResult(local.Value);
|
||||||
|
|
||||||
|
local.Value = 42;
|
||||||
|
watcher1.EnableRaisingEvents = true;
|
||||||
|
local.Value = 84;
|
||||||
|
watcher2.EnableRaisingEvents = true;
|
||||||
|
local.Value = 168;
|
||||||
|
|
||||||
|
File.Create(fileName).Dispose();
|
||||||
|
Task.WaitAll(new[] { tcs1.Task, tcs2.Task }, WaitForExpectedEventTimeout);
|
||||||
|
|
||||||
|
Assert.Equal(42, tcs1.Task.Result);
|
||||||
|
Assert.Equal(84, tcs2.Task.Result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "#34017")]
|
||||||
|
[OuterLoop]
|
||||||
|
[Fact]
|
||||||
|
public void FileSystemWatcher_File_Create_SuppressedExecutionContextHandled()
|
||||||
|
{
|
||||||
|
ExecuteWithRetry(() =>
|
||||||
|
{
|
||||||
|
using (var watcher1 = new FileSystemWatcher(TestDirectory))
|
||||||
|
{
|
||||||
|
string fileName = Path.Combine(TestDirectory, "file");
|
||||||
|
watcher1.Filter = Path.GetFileName(fileName);
|
||||||
|
|
||||||
|
var local = new AsyncLocal<int>();
|
||||||
|
|
||||||
|
var tcs1 = new TaskCompletionSource<int>();
|
||||||
|
watcher1.Created += (s, e) => tcs1.SetResult(local.Value);
|
||||||
|
|
||||||
|
local.Value = 42;
|
||||||
|
|
||||||
|
ExecutionContext.SuppressFlow();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
watcher1.EnableRaisingEvents = true;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ExecutionContext.RestoreFlow();
|
||||||
|
}
|
||||||
|
|
||||||
|
File.Create(fileName).Dispose();
|
||||||
|
tcs1.Task.Wait(WaitForExpectedEventTimeout);
|
||||||
|
|
||||||
|
Assert.Equal(0, tcs1.Task.Result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[OuterLoop]
|
||||||
|
[Fact]
|
||||||
|
public void FileSystemWatcher_File_Create_NotAffectEachOther()
|
||||||
|
{
|
||||||
|
ExecuteWithRetry(() =>
|
||||||
|
{
|
||||||
|
using (var watcher1 = new FileSystemWatcher(TestDirectory))
|
||||||
|
using (var watcher2 = new FileSystemWatcher(TestDirectory))
|
||||||
|
using (var watcher3 = new FileSystemWatcher(TestDirectory))
|
||||||
|
{
|
||||||
|
string fileName = Path.Combine(TestDirectory, "file");
|
||||||
|
watcher1.Filter = Path.GetFileName(fileName);
|
||||||
|
watcher2.Filter = Path.GetFileName(fileName);
|
||||||
|
watcher3.Filter = Path.GetFileName(fileName);
|
||||||
|
|
||||||
|
AutoResetEvent autoResetEvent1 = WatchCreated(watcher1, new[] { fileName }).EventOccured;
|
||||||
|
AutoResetEvent autoResetEvent2 = WatchCreated(watcher2, new[] { fileName }).EventOccured;
|
||||||
|
AutoResetEvent autoResetEvent3 = WatchCreated(watcher3, new[] { fileName }).EventOccured;
|
||||||
|
|
||||||
|
watcher1.EnableRaisingEvents = true;
|
||||||
|
watcher2.EnableRaisingEvents = true;
|
||||||
|
watcher3.EnableRaisingEvents = true;
|
||||||
|
|
||||||
|
File.Create(fileName).Dispose();
|
||||||
|
Assert.True(WaitHandle.WaitAll(new[] { autoResetEvent1, autoResetEvent2, autoResetEvent3 }, WaitForExpectedEventTimeout_NoRetry));
|
||||||
|
|
||||||
|
File.Delete(fileName);
|
||||||
|
watcher1.EnableRaisingEvents = false;
|
||||||
|
|
||||||
|
File.Create(fileName).Dispose();
|
||||||
|
Assert.False(autoResetEvent1.WaitOne(WaitForUnexpectedEventTimeout));
|
||||||
|
Assert.True(WaitHandle.WaitAll(new[] { autoResetEvent2, autoResetEvent3 }, WaitForExpectedEventTimeout_NoRetry));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[OuterLoop]
|
||||||
|
[Fact]
|
||||||
|
public void FileSystemWatcher_File_Create_WatchOwnPath()
|
||||||
|
{
|
||||||
|
ExecuteWithRetry(() =>
|
||||||
|
{
|
||||||
|
using (var dir = new TempDirectory(GetTestFilePath()))
|
||||||
|
using (var dir1 = new TempDirectory(Path.Combine(dir.Path, "dir1")))
|
||||||
|
using (var dir2 = new TempDirectory(Path.Combine(dir.Path, "dir2")))
|
||||||
|
using (var watcher1 = new FileSystemWatcher(dir1.Path, "*"))
|
||||||
|
using (var watcher2 = new FileSystemWatcher(dir2.Path, "*"))
|
||||||
|
{
|
||||||
|
string fileName1 = Path.Combine(dir1.Path, "file");
|
||||||
|
string fileName2 = Path.Combine(dir2.Path, "file");
|
||||||
|
|
||||||
|
AutoResetEvent autoResetEvent1 = WatchCreated(watcher1, new[] { fileName1 }).EventOccured;
|
||||||
|
AutoResetEvent autoResetEvent2 = WatchCreated(watcher2, new[] { fileName2 }).EventOccured;
|
||||||
|
|
||||||
|
watcher1.EnableRaisingEvents = true;
|
||||||
|
watcher2.EnableRaisingEvents = true;
|
||||||
|
|
||||||
|
File.Create(fileName1).Dispose();
|
||||||
|
Assert.True(autoResetEvent1.WaitOne(WaitForExpectedEventTimeout_NoRetry));
|
||||||
|
Assert.False(autoResetEvent2.WaitOne(WaitForUnexpectedEventTimeout));
|
||||||
|
|
||||||
|
File.Create(fileName2).Dispose();
|
||||||
|
Assert.True(autoResetEvent2.WaitOne(WaitForExpectedEventTimeout_NoRetry));
|
||||||
|
Assert.False(autoResetEvent1.WaitOne(WaitForUnexpectedEventTimeout));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[OuterLoop]
|
||||||
|
[Theory]
|
||||||
|
[InlineData(true)]
|
||||||
|
[InlineData(false)]
|
||||||
|
public void FileSystemWatcher_File_Create_ForceLoopRestart(bool useExistingWatchers)
|
||||||
|
{
|
||||||
|
ExecuteWithRetry(() =>
|
||||||
|
{
|
||||||
|
FileSystemWatcher[] watchers = new FileSystemWatcher[64];
|
||||||
|
FileSystemWatcher[] watchers1 = new FileSystemWatcher[64];
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string fileName = Path.Combine(TestDirectory, "file");
|
||||||
|
AutoResetEvent[] autoResetEvents = new AutoResetEvent[64];
|
||||||
|
for (var i = 0; i < watchers.Length; i++)
|
||||||
|
{
|
||||||
|
watchers[i] = new FileSystemWatcher(TestDirectory);
|
||||||
|
watchers[i].Filter = Path.GetFileName(fileName);
|
||||||
|
autoResetEvents[i] = WatchCreated(watchers[i], new[] { fileName }).EventOccured;
|
||||||
|
watchers[i].EnableRaisingEvents = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
File.Create(fileName).Dispose();
|
||||||
|
Assert.True(WaitHandle.WaitAll(autoResetEvents, WaitForExpectedEventTimeout_NoRetry));
|
||||||
|
|
||||||
|
File.Delete(fileName);
|
||||||
|
for (var i = 0; i < watchers.Length; i++)
|
||||||
|
{
|
||||||
|
watchers[i].EnableRaisingEvents = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
File.Create(fileName).Dispose();
|
||||||
|
Assert.False(WaitHandle.WaitAll(autoResetEvents, WaitForUnexpectedEventTimeout));
|
||||||
|
|
||||||
|
File.Delete(fileName);
|
||||||
|
|
||||||
|
if (useExistingWatchers)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < watchers.Length; i++)
|
||||||
|
{
|
||||||
|
watchers[i].EnableRaisingEvents = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
File.Create(fileName).Dispose();
|
||||||
|
Assert.True(WaitHandle.WaitAll(autoResetEvents, WaitForExpectedEventTimeout_NoRetry));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AutoResetEvent[] autoResetEvents1 = new AutoResetEvent[64];
|
||||||
|
for (var i = 0; i < watchers1.Length; i++)
|
||||||
|
{
|
||||||
|
watchers1[i] = new FileSystemWatcher(TestDirectory);
|
||||||
|
watchers1[i].Filter = Path.GetFileName(fileName);
|
||||||
|
autoResetEvents1[i] = WatchCreated(watchers1[i], new[] { fileName }).EventOccured;
|
||||||
|
watchers1[i].EnableRaisingEvents = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
File.Create(fileName).Dispose();
|
||||||
|
Assert.True(WaitHandle.WaitAll(autoResetEvents1, WaitForExpectedEventTimeout_NoRetry));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
for (var i = 0; i < watchers.Length; i++)
|
||||||
|
{
|
||||||
|
watchers[i]?.Dispose();
|
||||||
|
watchers1[i]?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[OuterLoop]
|
||||||
|
[Fact]
|
||||||
|
public void FileSystemWatcher_File_Changed_NotAffectEachOther()
|
||||||
|
{
|
||||||
|
ExecuteWithRetry(() =>
|
||||||
|
{
|
||||||
|
using (var testDirectory = new TempDirectory(GetTestFilePath()))
|
||||||
|
using (var file = new TempFile(Path.Combine(testDirectory.Path, "file")))
|
||||||
|
using (var otherFile = new TempFile(Path.Combine(testDirectory.Path, "otherfile")))
|
||||||
|
using (var watcher1 = new FileSystemWatcher(testDirectory.Path, Path.GetFileName(file.Path)))
|
||||||
|
using (var watcher2 = new FileSystemWatcher(testDirectory.Path, Path.GetFileName(file.Path)))
|
||||||
|
using (var watcher3 = new FileSystemWatcher(testDirectory.Path, Path.GetFileName(otherFile.Path)))
|
||||||
|
{
|
||||||
|
AutoResetEvent autoResetEvent1 = WatchChanged(watcher1, new[] { Path.Combine(testDirectory.Path, "file") }).EventOccured;
|
||||||
|
AutoResetEvent autoResetEvent2 = WatchChanged(watcher2, new[] { Path.Combine(testDirectory.Path, "file") }).EventOccured;
|
||||||
|
AutoResetEvent autoResetEvent3 = WatchChanged(watcher3, new[] { Path.Combine(testDirectory.Path, "otherfile") }).EventOccured;
|
||||||
|
|
||||||
|
watcher1.EnableRaisingEvents = true;
|
||||||
|
watcher2.EnableRaisingEvents = true;
|
||||||
|
watcher3.EnableRaisingEvents = true;
|
||||||
|
|
||||||
|
Directory.SetLastWriteTime(file.Path, DateTime.Now + TimeSpan.FromSeconds(10));
|
||||||
|
Assert.True(WaitHandle.WaitAll(new[] { autoResetEvent1, autoResetEvent2 }, WaitForExpectedEventTimeout_NoRetry));
|
||||||
|
Assert.False(autoResetEvent3.WaitOne(WaitForUnexpectedEventTimeout));
|
||||||
|
|
||||||
|
Directory.SetLastWriteTime(otherFile.Path, DateTime.Now + TimeSpan.FromSeconds(10));
|
||||||
|
Assert.False(WaitHandle.WaitAll(new[] { autoResetEvent1, autoResetEvent2 }, WaitForUnexpectedEventTimeout));
|
||||||
|
Assert.True(autoResetEvent3.WaitOne(WaitForExpectedEventTimeout_NoRetry));
|
||||||
|
|
||||||
|
watcher1.EnableRaisingEvents = false;
|
||||||
|
|
||||||
|
Directory.SetLastWriteTime(file.Path, DateTime.Now + TimeSpan.FromSeconds(10));
|
||||||
|
Assert.False(WaitHandle.WaitAll(new[] { autoResetEvent1, autoResetEvent3 }, WaitForUnexpectedEventTimeout));
|
||||||
|
Assert.True(autoResetEvent2.WaitOne(WaitForExpectedEventTimeout_NoRetry));
|
||||||
|
|
||||||
|
Directory.SetLastWriteTime(otherFile.Path, DateTime.Now + TimeSpan.FromSeconds(10));
|
||||||
|
Assert.False(WaitHandle.WaitAll(new[] { autoResetEvent1, autoResetEvent2 }, WaitForUnexpectedEventTimeout));
|
||||||
|
Assert.True(autoResetEvent3.WaitOne(WaitForExpectedEventTimeout_NoRetry));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[OuterLoop]
|
||||||
|
[Fact]
|
||||||
|
public void FileSystemWatcher_File_Delet_NotAffectEachOther()
|
||||||
|
{
|
||||||
|
ExecuteWithRetry(() =>
|
||||||
|
{
|
||||||
|
using (var watcher1 = new FileSystemWatcher(TestDirectory))
|
||||||
|
using (var watcher2 = new FileSystemWatcher(TestDirectory))
|
||||||
|
using (var watcher3 = new FileSystemWatcher(TestDirectory))
|
||||||
|
{
|
||||||
|
string fileName = Path.Combine(TestDirectory, "file");
|
||||||
|
File.Create(fileName).Dispose();
|
||||||
|
|
||||||
|
watcher1.Filter = Path.GetFileName(fileName);
|
||||||
|
watcher2.Filter = Path.GetFileName(fileName);
|
||||||
|
watcher3.Filter = Path.GetFileName(fileName);
|
||||||
|
|
||||||
|
AutoResetEvent autoResetEvent1 = WatchDeleted(watcher1, new[] { fileName }).EventOccured;
|
||||||
|
AutoResetEvent autoResetEvent2 = WatchDeleted(watcher2, new[] { fileName }).EventOccured;
|
||||||
|
AutoResetEvent autoResetEvent3 = WatchDeleted(watcher3, new[] { fileName }).EventOccured;
|
||||||
|
|
||||||
|
watcher1.EnableRaisingEvents = true;
|
||||||
|
watcher2.EnableRaisingEvents = true;
|
||||||
|
watcher3.EnableRaisingEvents = true;
|
||||||
|
|
||||||
|
File.Delete(fileName);
|
||||||
|
Assert.True(WaitHandle.WaitAll(new[] { autoResetEvent1, autoResetEvent2, autoResetEvent3 }, WaitForExpectedEventTimeout_NoRetry));
|
||||||
|
|
||||||
|
File.Create(fileName).Dispose();
|
||||||
|
watcher1.EnableRaisingEvents = false;
|
||||||
|
|
||||||
|
File.Delete(fileName);
|
||||||
|
Assert.False(autoResetEvent1.WaitOne(WaitForUnexpectedEventTimeout));
|
||||||
|
Assert.True(WaitHandle.WaitAll(new[] { autoResetEvent2, autoResetEvent3 }, WaitForExpectedEventTimeout_NoRetry));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[OuterLoop]
|
||||||
|
[Fact]
|
||||||
|
[PlatformSpecific(TestPlatforms.OSX)]
|
||||||
|
[SkipOnTargetFramework(TargetFrameworkMonikers.Mono)]
|
||||||
|
public void FileSystemWatcher_File_Rename_NotAffectEachOther()
|
||||||
|
{
|
||||||
|
ExecuteWithRetry(() =>
|
||||||
|
{
|
||||||
|
using (var testDirectory = new TempDirectory(GetTestFilePath()))
|
||||||
|
using (var file = new TempFile(Path.Combine(testDirectory.Path, "file")))
|
||||||
|
using (var watcher1 = new FileSystemWatcher(testDirectory.Path, Path.GetFileName(file.Path)))
|
||||||
|
using (var watcher2 = new FileSystemWatcher(testDirectory.Path, Path.GetFileName(file.Path)))
|
||||||
|
{
|
||||||
|
AutoResetEvent autoResetEvent1_created = WatchCreated(watcher1, new[] { Path.Combine(testDirectory.Path, "file") }).EventOccured;
|
||||||
|
AutoResetEvent autoResetEvent1_deleted = WatchDeleted(watcher1, new[] { Path.Combine(testDirectory.Path, "file") }).EventOccured;
|
||||||
|
AutoResetEvent autoResetEvent2_created = WatchCreated(watcher2, new[] { Path.Combine(testDirectory.Path, "file") }).EventOccured;
|
||||||
|
AutoResetEvent autoResetEvent2_deleted = WatchDeleted(watcher2, new[] { Path.Combine(testDirectory.Path, "file") }).EventOccured;
|
||||||
|
|
||||||
|
watcher1.EnableRaisingEvents = true;
|
||||||
|
watcher2.EnableRaisingEvents = true;
|
||||||
|
|
||||||
|
string filePath = file.Path;
|
||||||
|
string filePathRenamed = file.Path + "_renamed";
|
||||||
|
|
||||||
|
File.Move(filePath, filePathRenamed);
|
||||||
|
Assert.True(WaitHandle.WaitAll(
|
||||||
|
new[] { autoResetEvent1_created, autoResetEvent1_deleted, autoResetEvent2_created, autoResetEvent2_deleted },
|
||||||
|
WaitForExpectedEventTimeout_NoRetry));
|
||||||
|
|
||||||
|
File.Move(filePathRenamed, filePath);
|
||||||
|
watcher1.EnableRaisingEvents = false;
|
||||||
|
|
||||||
|
File.Move(filePath, filePathRenamed);
|
||||||
|
Assert.False(WaitHandle.WaitAll(
|
||||||
|
new[] { autoResetEvent1_created, autoResetEvent1_deleted },
|
||||||
|
WaitForUnexpectedEventTimeout));
|
||||||
|
Assert.True(WaitHandle.WaitAll(
|
||||||
|
new[] { autoResetEvent2_created, autoResetEvent2_deleted },
|
||||||
|
WaitForExpectedEventTimeout_NoRetry));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -474,7 +474,7 @@ namespace System.IO.Tests
|
|||||||
using (var file = new TempFile(Path.Combine(dir.Path, "file")))
|
using (var file = new TempFile(Path.Combine(dir.Path, "file")))
|
||||||
using (var fsw = new FileSystemWatcher(dir.Path))
|
using (var fsw = new FileSystemWatcher(dir.Path))
|
||||||
{
|
{
|
||||||
AutoResetEvent eventOccurred = WatchRenamed(fsw);
|
AutoResetEvent eventOccurred = WatchRenamed(fsw).EventOccured;
|
||||||
|
|
||||||
string newPath = Path.Combine(dir.Path, "newPath");
|
string newPath = Path.Combine(dir.Path, "newPath");
|
||||||
|
|
||||||
@ -626,7 +626,7 @@ namespace System.IO.Tests
|
|||||||
using (var dir = new TempDirectory(Path.Combine(testDirectory.Path, "dir")))
|
using (var dir = new TempDirectory(Path.Combine(testDirectory.Path, "dir")))
|
||||||
using (var fsw = new FileSystemWatcher(dir.Path))
|
using (var fsw = new FileSystemWatcher(dir.Path))
|
||||||
{
|
{
|
||||||
AutoResetEvent are = WatchCreated(fsw);
|
AutoResetEvent are = WatchCreated(fsw).EventOccured;
|
||||||
|
|
||||||
fsw.Filter = "*";
|
fsw.Filter = "*";
|
||||||
fsw.EnableRaisingEvents = true;
|
fsw.EnableRaisingEvents = true;
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
<Compile Include="FileSystemWatcher.File.Move.cs" />
|
<Compile Include="FileSystemWatcher.File.Move.cs" />
|
||||||
<Compile Include="FileSystemWatcher.File.NotifyFilter.cs" />
|
<Compile Include="FileSystemWatcher.File.NotifyFilter.cs" />
|
||||||
<Compile Include="FileSystemWatcher.InternalBufferSize.cs" />
|
<Compile Include="FileSystemWatcher.InternalBufferSize.cs" />
|
||||||
|
<Compile Include="FileSystemWatcher.MultipleWatchers.cs" />
|
||||||
<Compile Include="FileSystemWatcher.WaitForChanged.cs" />
|
<Compile Include="FileSystemWatcher.WaitForChanged.cs" />
|
||||||
<Compile Include="FileSystemWatcher.unit.cs" />
|
<Compile Include="FileSystemWatcher.unit.cs" />
|
||||||
<!-- Helpers -->
|
<!-- Helpers -->
|
||||||
|
@ -4,14 +4,14 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
using Xunit.Sdk;
|
||||||
|
|
||||||
namespace System.IO.Tests
|
namespace System.IO.Tests
|
||||||
{
|
{
|
||||||
public abstract class FileSystemWatcherTest : FileCleanupTestBase
|
public abstract partial class FileSystemWatcherTest : FileCleanupTestBase
|
||||||
{
|
{
|
||||||
// Events are reported asynchronously by the OS, so allow an amount of time for
|
// Events are reported asynchronously by the OS, so allow an amount of time for
|
||||||
// them to arrive before testing an assertion. If we expect an event to occur,
|
// them to arrive before testing an assertion. If we expect an event to occur,
|
||||||
@ -23,18 +23,20 @@ namespace System.IO.Tests
|
|||||||
public const int LongWaitTimeout = 50000; // ms to wait for an event that takes a longer time than the average operation
|
public const int LongWaitTimeout = 50000; // ms to wait for an event that takes a longer time than the average operation
|
||||||
public const int SubsequentExpectedWait = 10; // ms to wait for checks that occur after the first.
|
public const int SubsequentExpectedWait = 10; // ms to wait for checks that occur after the first.
|
||||||
public const int WaitForExpectedEventTimeout_NoRetry = 3000;// ms to wait for an event that isn't surrounded by a retry.
|
public const int WaitForExpectedEventTimeout_NoRetry = 3000;// ms to wait for an event that isn't surrounded by a retry.
|
||||||
|
public const int WaitForUnexpectedEventTimeout = 150; // ms to wait for a non-expected event.
|
||||||
public const int DefaultAttemptsForExpectedEvent = 3; // Number of times an expected event should be retried if failing.
|
public const int DefaultAttemptsForExpectedEvent = 3; // Number of times an expected event should be retried if failing.
|
||||||
public const int DefaultAttemptsForUnExpectedEvent = 2; // Number of times an unexpected event should be retried if failing.
|
public const int DefaultAttemptsForUnExpectedEvent = 2; // Number of times an unexpected event should be retried if failing.
|
||||||
|
public const int RetryDelayMilliseconds = 500; // ms to wait when retrying after failure
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Watches the Changed WatcherChangeType and unblocks the returned AutoResetEvent when a
|
/// Watches the Changed WatcherChangeType and unblocks the returned AutoResetEvent when a
|
||||||
/// Changed event is thrown by the watcher.
|
/// Changed event is thrown by the watcher.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static AutoResetEvent WatchChanged(FileSystemWatcher watcher, string[] expectedPaths = null)
|
public static (AutoResetEvent EventOccured, FileSystemEventHandler Handler) WatchChanged(FileSystemWatcher watcher, string[] expectedPaths = null)
|
||||||
{
|
{
|
||||||
AutoResetEvent eventOccurred = new AutoResetEvent(false);
|
AutoResetEvent eventOccurred = new AutoResetEvent(false);
|
||||||
|
|
||||||
watcher.Changed += (o, e) =>
|
FileSystemEventHandler changeHandler = (o, e) =>
|
||||||
{
|
{
|
||||||
Assert.Equal(WatcherChangeTypes.Changed, e.ChangeType);
|
Assert.Equal(WatcherChangeTypes.Changed, e.ChangeType);
|
||||||
if (expectedPaths != null)
|
if (expectedPaths != null)
|
||||||
@ -44,18 +46,19 @@ namespace System.IO.Tests
|
|||||||
eventOccurred.Set();
|
eventOccurred.Set();
|
||||||
};
|
};
|
||||||
|
|
||||||
return eventOccurred;
|
watcher.Changed += changeHandler;
|
||||||
|
return (eventOccurred, changeHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Watches the Created WatcherChangeType and unblocks the returned AutoResetEvent when a
|
/// Watches the Created WatcherChangeType and unblocks the returned AutoResetEvent when a
|
||||||
/// Created event is thrown by the watcher.
|
/// Created event is thrown by the watcher.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static AutoResetEvent WatchCreated(FileSystemWatcher watcher, string[] expectedPaths = null)
|
public static (AutoResetEvent EventOccured, FileSystemEventHandler Handler) WatchCreated(FileSystemWatcher watcher, string[] expectedPaths = null)
|
||||||
{
|
{
|
||||||
AutoResetEvent eventOccurred = new AutoResetEvent(false);
|
AutoResetEvent eventOccurred = new AutoResetEvent(false);
|
||||||
|
|
||||||
watcher.Created += (o, e) =>
|
FileSystemEventHandler handler = (o, e) =>
|
||||||
{
|
{
|
||||||
Assert.Equal(WatcherChangeTypes.Created, e.ChangeType);
|
Assert.Equal(WatcherChangeTypes.Created, e.ChangeType);
|
||||||
if (expectedPaths != null)
|
if (expectedPaths != null)
|
||||||
@ -65,18 +68,18 @@ namespace System.IO.Tests
|
|||||||
eventOccurred.Set();
|
eventOccurred.Set();
|
||||||
};
|
};
|
||||||
|
|
||||||
return eventOccurred;
|
watcher.Created += handler;
|
||||||
|
return (eventOccurred, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Watches the Renamed WatcherChangeType and unblocks the returned AutoResetEvent when a
|
/// Watches the Renamed WatcherChangeType and unblocks the returned AutoResetEvent when a
|
||||||
/// Renamed event is thrown by the watcher.
|
/// Renamed event is thrown by the watcher.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static AutoResetEvent WatchDeleted(FileSystemWatcher watcher, string[] expectedPaths = null)
|
public static (AutoResetEvent EventOccured, FileSystemEventHandler Handler) WatchDeleted(FileSystemWatcher watcher, string[] expectedPaths = null)
|
||||||
{
|
{
|
||||||
AutoResetEvent eventOccurred = new AutoResetEvent(false);
|
AutoResetEvent eventOccurred = new AutoResetEvent(false);
|
||||||
|
FileSystemEventHandler handler = (o, e) =>
|
||||||
watcher.Deleted += (o, e) =>
|
|
||||||
{
|
{
|
||||||
Assert.Equal(WatcherChangeTypes.Deleted, e.ChangeType);
|
Assert.Equal(WatcherChangeTypes.Deleted, e.ChangeType);
|
||||||
if (expectedPaths != null)
|
if (expectedPaths != null)
|
||||||
@ -86,18 +89,19 @@ namespace System.IO.Tests
|
|||||||
eventOccurred.Set();
|
eventOccurred.Set();
|
||||||
};
|
};
|
||||||
|
|
||||||
return eventOccurred;
|
watcher.Deleted += handler;
|
||||||
|
return (eventOccurred, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Watches the Renamed WatcherChangeType and unblocks the returned AutoResetEvent when a
|
/// Watches the Renamed WatcherChangeType and unblocks the returned AutoResetEvent when a
|
||||||
/// Renamed event is thrown by the watcher.
|
/// Renamed event is thrown by the watcher.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static AutoResetEvent WatchRenamed(FileSystemWatcher watcher, string[] expectedPaths = null)
|
public static (AutoResetEvent EventOccured, RenamedEventHandler Handler) WatchRenamed(FileSystemWatcher watcher, string[] expectedPaths = null)
|
||||||
{
|
{
|
||||||
AutoResetEvent eventOccurred = new AutoResetEvent(false);
|
AutoResetEvent eventOccurred = new AutoResetEvent(false);
|
||||||
|
|
||||||
watcher.Renamed += (o, e) =>
|
RenamedEventHandler handler = (o, e) =>
|
||||||
{
|
{
|
||||||
Assert.Equal(WatcherChangeTypes.Renamed, e.ChangeType);
|
Assert.Equal(WatcherChangeTypes.Renamed, e.ChangeType);
|
||||||
if (expectedPaths != null)
|
if (expectedPaths != null)
|
||||||
@ -107,7 +111,8 @@ namespace System.IO.Tests
|
|||||||
eventOccurred.Set();
|
eventOccurred.Set();
|
||||||
};
|
};
|
||||||
|
|
||||||
return eventOccurred;
|
watcher.Renamed += handler;
|
||||||
|
return (eventOccurred, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -115,7 +120,7 @@ namespace System.IO.Tests
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static void ExpectEvent(WaitHandle eventOccurred, string eventName_NoRetry)
|
public static void ExpectEvent(WaitHandle eventOccurred, string eventName_NoRetry)
|
||||||
{
|
{
|
||||||
string message = String.Format("Didn't observe a {0} event within {1}ms", eventName_NoRetry, WaitForExpectedEventTimeout_NoRetry);
|
string message = string.Format("Didn't observe a {0} event within {1}ms", eventName_NoRetry, WaitForExpectedEventTimeout_NoRetry);
|
||||||
Assert.True(eventOccurred.WaitOne(WaitForExpectedEventTimeout_NoRetry), message);
|
Assert.True(eventOccurred.WaitOne(WaitForExpectedEventTimeout_NoRetry), message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,32 +166,63 @@ namespace System.IO.Tests
|
|||||||
{
|
{
|
||||||
int attemptsCompleted = 0;
|
int attemptsCompleted = 0;
|
||||||
bool result = false;
|
bool result = false;
|
||||||
|
FileSystemWatcher newWatcher = watcher;
|
||||||
while (!result && attemptsCompleted++ < attempts)
|
while (!result && attemptsCompleted++ < attempts)
|
||||||
{
|
{
|
||||||
if (attemptsCompleted > 1)
|
if (attemptsCompleted > 1)
|
||||||
{
|
{
|
||||||
// Re-create the watcher to get a clean iteration.
|
// Re-create the watcher to get a clean iteration.
|
||||||
watcher = new FileSystemWatcher()
|
newWatcher = RecreateWatcher(newWatcher);
|
||||||
{
|
|
||||||
IncludeSubdirectories = watcher.IncludeSubdirectories,
|
|
||||||
NotifyFilter = watcher.NotifyFilter,
|
|
||||||
Filter = watcher.Filter,
|
|
||||||
Path = watcher.Path,
|
|
||||||
InternalBufferSize = watcher.InternalBufferSize
|
|
||||||
};
|
|
||||||
// Most intermittent failures in FSW are caused by either a shortage of resources (e.g. inotify instances)
|
// Most intermittent failures in FSW are caused by either a shortage of resources (e.g. inotify instances)
|
||||||
// or by insufficient time to execute (e.g. CI gets bogged down). Immediately re-running a failed test
|
// or by insufficient time to execute (e.g. CI gets bogged down). Immediately re-running a failed test
|
||||||
// won't resolve the first issue, so we wait a little while hoping that things clear up for the next run.
|
// won't resolve the first issue, so we wait a little while hoping that things clear up for the next run.
|
||||||
Thread.Sleep(500);
|
Thread.Sleep(RetryDelayMilliseconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
result = ExecuteAndVerifyEvents(watcher, expectedEvents, action, attemptsCompleted == attempts, expectedPaths, timeout);
|
result = ExecuteAndVerifyEvents(newWatcher, expectedEvents, action, attemptsCompleted == attempts, expectedPaths, timeout);
|
||||||
|
|
||||||
if (cleanup != null)
|
if (cleanup != null)
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Invokes the specified test action with retry on failure (other than assertion failure).</summary>
|
||||||
|
/// <param name="action">The test action.</param>
|
||||||
|
/// <param name="maxAttempts">The maximum number of times to attempt to run the test.</param>
|
||||||
|
public static void ExecuteWithRetry(Action action, int maxAttempts = DefaultAttemptsForExpectedEvent)
|
||||||
|
{
|
||||||
|
for (int retry = 0; retry < maxAttempts; retry++)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
action();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (Exception e) when (!(e is XunitException) && retry < maxAttempts - 1)
|
||||||
|
{
|
||||||
|
Thread.Sleep(RetryDelayMilliseconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Does verification that the given watcher will not throw exactly/only the events in "expectedEvents" when
|
||||||
|
/// "action" is executed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="watcher">The FileSystemWatcher to test</param>
|
||||||
|
/// <param name="unExpectedEvents">All of the events that are expected to be raised by this action</param>
|
||||||
|
/// <param name="action">The Action that will trigger events.</param>
|
||||||
|
/// <param name="cleanup">Optional. Undoes the action and cleans up the watcher so the test may be run again if necessary.</param>
|
||||||
|
/// <param name="expectedPath">Optional. Adds path verification to all expected events.</param>
|
||||||
|
public static void ExpectNoEvent(FileSystemWatcher watcher, WatcherChangeTypes unExpectedEvents, Action action, Action cleanup = null, string expectedPath = null, int timeout = WaitForExpectedEventTimeout)
|
||||||
|
{
|
||||||
|
bool result = ExecuteAndVerifyEvents(watcher, unExpectedEvents, action, false, new string[] { expectedPath }, timeout);
|
||||||
|
Assert.False(result, "Expected Event occured");
|
||||||
|
|
||||||
|
if (cleanup != null)
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Helper for the ExpectEvent function.
|
/// Helper for the ExpectEvent function.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -199,8 +235,8 @@ namespace System.IO.Tests
|
|||||||
public static bool ExecuteAndVerifyEvents(FileSystemWatcher watcher, WatcherChangeTypes expectedEvents, Action action, bool assertExpected, string[] expectedPaths, int timeout)
|
public static bool ExecuteAndVerifyEvents(FileSystemWatcher watcher, WatcherChangeTypes expectedEvents, Action action, bool assertExpected, string[] expectedPaths, int timeout)
|
||||||
{
|
{
|
||||||
bool result = true, verifyChanged = true, verifyCreated = true, verifyDeleted = true, verifyRenamed = true;
|
bool result = true, verifyChanged = true, verifyCreated = true, verifyDeleted = true, verifyRenamed = true;
|
||||||
AutoResetEvent changed = null, created = null, deleted = null, renamed = null;
|
(AutoResetEvent EventOccured, FileSystemEventHandler Handler) changed = default, created = default, deleted = default;
|
||||||
string[] expectedFullPaths = expectedPaths == null ? null : expectedPaths.Select(e => Path.GetFullPath(e)).ToArray();
|
(AutoResetEvent EventOccured, RenamedEventHandler Handler) renamed = default;
|
||||||
|
|
||||||
if (verifyChanged = ((expectedEvents & WatcherChangeTypes.Changed) > 0))
|
if (verifyChanged = ((expectedEvents & WatcherChangeTypes.Changed) > 0))
|
||||||
changed = WatchChanged(watcher, expectedPaths);
|
changed = WatchChanged(watcher, expectedPaths);
|
||||||
@ -218,7 +254,8 @@ namespace System.IO.Tests
|
|||||||
if (verifyChanged)
|
if (verifyChanged)
|
||||||
{
|
{
|
||||||
bool Changed_expected = ((expectedEvents & WatcherChangeTypes.Changed) > 0);
|
bool Changed_expected = ((expectedEvents & WatcherChangeTypes.Changed) > 0);
|
||||||
bool Changed_actual = changed.WaitOne(timeout);
|
bool Changed_actual = changed.EventOccured.WaitOne(timeout);
|
||||||
|
watcher.Changed -= changed.Handler;
|
||||||
result = Changed_expected == Changed_actual;
|
result = Changed_expected == Changed_actual;
|
||||||
if (assertExpected)
|
if (assertExpected)
|
||||||
Assert.True(Changed_expected == Changed_actual, "Changed event did not occur as expected");
|
Assert.True(Changed_expected == Changed_actual, "Changed event did not occur as expected");
|
||||||
@ -228,7 +265,8 @@ namespace System.IO.Tests
|
|||||||
if (verifyCreated)
|
if (verifyCreated)
|
||||||
{
|
{
|
||||||
bool Created_expected = ((expectedEvents & WatcherChangeTypes.Created) > 0);
|
bool Created_expected = ((expectedEvents & WatcherChangeTypes.Created) > 0);
|
||||||
bool Created_actual = created.WaitOne(verifyChanged ? SubsequentExpectedWait : timeout);
|
bool Created_actual = created.EventOccured.WaitOne(verifyChanged ? SubsequentExpectedWait : timeout);
|
||||||
|
watcher.Created -= created.Handler;
|
||||||
result = result && Created_expected == Created_actual;
|
result = result && Created_expected == Created_actual;
|
||||||
if (assertExpected)
|
if (assertExpected)
|
||||||
Assert.True(Created_expected == Created_actual, "Created event did not occur as expected");
|
Assert.True(Created_expected == Created_actual, "Created event did not occur as expected");
|
||||||
@ -238,7 +276,8 @@ namespace System.IO.Tests
|
|||||||
if (verifyDeleted)
|
if (verifyDeleted)
|
||||||
{
|
{
|
||||||
bool Deleted_expected = ((expectedEvents & WatcherChangeTypes.Deleted) > 0);
|
bool Deleted_expected = ((expectedEvents & WatcherChangeTypes.Deleted) > 0);
|
||||||
bool Deleted_actual = deleted.WaitOne(verifyChanged || verifyCreated ? SubsequentExpectedWait : timeout);
|
bool Deleted_actual = deleted.EventOccured.WaitOne(verifyChanged || verifyCreated ? SubsequentExpectedWait : timeout);
|
||||||
|
watcher.Deleted -= deleted.Handler;
|
||||||
result = result && Deleted_expected == Deleted_actual;
|
result = result && Deleted_expected == Deleted_actual;
|
||||||
if (assertExpected)
|
if (assertExpected)
|
||||||
Assert.True(Deleted_expected == Deleted_actual, "Deleted event did not occur as expected");
|
Assert.True(Deleted_expected == Deleted_actual, "Deleted event did not occur as expected");
|
||||||
@ -248,7 +287,8 @@ namespace System.IO.Tests
|
|||||||
if (verifyRenamed)
|
if (verifyRenamed)
|
||||||
{
|
{
|
||||||
bool Renamed_expected = ((expectedEvents & WatcherChangeTypes.Renamed) > 0);
|
bool Renamed_expected = ((expectedEvents & WatcherChangeTypes.Renamed) > 0);
|
||||||
bool Renamed_actual = renamed.WaitOne(verifyChanged || verifyCreated || verifyDeleted? SubsequentExpectedWait : timeout);
|
bool Renamed_actual = renamed.EventOccured.WaitOne(verifyChanged || verifyCreated || verifyDeleted ? SubsequentExpectedWait : timeout);
|
||||||
|
watcher.Renamed -= renamed.Handler;
|
||||||
result = result && Renamed_expected == Renamed_actual;
|
result = result && Renamed_expected == Renamed_actual;
|
||||||
if (assertExpected)
|
if (assertExpected)
|
||||||
Assert.True(Renamed_expected == Renamed_actual, "Renamed event did not occur as expected");
|
Assert.True(Renamed_expected == Renamed_actual, "Renamed event did not occur as expected");
|
||||||
@ -409,7 +449,7 @@ namespace System.IO.Tests
|
|||||||
foreach (NotifyFilters filter in Enum.GetValues(typeof(NotifyFilters)))
|
foreach (NotifyFilters filter in Enum.GetValues(typeof(NotifyFilters)))
|
||||||
yield return new object[] { filter };
|
yield return new object[] { filter };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Linux and OSX systems have less precise filtering systems than Windows, so most
|
// Linux and OSX systems have less precise filtering systems than Windows, so most
|
||||||
// metadata filters are effectively equivalent to each other on those systems. For example
|
// metadata filters are effectively equivalent to each other on those systems. For example
|
||||||
// there isn't a way to filter only LastWrite events on either system; setting
|
// there isn't a way to filter only LastWrite events on either system; setting
|
||||||
@ -430,5 +470,19 @@ namespace System.IO.Tests
|
|||||||
NotifyFilters.LastAccess |
|
NotifyFilters.LastAccess |
|
||||||
NotifyFilters.LastWrite |
|
NotifyFilters.LastWrite |
|
||||||
NotifyFilters.Size;
|
NotifyFilters.Size;
|
||||||
|
|
||||||
|
#if MONO
|
||||||
|
private static FileSystemWatcher RecreateWatcher(FileSystemWatcher watcher)
|
||||||
|
{
|
||||||
|
FileSystemWatcher newWatcher = new FileSystemWatcher()
|
||||||
|
{
|
||||||
|
IncludeSubdirectories = watcher.IncludeSubdirectories,
|
||||||
|
NotifyFilter = watcher.NotifyFilter,
|
||||||
|
Path = watcher.Path,
|
||||||
|
InternalBufferSize = watcher.InternalBufferSize
|
||||||
|
};
|
||||||
|
return newWatcher;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -397,27 +397,31 @@ namespace System.IO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool DirectoryExists(string fullPath)
|
public static bool DirectoryExists(ReadOnlySpan<char> fullPath)
|
||||||
{
|
{
|
||||||
Interop.ErrorInfo ignored;
|
Interop.ErrorInfo ignored;
|
||||||
return DirectoryExists(fullPath, out ignored);
|
return DirectoryExists(fullPath, out ignored);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool DirectoryExists(string fullPath, out Interop.ErrorInfo errorInfo)
|
private static bool DirectoryExists(ReadOnlySpan<char> fullPath, out Interop.ErrorInfo errorInfo)
|
||||||
{
|
{
|
||||||
return FileExists(fullPath, Interop.Sys.FileTypes.S_IFDIR, out errorInfo);
|
return FileExists(fullPath, Interop.Sys.FileTypes.S_IFDIR, out errorInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool FileExists(string fullPath)
|
public static bool FileExists(ReadOnlySpan<char> fullPath)
|
||||||
{
|
{
|
||||||
Interop.ErrorInfo ignored;
|
Interop.ErrorInfo ignored;
|
||||||
|
// File.Exists() explicitly checks for a trailing separator and returns false if found. FileInfo.Exists and all other
|
||||||
// Input allows trailing separators in order to match Windows behavior
|
// internal usages do not check for the trailing separator. Historically we've always removed the trailing separator
|
||||||
// Unix does not accept trailing separators, so must be trimmed
|
// when getting attributes as trailing separators are generally not accepted by Windows APIs. Unix will take
|
||||||
|
// trailing separators, but it infers that the path must be a directory (it effectively appends "."). To align with
|
||||||
|
// our historical behavior (outside of File.Exists()), we need to trim.
|
||||||
|
//
|
||||||
|
// See http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap04.html#tag_04_11 for details.
|
||||||
return FileExists(PathInternal.TrimEndingDirectorySeparator(fullPath), Interop.Sys.FileTypes.S_IFREG, out ignored);
|
return FileExists(PathInternal.TrimEndingDirectorySeparator(fullPath), Interop.Sys.FileTypes.S_IFREG, out ignored);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool FileExists(string fullPath, int fileType, out Interop.ErrorInfo errorInfo)
|
private static bool FileExists(ReadOnlySpan<char> fullPath, int fileType, out Interop.ErrorInfo errorInfo)
|
||||||
{
|
{
|
||||||
Debug.Assert(fileType == Interop.Sys.FileTypes.S_IFREG || fileType == Interop.Sys.FileTypes.S_IFDIR);
|
Debug.Assert(fileType == Interop.Sys.FileTypes.S_IFREG || fileType == Interop.Sys.FileTypes.S_IFDIR);
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ static class Consts
|
|||||||
// Use these assembly version constants to make code more maintainable.
|
// Use these assembly version constants to make code more maintainable.
|
||||||
//
|
//
|
||||||
|
|
||||||
public const string MonoVersion = "5.18.0.237";
|
public const string MonoVersion = "5.18.0.239";
|
||||||
public const string MonoCompany = "Mono development team";
|
public const string MonoCompany = "Mono development team";
|
||||||
public const string MonoProduct = "Mono Common Language Infrastructure";
|
public const string MonoProduct = "Mono Common Language Infrastructure";
|
||||||
public const string MonoCopyright = "(c) Various Mono authors";
|
public const string MonoCopyright = "(c) Various Mono authors";
|
||||||
|
@ -1 +1 @@
|
|||||||
908cd04819fde0fa5b49a8179b597c3a66e22c4e
|
6906969e1560c229aa270d601a88da36d3b87383
|
@ -1 +1 @@
|
|||||||
7cfb76b39793a684c9a17a0405973089bcd5f954
|
8260010ca66cf7bf43ddbb1ae0b222492747167a
|
@ -1 +1 @@
|
|||||||
c95ba4d04ef7f337af46d383ede8538bd5035b34
|
2d5243eb818f2ae2bd193faffdc1cd4ea4700dd7
|
Binary file not shown.
@ -1 +1 @@
|
|||||||
e98e0d83cabf1f38615315bb2b7df51f73adea61
|
3883499a603ad6ea46014f6bb783606055560eda
|
@ -1 +1 @@
|
|||||||
0a80a6acb8e70f48b3835bbd0108b9121f32dc6d
|
812d2587a089ec5a9f3a2433ebb0b07215307bd5
|
@ -1 +1 @@
|
|||||||
4b0c2992c734491de65ff79d23ecc6b961c42609
|
07c6863ea81f0ef67612f4209bc0dda817402417
|
@ -1 +1 @@
|
|||||||
74d8a027616997fa1fe916cb9eb486f73393f9c7
|
8f029d8d6f60059315f0005365154e32d83d659e
|
@ -1 +1 @@
|
|||||||
3d2eee5927931b3874594e1a689fd92b3792653e
|
a0c4a95a9d6ac6211cff450c608792d78d313b7e
|
@ -1 +1 @@
|
|||||||
908cd04819fde0fa5b49a8179b597c3a66e22c4e
|
6906969e1560c229aa270d601a88da36d3b87383
|
@ -1 +1 @@
|
|||||||
7cfb76b39793a684c9a17a0405973089bcd5f954
|
8260010ca66cf7bf43ddbb1ae0b222492747167a
|
@ -1 +1 @@
|
|||||||
c95ba4d04ef7f337af46d383ede8538bd5035b34
|
2d5243eb818f2ae2bd193faffdc1cd4ea4700dd7
|
Binary file not shown.
@ -1 +1 @@
|
|||||||
e98e0d83cabf1f38615315bb2b7df51f73adea61
|
3883499a603ad6ea46014f6bb783606055560eda
|
@ -1 +1 @@
|
|||||||
0a80a6acb8e70f48b3835bbd0108b9121f32dc6d
|
812d2587a089ec5a9f3a2433ebb0b07215307bd5
|
@ -1 +1 @@
|
|||||||
4b0c2992c734491de65ff79d23ecc6b961c42609
|
07c6863ea81f0ef67612f4209bc0dda817402417
|
@ -1 +1 @@
|
|||||||
74d8a027616997fa1fe916cb9eb486f73393f9c7
|
8f029d8d6f60059315f0005365154e32d83d659e
|
@ -1 +1 @@
|
|||||||
3d2eee5927931b3874594e1a689fd92b3792653e
|
a0c4a95a9d6ac6211cff450c608792d78d313b7e
|
@ -1 +1 @@
|
|||||||
908cd04819fde0fa5b49a8179b597c3a66e22c4e
|
6906969e1560c229aa270d601a88da36d3b87383
|
@ -1 +1 @@
|
|||||||
7cfb76b39793a684c9a17a0405973089bcd5f954
|
8260010ca66cf7bf43ddbb1ae0b222492747167a
|
@ -1 +1 @@
|
|||||||
c95ba4d04ef7f337af46d383ede8538bd5035b34
|
2d5243eb818f2ae2bd193faffdc1cd4ea4700dd7
|
Binary file not shown.
@ -1 +1 @@
|
|||||||
e98e0d83cabf1f38615315bb2b7df51f73adea61
|
3883499a603ad6ea46014f6bb783606055560eda
|
@ -1 +1 @@
|
|||||||
0a80a6acb8e70f48b3835bbd0108b9121f32dc6d
|
812d2587a089ec5a9f3a2433ebb0b07215307bd5
|
@ -1 +1 @@
|
|||||||
4b0c2992c734491de65ff79d23ecc6b961c42609
|
07c6863ea81f0ef67612f4209bc0dda817402417
|
@ -1 +1 @@
|
|||||||
74d8a027616997fa1fe916cb9eb486f73393f9c7
|
8f029d8d6f60059315f0005365154e32d83d659e
|
@ -1 +1 @@
|
|||||||
3d2eee5927931b3874594e1a689fd92b3792653e
|
a0c4a95a9d6ac6211cff450c608792d78d313b7e
|
@ -1 +1 @@
|
|||||||
908cd04819fde0fa5b49a8179b597c3a66e22c4e
|
6906969e1560c229aa270d601a88da36d3b87383
|
@ -1 +1 @@
|
|||||||
7cfb76b39793a684c9a17a0405973089bcd5f954
|
8260010ca66cf7bf43ddbb1ae0b222492747167a
|
@ -1 +1 @@
|
|||||||
c95ba4d04ef7f337af46d383ede8538bd5035b34
|
2d5243eb818f2ae2bd193faffdc1cd4ea4700dd7
|
Binary file not shown.
@ -1 +1 @@
|
|||||||
e98e0d83cabf1f38615315bb2b7df51f73adea61
|
3883499a603ad6ea46014f6bb783606055560eda
|
@ -1 +1 @@
|
|||||||
0a80a6acb8e70f48b3835bbd0108b9121f32dc6d
|
812d2587a089ec5a9f3a2433ebb0b07215307bd5
|
@ -1 +1 @@
|
|||||||
4b0c2992c734491de65ff79d23ecc6b961c42609
|
07c6863ea81f0ef67612f4209bc0dda817402417
|
@ -1 +1 @@
|
|||||||
74d8a027616997fa1fe916cb9eb486f73393f9c7
|
8f029d8d6f60059315f0005365154e32d83d659e
|
@ -1 +1 @@
|
|||||||
3d2eee5927931b3874594e1a689fd92b3792653e
|
a0c4a95a9d6ac6211cff450c608792d78d313b7e
|
@ -1 +1 @@
|
|||||||
#define FULL_VERSION "explicit/51c4f45"
|
#define FULL_VERSION "explicit/0d988bc"
|
||||||
|
BIN
po/mcs/de.gmo
BIN
po/mcs/de.gmo
Binary file not shown.
@ -1 +1 @@
|
|||||||
a65e7b782001b86d8225d04fa68512a5bc44c967
|
18b1807a4d1acdb4c1525b1b58f8ee1c7180f20a
|
BIN
po/mcs/es.gmo
BIN
po/mcs/es.gmo
Binary file not shown.
@ -1 +1 @@
|
|||||||
fd39dee88556b3baad4dbd99270795579407ef77
|
6e89df71c925ed8a137134b58c22502325be917c
|
BIN
po/mcs/ja.gmo
BIN
po/mcs/ja.gmo
Binary file not shown.
@ -1 +1 @@
|
|||||||
b4fb7a778bf8cea7d5aed1673c98ff39b4fd8468
|
3a581243027655a91f7ea8149e2cd44124293f47
|
@ -6,9 +6,9 @@
|
|||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: mono 5.18.0.237\n"
|
"Project-Id-Version: mono 5.18.0.239\n"
|
||||||
"Report-Msgid-Bugs-To: http://www.mono-project.com/Bugs\n"
|
"Report-Msgid-Bugs-To: http://www.mono-project.com/Bugs\n"
|
||||||
"POT-Creation-Date: 2019-01-12 08:06+0000\n"
|
"POT-Creation-Date: 2019-01-15 08:09+0000\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
BIN
po/mcs/pt_BR.gmo
BIN
po/mcs/pt_BR.gmo
Binary file not shown.
@ -1 +1 @@
|
|||||||
f62a6a2e821d7772818d78785a49b72df9f2a7a5
|
a626d442d10692d9e6487fe822577c949347180b
|
Loading…
x
Reference in New Issue
Block a user