Merge branch 'upstream'

Former-commit-id: 5d3d94117603ec2ee44e6e4cea229bdeb53962f2
This commit is contained in:
Xamarin Public Jenkins (auto-signing) 2020-01-07 09:06:30 +00:00
commit 59c8e47ed1
65 changed files with 565 additions and 194 deletions

View File

@ -1 +1 @@
523906662921cbaf2a45e1930b524dd9e64bcc91 049b897e1931b418af32b321b8bec26b0c4f1210

View File

@ -1 +1 @@
79b8df2131d5b3213b46975e3dc08979ea9001b4 2460f68689d061e282b97ea542dea9e46ce5f0b5

View File

@ -779,20 +779,16 @@ mono_assembly_getrootdir (void)
<div class="mapi-ptr"></div> <div class="mapi-ptr"></div>
<div class="mapi-declaration mapi-section">Syntax</div> <div class="mapi-declaration mapi-section">Syntax</div>
<div class="mapi-prototype">gboolean <div class="mapi-prototype">void
mono_assembly_get_assemblyref_checked (MonoImage *image, int index, MonoAssemblyName *aname, MonoError *error) mono_assembly_get_assemblyref (MonoImage *image, int index, MonoAssemblyName *aname)
</div> </div>
<p /> <p />
<div class="mapi-section">Parameters</div> <div class="mapi-section">Parameters</div>
<table class="mapi-parameters"><tbody><tr><td><i>image</i></td><td> pointer to the <code>MonoImage</code> to extract the information from.</td></tr><tr><td><i>index</i></td><td> index to the assembly reference in the image.</td></tr><tr><td><i>aname</i></td><td> pointer to a <code>MonoAssemblyName</code> that will hold the returned value.</td></tr><tr><td><i>error</i></td><td> set on error</td></tr></tbody></table> <div class="mapi-section">Return value</div> <table class="mapi-parameters"><tbody><tr><td><i>image</i></td><td> pointer to the <code>MonoImage</code> to extract the information from.</td></tr><tr><td><i>index</i></td><td> index to the assembly reference in the image.</td></tr><tr><td><i>aname</i></td><td> pointer to a <code>MonoAssemblyName</code> that will hold the returned value.</td></tr></tbody></table> <div class="mapi-section">Description</div>
<div> <code>TRUE</code> on success, otherwise sets <i>error</i> and returns <code>FALSE</code>
</div>
<div class="mapi-section">Description</div>
<div> <div>
<p /> <p />
Fills out the <i>aname</i> with the assembly name of the <i>index</i> assembly reference in <i>image</i>. Fills out the <i>aname</i> with the assembly name of the <i>index</i> assembly reference in <i>image</i>.</div>
<p /></div>
</div><!--mapi-description --> </div><!--mapi-description -->
</div><!--height container --> </div><!--height container -->
</div> <!-- class=mapi --> </div> <!-- class=mapi -->

View File

@ -88,10 +88,8 @@ internal static partial class Interop
size_t numEvents, size_t numEvents,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)]
byte** eventPaths, byte** eventPaths,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] FSEventStreamEventFlags* eventFlags,
FSEventStreamEventFlags[] eventFlags, FSEventStreamEventId* eventIds);
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)]
FSEventStreamEventId[] eventIds);
/// <summary> /// <summary>
/// Internal wrapper to create a new EventStream to listen to events from the core OS (such as File System events). /// Internal wrapper to create a new EventStream to listen to events from the core OS (such as File System events).

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build"> <Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build">
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" /> <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
<PropertyGroup> <PropertyGroup>

View File

