private unsafe void ProcessEvents(int numEvents, byte **eventPaths, 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 // 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; Memory <char>[] events = new Memory <char> [numEvents]; ParseEvents(); for (int i = 0; i < numEvents; i++) { ReadOnlySpan <char> path = events[i].Span; 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 if (_fullDirectory.Length >= path.Length && path.Equals(_fullDirectory.AsSpan(0, path.Length), StringComparison.OrdinalIgnoreCase)) { continue; } WatcherChangeTypes eventType = 0; // First, we should check if this event should kick off a re-scan since we can't really rely on anything after this point if that is true if (ShouldRescanOccur(eventFlags[i])) { watcher.OnError(new ErrorEventArgs(new IOException(SR.FSW_BufferOverflow, (int)eventFlags[i]))); break; } else if ((handledRenameEvents != null) && (handledRenameEvents.Contains(eventIds[i]))) { // If this event is the second in a rename pair then skip it continue; } else if (CheckIfPathIsNested(path) && ((eventType = FilterEvents(eventFlags[i])) != 0)) { // 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. ReadOnlySpan <char> relativePath = ReadOnlySpan <char> .Empty; if (path.Length > _fullDirectory.Length && path.StartsWith(_fullDirectory, StringComparison.OrdinalIgnoreCase)) { // Remove the root directory to get the relative path relativePath = path.Slice(_fullDirectory.Length); } // Raise a notification for the event if (((eventType & WatcherChangeTypes.Changed) > 0)) { watcher.NotifyFileSystemEventArgs(WatcherChangeTypes.Changed, relativePath); } if (((eventType & WatcherChangeTypes.Created) > 0)) { watcher.NotifyFileSystemEventArgs(WatcherChangeTypes.Created, relativePath); } if (((eventType & WatcherChangeTypes.Deleted) > 0)) { watcher.NotifyFileSystemEventArgs(WatcherChangeTypes.Deleted, relativePath); } if (((eventType & WatcherChangeTypes.Renamed) > 0)) { // Find the rename that is paired to this rename, which should be the next rename in the list int pairedId = FindRenameChangePairedChange(i, eventFlags, numEvents); if (pairedId == int.MinValue) { // 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. // 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. if (DoesItemExist(path, IsFlagSet(eventFlags[i], FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsFile))) { if ((eventType & WatcherChangeTypes.Created) == 0) { watcher.NotifyFileSystemEventArgs(WatcherChangeTypes.Created, relativePath); } } else if ((eventType & WatcherChangeTypes.Deleted) == 0) { watcher.NotifyFileSystemEventArgs(WatcherChangeTypes.Deleted, relativePath); } } else { // Remove the base directory prefix and add the paired event to the list of // events to skip and notify the user of the rename ReadOnlySpan <char> newPathRelativeName = events[pairedId].Span; if (newPathRelativeName.Length >= _fullDirectory.Length && newPathRelativeName.StartsWith(_fullDirectory, StringComparison.OrdinalIgnoreCase)) { newPathRelativeName = newPathRelativeName.Slice(_fullDirectory.Length); } watcher.NotifyRenameEventArgs(WatcherChangeTypes.Renamed, newPathRelativeName, 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(); 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); } } }