@ -9,10 +9,12 @@ using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using System.Text; using System.Text;
#pragma warning disable SA1121 // we don't want to simplify built-ins here as we're using aliasing
using CFStringRef = System.IntPtr; using CFStringRef = System.IntPtr;
using FSEventStreamRef = System.IntPtr; using FSEventStreamRef = System.IntPtr;
using size_t = System.IntPtr; using size_t = System.IntPtr;
using FSEventStreamEventId = System.UInt64; using FSEventStreamEventId = System.UInt64;
using FSEventStreamEventFlags = Interop.EventStream.FSEventStreamEventFlags;
using CFRunLoopRef = System.IntPtr; using CFRunLoopRef = System.IntPtr;
using Microsoft.Win32.SafeHandles; using Microsoft.Win32.SafeHandles;
@ -87,38 +89,38 @@ namespace System.IO
private CancellationTokenSource _cancellation; private CancellationTokenSource _cancellation;
private static Interop.EventStream.FSEventStreamEventFlags TranslateFlags(NotifyFilters flagsToTranslate) private static FSEventStreamEventFlags TranslateFlags(NotifyFilters flagsToTranslate)
{ {
Interop.EventStream.FSEventStreamEventFlags flags = 0; FSEventStreamEventFlags flags = 0;
// Always re-create the filter flags when start is called since they could have changed // Always re-create the filter flags when start is called since they could have changed
if ((flagsToTranslate & (NotifyFilters.Attributes | NotifyFilters.CreationTime | NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.Size)) != 0) if ((flagsToTranslate & (NotifyFilters.Attributes | NotifyFilters.CreationTime | NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.Size)) != 0)
{ {
flags = Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemInodeMetaMod | flags = FSEventStreamEventFlags.kFSEventStreamEventFlagItemInodeMetaMod |
Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemFinderInfoMod | FSEventStreamEventFlags.kFSEventStreamEventFlagItemFinderInfoMod |
Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemModified | FSEventStreamEventFlags.kFSEventStreamEventFlagItemModified |
Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemChangeOwner; FSEventStreamEventFlags.kFSEventStreamEventFlagItemChangeOwner;
} }
if ((flagsToTranslate & NotifyFilters.Security) != 0) if ((flagsToTranslate & NotifyFilters.Security) != 0)
{ {
flags |= Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemChangeOwner | flags |= FSEventStreamEventFlags.kFSEventStreamEventFlagItemChangeOwner |
Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemXattrMod; FSEventStreamEventFlags.kFSEventStreamEventFlagItemXattrMod;
} }
if ((flagsToTranslate & NotifyFilters.DirectoryName) != 0) if ((flagsToTranslate & NotifyFilters.DirectoryName) != 0)
{ {
flags |= Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsDir | flags |= FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsDir |
Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsSymlink | FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsSymlink |
Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemCreated | FSEventStreamEventFlags.kFSEventStreamEventFlagItemCreated |
Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemRemoved | FSEventStreamEventFlags.kFSEventStreamEventFlagItemRemoved |
Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemRenamed; FSEventStreamEventFlags.kFSEventStreamEventFlagItemRenamed;
} }
if ((flagsToTranslate & NotifyFilters.FileName) != 0) if ((flagsToTranslate & NotifyFilters.FileName) != 0)
{ {
flags |= Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsFile | flags |= FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsFile |
Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsSymlink | FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsSymlink |
Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemCreated | FSEventStreamEventFlags.kFSEventStreamEventFlagItemCreated |
Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemRemoved | FSEventStreamEventFlags.kFSEventStreamEventFlagItemRemoved |
Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemRenamed; FSEventStreamEventFlags.kFSEventStreamEventFlagItemRenamed;
} }
return flags; return flags;
@ -143,7 +145,7 @@ namespace System.IO
private bool _includeChildren; private bool _includeChildren;
// The bitmask of events that we want to send to the user // The bitmask of events that we want to send to the user
private Interop.EventStream.FSEventStreamEventFlags _filterFlags; private FSEventStreamEventFlags _filterFlags;
// The EventStream to listen for events on // The EventStream to listen for events on
private SafeEventStreamHandle _eventStream; private SafeEventStreamHandle _eventStream;
@ -165,7 +167,7 @@ namespace System.IO
FileSystemWatcher watcher, FileSystemWatcher watcher,
string directory, string directory,
bool includeChildren, bool includeChildren,
Interop.EventStream.FSEventStreamEventFlags filter, FSEventStreamEventFlags filter,
CancellationToken cancelToken) CancellationToken cancelToken)
{ {
Debug.Assert(string.IsNullOrEmpty(directory) == false); Debug.Assert(string.IsNullOrEmpty(directory) == false);
@ -364,13 +366,9 @@ namespace System.IO
IntPtr clientCallBackInfo, IntPtr clientCallBackInfo,
size_t numEvents, size_t numEvents,
byte** eventPaths, byte** eventPaths,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] FSEventStreamEventFlags* eventFlags,
Interop.EventStream.FSEventStreamEventFlags[] eventFlags, FSEventStreamEventId* eventIds)
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)]
FSEventStreamEventId[] eventIds)
{ {
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
// if the watcher is dropped by the user without being disposed. If we can't get the watcher, // if the watcher is dropped by the user without being disposed. If we can't get the watcher,
@ -386,33 +384,33 @@ namespace System.IO
if (context is null) if (context is null)
{ {
// Flow suppressed, just run here // Flow suppressed, just run here
ProcessEvents(numEvents.ToInt32(), eventPaths, eventFlags, eventIds, watcher); ProcessEvents(numEvents.ToInt32(), eventPaths, new Span<FSEventStreamEventFlags>(eventFlags, numEvents.ToInt32()), new Span<FSEventStreamEventId>(eventIds, numEvents.ToInt32()), watcher);
} }
else else
{ {
ExecutionContext.Run( ExecutionContext.Run(
context, context,
(object o) => ((RunningInstance)o).ProcessEvents(numEvents.ToInt32(), eventPaths, eventFlags, eventIds, watcher), (object o) => ((RunningInstance)o).ProcessEvents(numEvents.ToInt32(), eventPaths, new Span<FSEventStreamEventFlags>(eventFlags, numEvents.ToInt32()), new Span<FSEventStreamEventId>(eventIds, numEvents.ToInt32()), watcher),
this); this);
} }
} }
private unsafe void ProcessEvents(int numEvents, private unsafe void ProcessEvents(int numEvents,
byte** eventPaths, byte** eventPaths,
Interop.EventStream.FSEventStreamEventFlags[] eventFlags, Span<FSEventStreamEventFlags> eventFlags,
FSEventStreamEventId[] eventIds, Span<FSEventStreamEventId> eventIds,
FileSystemWatcher watcher) 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 reach the first we need to test for the next one if it is the case. If the next one belongs into 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. // we'll store the event id 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; int? handledRenameEvents = null;
Memory<char>[] events = new Memory<char>[numEvents];
ParseEvents();
for (long i = 0; i < numEvents; i++) for (int i = 0; i < numEvents; i++)
{ {
ReadOnlySpan<char> path = events[i].Span; using ParsedEvent parsedEvent = ParseEvent(eventPaths[i]);
Debug.Assert(path[path.Length - 1] != '/', "Trailing slashes on events is not supported");
ReadOnlySpan<char> path = parsedEvent.Path;
Debug.Assert(path[^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
if (_fullDirectory.Length >= path.Length && path.Equals(_fullDirectory.AsSpan(0, path.Length), StringComparison.OrdinalIgnoreCase)) if (_fullDirectory.Length >= path.Length && path.Equals(_fullDirectory.AsSpan(0, path.Length), StringComparison.OrdinalIgnoreCase))
@ -427,7 +425,7 @@ namespace System.IO
watcher.OnError(new ErrorEventArgs(new IOException(SR.FSW_BufferOverflow, (int)eventFlags[i]))); watcher.OnError(new ErrorEventArgs(new IOException(SR.FSW_BufferOverflow, (int)eventFlags[i])));
break; break;
} }
else if ((handledRenameEvents != null) && (handledRenameEvents.Contains(eventIds[i]))) else if (handledRenameEvents == i)
{ {
// 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;
@ -460,15 +458,15 @@ 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.
long pairedId = FindRenameChangePairedChange(i, eventFlags); int? pairedId = FindRenameChangePairedChange(i, eventFlags, eventIds);
if (pairedId == long.MinValue) if (!pairedId.HasValue)
{ {
// 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
// move from unwatched folder to watcher folder scenario or a move from the watcher folder out. // move from unwatched folder to watcher folder scenario or a move from the watcher folder out.
// Check if the item exists on disk to check which it is // Check if the item exists on disk to check which it is
// Don't send a new notification if we already sent one for this event. // Don't send a new notification if we already sent one for this event.
if (DoesItemExist(path, IsFlagSet(eventFlags[i], Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsFile))) if (DoesItemExist(path, eventFlags[i].HasFlag(FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsFile)))
{ {
if ((eventType & WatcherChangeTypes.Created) == 0) if ((eventType & WatcherChangeTypes.Created) == 0)
{ {
@ -484,42 +482,30 @@ 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
if (events[pairedId].Span.Length >= _fullDirectory.Length using (ParsedEvent pairedEvent = ParseEvent(eventPaths[pairedId.GetValueOrDefault()]))
&& _fullDirectory.AsSpan().Equals(events[pairedId].Span.Slice(0, _fullDirectory.Length), StringComparison.OrdinalIgnoreCase))
{ {
ReadOnlySpan<char> newPathRelativeName = events[pairedId].Span.Slice(_fullDirectory.Length); ReadOnlySpan<char> newPathRelativeName = pairedEvent.Path;
if (newPathRelativeName.Length >= _fullDirectory.Length &&
newPathRelativeName.StartsWith(_fullDirectory, StringComparison.OrdinalIgnoreCase))
{
newPathRelativeName = newPathRelativeName.Slice(_fullDirectory.Length);
}
watcher.NotifyRenameEventArgs(WatcherChangeTypes.Renamed, newPathRelativeName, relativePath); watcher.NotifyRenameEventArgs(WatcherChangeTypes.Renamed, newPathRelativeName, relativePath);
} }
else handledRenameEvents = pairedId.GetValueOrDefault();
{
//if the base directory prefix isn't there, just use the full absolute path
watcher.NotifyRenameEventArgs(WatcherChangeTypes.Renamed, events[pairedId].Span, relativePath);
}
// Create a new list, if necessary, and add the event
if (handledRenameEvents == null)
{
handledRenameEvents = new List<FSEventStreamEventId>();
}
handledRenameEvents.Add(eventIds[pairedId]);
} }
} }
} }
ArraySegment<char> underlyingArray;
if (MemoryMarshal.TryGetArray(events[i], out underlyingArray))
ArrayPool<char>.Shared.Return(underlyingArray.Array);
} }
this._context = ExecutionContext.Capture(); this._context = ExecutionContext.Capture();
void ParseEvents() ParsedEvent ParseEvent(byte* nativeEventPath)
{
for (int i = 0; i < events.Length; i++)
{ {
int byteCount = 0; int byteCount = 0;
Debug.Assert(eventPaths[i] != null); Debug.Assert(nativeEventPath != null);
byte* temp = eventPaths[i]; byte* temp = nativeEventPath;
// Finds the position of null character. // Finds the position of null character.
while (*temp != 0) while (*temp != 0)
@ -529,27 +515,43 @@ namespace System.IO
} }
Debug.Assert(byteCount > 0, "Empty events are not supported"); Debug.Assert(byteCount > 0, "Empty events are not supported");
events[i] = new Memory<char>(ArrayPool<char>.Shared.Rent(Encoding.UTF8.GetMaxCharCount(byteCount))); char[] tempBuffer = ArrayPool<char>.Shared.Rent(Encoding.UTF8.GetMaxCharCount(byteCount));
int charCount;
// Converting an array of bytes to UTF-8 char array // Converting an array of bytes to UTF-8 char array
charCount = Encoding.UTF8.GetChars(new ReadOnlySpan<byte>(eventPaths[i], byteCount), events[i].Span); int charCount = Encoding.UTF8.GetChars(new ReadOnlySpan<byte>(nativeEventPath, byteCount), tempBuffer);
events[i] = events[i].Slice(0, charCount); return new ParsedEvent(tempBuffer.AsSpan(0, charCount), tempBuffer);
} }
} }
private readonly ref struct ParsedEvent
{
public ParsedEvent(ReadOnlySpan<char> path, char[] tempBuffer)
{
TempBuffer = tempBuffer;
Path = path;
}
public readonly ReadOnlySpan<char> Path;
public readonly char[] TempBuffer;
public void Dispose() => ArrayPool<char>.Shared.Return(TempBuffer);
} }
/// <summary> /// <summary>
/// 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) private WatcherChangeTypes FilterEvents(FSEventStreamEventFlags eventFlags)
{ {
const Interop.EventStream.FSEventStreamEventFlags changedFlags = Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemInodeMetaMod | const FSEventStreamEventFlags changedFlags = FSEventStreamEventFlags.kFSEventStreamEventFlagItemInodeMetaMod |
Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemFinderInfoMod | FSEventStreamEventFlags.kFSEventStreamEventFlagItemFinderInfoMod |
Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemModified | FSEventStreamEventFlags.kFSEventStreamEventFlagItemModified |
Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemChangeOwner | FSEventStreamEventFlags.kFSEventStreamEventFlagItemChangeOwner |
Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemXattrMod; FSEventStreamEventFlags.kFSEventStreamEventFlagItemXattrMod;
WatcherChangeTypes eventType = 0; WatcherChangeTypes eventType = 0;
// If any of the Changed flags are set in both Filter and Event then a Changed event has occurred. // If any of the Changed flags are set in both Filter and Event then a Changed event has occurred.
if (((_filterFlags & changedFlags) & (eventFlags & changedFlags)) > 0) if (((_filterFlags & changedFlags) & (eventFlags & changedFlags)) > 0)
@ -558,25 +560,25 @@ namespace System.IO
} }
// Notify created/deleted/renamed events if they pass through the filters // Notify created/deleted/renamed events if they pass through the filters
bool allowDirs = (_filterFlags & Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsDir) > 0; bool allowDirs = (_filterFlags & FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsDir) > 0;
bool allowFiles = (_filterFlags & Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsFile) > 0; bool allowFiles = (_filterFlags & FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsFile) > 0;
bool isDir = (eventFlags & Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsDir) > 0; bool isDir = (eventFlags & FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsDir) > 0;
bool isFile = (eventFlags & Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsFile) > 0; bool isFile = (eventFlags & FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsFile) > 0;
bool eventIsCorrectType = (isDir && allowDirs) || (isFile && allowFiles); bool eventIsCorrectType = (isDir && allowDirs) || (isFile && allowFiles);
bool eventIsLink = (eventFlags & (Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsHardlink | Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsSymlink | Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsLastHardlink)) > 0; bool eventIsLink = (eventFlags & (FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsHardlink | FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsSymlink | FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsLastHardlink)) > 0;
if (eventIsCorrectType || ((allowDirs || allowFiles) && (eventIsLink))) if (eventIsCorrectType || ((allowDirs || allowFiles) && (eventIsLink)))
{ {
// Notify Created/Deleted/Renamed events. // Notify Created/Deleted/Renamed events.
if (IsFlagSet(eventFlags, Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemRenamed)) if (eventFlags.HasFlag(FSEventStreamEventFlags.kFSEventStreamEventFlagItemRenamed))
{ {
eventType |= WatcherChangeTypes.Renamed; eventType |= WatcherChangeTypes.Renamed;
} }
if (IsFlagSet(eventFlags, Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemCreated)) if (eventFlags.HasFlag(FSEventStreamEventFlags.kFSEventStreamEventFlagItemCreated))
{ {
eventType |= WatcherChangeTypes.Created; eventType |= WatcherChangeTypes.Created;
} }
if (IsFlagSet(eventFlags, Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemRemoved)) if (eventFlags.HasFlag(FSEventStreamEventFlags.kFSEventStreamEventFlagItemRemoved))
{ {
eventType |= WatcherChangeTypes.Deleted; eventType |= WatcherChangeTypes.Deleted;
} }
@ -584,15 +586,15 @@ namespace System.IO
return eventType; return eventType;
} }
private bool ShouldRescanOccur(Interop.EventStream.FSEventStreamEventFlags flags) private bool ShouldRescanOccur(FSEventStreamEventFlags flags)
{ {
// Check if any bit is set that signals that the caller should rescan // Check if any bit is set that signals that the caller should rescan
return (IsFlagSet(flags, Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagMustScanSubDirs) || return (flags.HasFlag(FSEventStreamEventFlags.kFSEventStreamEventFlagMustScanSubDirs) ||
IsFlagSet(flags, Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagUserDropped) || flags.HasFlag(FSEventStreamEventFlags.kFSEventStreamEventFlagUserDropped) ||
IsFlagSet(flags, Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagKernelDropped) || flags.HasFlag(FSEventStreamEventFlags.kFSEventStreamEventFlagKernelDropped) ||
IsFlagSet(flags, Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagRootChanged) || flags.HasFlag(FSEventStreamEventFlags.kFSEventStreamEventFlagRootChanged) ||
IsFlagSet(flags, Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagMount) || flags.HasFlag(FSEventStreamEventFlags.kFSEventStreamEventFlagMount) ||
IsFlagSet(flags, Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagUnmount)); flags.HasFlag(FSEventStreamEventFlags.kFSEventStreamEventFlagUnmount));
} }
private bool CheckIfPathIsNested(ReadOnlySpan<char> eventPath) private bool CheckIfPathIsNested(ReadOnlySpan<char> eventPath)
@ -603,28 +605,29 @@ namespace System.IO
return _includeChildren || _fullDirectory.AsSpan().StartsWith(System.IO.Path.GetDirectoryName(eventPath), StringComparison.OrdinalIgnoreCase); return _includeChildren || _fullDirectory.AsSpan().StartsWith(System.IO.Path.GetDirectoryName(eventPath), StringComparison.OrdinalIgnoreCase);
} }
private long FindRenameChangePairedChange( private unsafe int? FindRenameChangePairedChange(
long currentIndex, int currentIndex,
Interop.EventStream.FSEventStreamEventFlags[] eventFlags) Span<FSEventStreamEventFlags> flags, Span<FSEventStreamEventId> ids)
{ {
// Start at one past the current index and try to find the next Rename item, which should be the old path. // The rename event can be composed of two events. The first contains the original file name the second contains the new file name.
for (long i = currentIndex + 1; i < eventFlags.Length; i++) // Each of the events is delivered only when the corresponding folder is watched. It means both events are delivered when the rename/move
// occurs inside the watched folder. When the move has origin o final destination outside, only one event is delivered. To distinguish
// between two nonrelated events and the event which belong together the event ID is tested. Only related rename events differ in ID by one.
// This behavior isn't documented and there is an open radar http://www.openradar.me/13461247.
int nextIndex = currentIndex + 1;
if (nextIndex >= flags.Length)
return null;
if (ids[currentIndex] + 1 == ids[nextIndex] &&
flags[nextIndex].HasFlag(FSEventStreamEventFlags.kFSEventStreamEventFlagItemRenamed))
{ {
if (IsFlagSet(eventFlags[i], Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemRenamed)) return nextIndex;
{
// We found match, stop looking
return i;
}
} }
return long.MinValue; return null;
} }
private static bool IsFlagSet(Interop.EventStream.FSEventStreamEventFlags flags, Interop.EventStream.FSEventStreamEventFlags value)
{
return (value & flags) == value;
}
private static bool DoesItemExist(ReadOnlySpan<char> path, bool isFile) private static bool DoesItemExist(ReadOnlySpan<char> path, bool isFile)
{ {
if (path.IsEmpty || path.Length == 0) if (path.IsEmpty || path.Length == 0)

View File

@ -4,6 +4,10 @@
using Xunit; using Xunit;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace System.IO.Tests namespace System.IO.Tests
{ {
public class Directory_Move_Tests : FileSystemWatcherTest public class Directory_Move_Tests : FileSystemWatcherTest
@ -16,11 +20,43 @@ namespace System.IO.Tests
} }
[Fact] [Fact]
[PlatformSpecific(TestPlatforms.OSX)]
public void Directory_Move_From_Watched_To_Unwatched() public void Directory_Move_From_Watched_To_Unwatched()
{ {
DirectoryMove_FromWatchedToUnwatched(WatcherChangeTypes.Deleted); DirectoryMove_FromWatchedToUnwatched(WatcherChangeTypes.Deleted);
} }
[Theory]
[PlatformSpecific(TestPlatforms.OSX)]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public void Directory_Move_Multiple_From_Watched_To_Unwatched_Mac(int filesCount)
{
// On Mac, the FSStream aggregate old events caused by the test setup.
// There is no option how to get rid of it but skip it.
DirectoryMove_Multiple_FromWatchedToUnwatched(filesCount, skipOldEvents: true);
}
[Theory]
[PlatformSpecific(~TestPlatforms.OSX)]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public void Directory_Move_Multiple_From_Watched_To_Unwatched(int filesCount)
{
DirectoryMove_Multiple_FromWatchedToUnwatched(filesCount, skipOldEvents: false);
}
[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public void Directory_Move_Multiple_From_Unatched_To_Watched(int filesCount)
{
DirectoryMove_Multiple_FromUnwatchedToWatched(filesCount);
}
[Fact] [Fact]
[PlatformSpecific(TestPlatforms.Windows)] // Expected WatcherChangeTypes are different based on OS [PlatformSpecific(TestPlatforms.Windows)] // Expected WatcherChangeTypes are different based on OS
public void Windows_Directory_Move_To_Different_Watched_Directory() public void Windows_Directory_Move_To_Different_Watched_Directory()
@ -92,6 +128,71 @@ namespace System.IO.Tests
} }
} }
private void DirectoryMove_Multiple_FromWatchedToUnwatched(int filesCount, bool skipOldEvents)
{
Assert.InRange(filesCount, 0, int.MaxValue);
using var watchedTestDirectory = new TempDirectory(GetTestFilePath());
using var unwatchedTestDirectory = new TempDirectory(GetTestFilePath());
var dirs = Enumerable.Range(0, filesCount)
.Select(i => new
{
DirecoryInWatchedDir = Path.Combine(watchedTestDirectory.Path, $"dir{i}"),
DirecoryInUnwatchedDir = Path.Combine(unwatchedTestDirectory.Path, $"dir{i}")
}).ToArray();
Array.ForEach(dirs, (dir) => Directory.CreateDirectory(dir.DirecoryInWatchedDir));
using var watcher = new FileSystemWatcher(watchedTestDirectory.Path, "*");
Action action = () => Array.ForEach(dirs, dir => Directory.Move(dir.DirecoryInWatchedDir, dir.DirecoryInUnwatchedDir));
// On macOS, for each file we receive two events as describe in comment below.
int expectEvents = filesCount;
if (skipOldEvents)
expectEvents = expectEvents * 2;
IEnumerable<FiredEvent> events = ExpectEvents(watcher, expectEvents, action);
if (skipOldEvents)
events = events.Where(x => x.EventType != WatcherChangeTypes.Created);
var expectedEvents = dirs.Select(dir => new FiredEvent(WatcherChangeTypes.Deleted, dir.DirecoryInWatchedDir));
// Remove Created events as there is racecondition when create dir and then observe parent folder. It receives Create event altought Watcher is not registered yet.
Assert.Equal(expectedEvents, events.Where(x => x.EventType != WatcherChangeTypes.Created));
}
private void DirectoryMove_Multiple_FromUnwatchedToWatched(int filesCount)
{
Assert.InRange(filesCount, 0, int.MaxValue);
using var watchedTestDirectory = new TempDirectory(GetTestFilePath());
using var unwatchedTestDirectory = new TempDirectory(GetTestFilePath());
var dirs = Enumerable.Range(0, filesCount)
.Select(i => new
{
DirecoryInWatchedDir = Path.Combine(watchedTestDirectory.Path, $"dir{i}"),
DirecoryInUnwatchedDir = Path.Combine(unwatchedTestDirectory.Path, $"dir{i}")
}).ToArray();
Array.ForEach(dirs, (dir) => Directory.CreateDirectory(dir.DirecoryInUnwatchedDir));
using var watcher = new FileSystemWatcher(watchedTestDirectory.Path, "*");
Action action = () => Array.ForEach(dirs, dir => Directory.Move(dir.DirecoryInUnwatchedDir, dir.DirecoryInWatchedDir));
List<FiredEvent> events = ExpectEvents(watcher, filesCount, action);
var expectedEvents = dirs.Select(dir => new FiredEvent(WatcherChangeTypes.Created, dir.DirecoryInWatchedDir));
Assert.Equal(expectedEvents, events);
}
private void DirectoryMove_FromWatchedToUnwatched(WatcherChangeTypes eventType) private void DirectoryMove_FromWatchedToUnwatched(WatcherChangeTypes eventType)
{ {
using (var watchedTestDirectory = new TempDirectory(GetTestFilePath())) using (var watchedTestDirectory = new TempDirectory(GetTestFilePath()))

View File

@ -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.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Xunit; using Xunit;
namespace System.IO.Tests namespace System.IO.Tests
@ -28,6 +31,37 @@ namespace System.IO.Tests
FileMove_FromWatchedToUnwatched(WatcherChangeTypes.Deleted); FileMove_FromWatchedToUnwatched(WatcherChangeTypes.Deleted);
} }
[Theory]
[PlatformSpecific(TestPlatforms.OSX)]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public void File_Move_Multiple_From_Watched_To_Unwatched_Mac(int filesCount)
{
// On Mac, the FSStream aggregate old events caused by the test setup.
// There is no option how to get rid of it but skip it.
FileMove_Multiple_FromWatchedToUnwatched(filesCount, skipOldEvents: true);
}
[Theory]
[PlatformSpecific(~TestPlatforms.OSX)]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public void File_Move_From_Watched_To_Unwatched(int filesCount)
{
FileMove_Multiple_FromWatchedToUnwatched(filesCount, skipOldEvents: false);
}
[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public void File_Move_Multiple_From_Unwatched_To_WatchedMac(int filesCount)
{
FileMove_Multiple_FromUnwatchedToWatched(filesCount);
}
[Fact] [Fact]
[PlatformSpecific(TestPlatforms.Windows)] // Expected WatcherChangeTypes are different based on OS [PlatformSpecific(TestPlatforms.Windows)] // Expected WatcherChangeTypes are different based on OS
public void Windows_File_Move_To_Different_Watched_Directory() public void Windows_File_Move_To_Different_Watched_Directory()
@ -147,6 +181,74 @@ namespace System.IO.Tests
} }
} }
private void FileMove_Multiple_FromWatchedToUnwatched(int filesCount, bool skipOldEvents)
{
Assert.InRange(filesCount, 0, int.MaxValue);
using var testDirectory = new TempDirectory(GetTestFilePath());
using var watchedTestDirectory = new TempDirectory(Path.Combine(testDirectory.Path, "dir_watched"));
using var unwatchedTestDirectory = new TempDirectory(Path.Combine(testDirectory.Path, "dir_unwatched"));
var files = Enumerable.Range(0, filesCount)
.Select(i => new
{
FileInWatchedDir = Path.Combine(watchedTestDirectory.Path, $"file{i}"),
FileInUnwatchedDir = Path.Combine(unwatchedTestDirectory.Path, $"file{i}")
}).ToArray();
Array.ForEach(files, (file) => File.Create(file.FileInWatchedDir).Dispose());
using var watcher = new FileSystemWatcher(watchedTestDirectory.Path, "*");
Action action = () => Array.ForEach(files, file => File.Move(file.FileInWatchedDir, file.FileInUnwatchedDir));
// On macOS, for each file we receive two events as describe in comment below.
int expectEvents = filesCount;
if (skipOldEvents)
{
expectEvents = expectEvents * 3;
}
IEnumerable<FiredEvent> events = ExpectEvents(watcher, expectEvents, action);
// Remove Created and Changed events as there is racecondition when create file and then observe parent folder. It receives Create and Changed event altought Watcher is not registered yet.
if (skipOldEvents)
{
events = events.Where(x => (x.EventType & (WatcherChangeTypes.Created | WatcherChangeTypes.Changed)) == 0);
}
var expectedEvents = files.Select(file => new FiredEvent(WatcherChangeTypes.Deleted, file.FileInWatchedDir));
Assert.Equal(expectedEvents, events);
}
private void FileMove_Multiple_FromUnwatchedToWatched(int filesCount)
{
Assert.InRange(filesCount, 0, int.MaxValue);
using var testDirectory = new TempDirectory(GetTestFilePath());
using var watchedTestDirectory = new TempDirectory(Path.Combine(testDirectory.Path, "dir_watched"));
using var unwatchedTestDirectory = new TempDirectory(Path.Combine(testDirectory.Path, "dir_unwatched"));
var files = Enumerable.Range(0, filesCount)
.Select(i => new
{
FileInWatchedDir = Path.Combine(watchedTestDirectory.Path, $"file{i}"),
FileInUnwatchedDir = Path.Combine(unwatchedTestDirectory.Path, $"file{i}")
}).ToArray();
Array.ForEach(files, (file) => File.Create(file.FileInUnwatchedDir).Dispose());
using var watcher = new FileSystemWatcher(watchedTestDirectory.Path, "*");
Action action = () => Array.ForEach(files, file => File.Move(file.FileInUnwatchedDir, file.FileInWatchedDir));
List<FiredEvent> events = ExpectEvents(watcher, filesCount, action);
var expectedEvents = files.Select(file => new FiredEvent(WatcherChangeTypes.Created, file.FileInWatchedDir));
Assert.Equal(expectedEvents, events);
}
private void FileMove_FromUnwatchedToWatched(WatcherChangeTypes eventType) private void FileMove_FromUnwatchedToWatched(WatcherChangeTypes eventType)
{ {
using (var testDirectory = new TempDirectory(GetTestFilePath())) using (var testDirectory = new TempDirectory(GetTestFilePath()))

View File

@ -484,5 +484,82 @@ namespace System.IO.Tests
return newWatcher; return newWatcher;
} }
#endif #endif
internal readonly struct FiredEvent
{
public FiredEvent(WatcherChangeTypes eventType, string dir1, string dir2 = "") => (EventType, Dir1, Dir2) = (eventType, dir1, dir2);
public readonly WatcherChangeTypes EventType;
public readonly string Dir1;
public readonly string Dir2;
public override bool Equals(object obj) => obj is FiredEvent evt && Equals(evt);
public bool Equals(FiredEvent other) => EventType == other.EventType &&
Dir1 == other.Dir1 &&
Dir2 == other.Dir2;
public override int GetHashCode() => EventType.GetHashCode() ^ Dir1.GetHashCode() ^ Dir2.GetHashCode();
public override string ToString() => $"{EventType} {Dir1} {Dir2}";
}
// Observe until an expected count of events is triggered, otherwise fail. Return all collected events.
internal static List<FiredEvent> ExpectEvents(FileSystemWatcher watcher, int expectedEvents, Action action)
{
using var eventsOccured = new AutoResetEvent(false);
var eventsOrrures = 0;
var events = new List<FiredEvent>();
ErrorEventArgs error = null;
FileSystemEventHandler fileWatcherEvent = (_, e) => AddEvent(e.ChangeType, e.FullPath);
RenamedEventHandler renameWatcherEvent = (_, e) => AddEvent(e.ChangeType, e.FullPath, e.OldFullPath);
ErrorEventHandler errorHandler = (_, e) => error ??= e ?? new ErrorEventArgs(null);
watcher.Changed += fileWatcherEvent;
watcher.Created += fileWatcherEvent;
watcher.Deleted += fileWatcherEvent;
watcher.Renamed += renameWatcherEvent;
watcher.Error += errorHandler;
bool raisingEvent = watcher.EnableRaisingEvents;
watcher.EnableRaisingEvents = true;
try
{
action();
eventsOccured.WaitOne(new TimeSpan(0, 0, 5));
}
finally
{
watcher.Changed -= fileWatcherEvent;
watcher.Created -= fileWatcherEvent;
watcher.Deleted -= fileWatcherEvent;
watcher.Renamed -= renameWatcherEvent;
watcher.Error -= errorHandler;
watcher.EnableRaisingEvents = raisingEvent;
}
if (error != null)
{
Assert.False(true, $"Filewatcher error event triggered: { error.GetException()?.Message ?? "Unknow error" }");
}
Assert.True(eventsOrrures == expectedEvents, $"Expected events ({expectedEvents}) count doesn't match triggered events count ({eventsOrrures}):{Environment.NewLine}{String.Join(Environment.NewLine, events)}");
return events;
void AddEvent(WatcherChangeTypes eventType, string dir1, string dir2 = "")
{
events.Add(new FiredEvent(eventType, dir1, dir2));
if (Interlocked.Increment(ref eventsOrrures) == expectedEvents)
{
eventsOccured.Set();
}
}
}
} }
} }

View File

@ -41,7 +41,7 @@ static partial 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 = "6.8.0.85"; public const string MonoVersion = "6.8.0.87";
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";

View File

@ -1 +1 @@
222ece4633ae80aea9d99ef4526b5a75372aa725 98e81a42aedaf7e4a355b79292c70367377538c1

View File

@ -1 +1 @@
1d6ec4ca64500ff82995780bf33175f1421b7d08 54d97180aa78f4c8d673a5da0326d2733aa8fdac

View File

@ -1 +1 @@
1c1b67655a882062b5fb2973fc1cdb968c931733 beccb5ae6dbcd4b168c68fb63b9417d52b95bc8a

View File

@ -1 +1 @@
8429d46a922f9c2eb6c8aebe20948006528bdd21 f8f9ccfedec878c09fa0e0d5fd4b87912e952330

View File

@ -1 +1 @@
82b56e2bf5f1981c96e411a0b2a88a1aaf56142e 17ee4ef88336af007120be5b8f3db670de143b1d

View File

@ -1 +1 @@
56f3d2d99b033dfc3c6159718b9ed4a56b8bd3be e440f6ac09f22b1db2fe660ed68ae2f423dffe24

View File

@ -1 +1 @@
c5d83c42e9e1b343cce4ed2301d445f534429010 7d9290d77fb963197df93b5e5d2d1b286898bac9

View File

@ -1 +1 @@
85a98d4312254cdf9fc82ee4e2cad84afd515c69 ace7ea7edcb443539836ff6217f6018f16f5e61b

View File

@ -1 +1 @@
e318cd3079c2f1034c5a6d0874ad3eb544e93782 955d03cb282a3dc4d8355f27d6f910d7d4778722

View File

@ -1 +1 @@
1d6ec4ca64500ff82995780bf33175f1421b7d08 54d97180aa78f4c8d673a5da0326d2733aa8fdac

Some files were not shown because too many files have changed in this diff Show More