This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
Adding initial implementation of the FileSystemWatcher on OS X #1193
Merged
Merged
Changes from 2 commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
using System; | ||
using System.Runtime.InteropServices; | ||
|
||
using CFStringRef = System.IntPtr; | ||
using CFArrayRef = System.IntPtr; | ||
using CFTimeInterval = System.Double; | ||
|
||
internal static partial class Interop | ||
{ | ||
internal static partial class CoreFoundation | ||
{ | ||
/// <summary> | ||
/// Tells the OS what encoding the passed in String is in. These come from the CFString.h header file in the CoreFoundation framework. | ||
/// </summary> | ||
private enum CFStringBuiltInEncodings : uint | ||
{ | ||
kCFStringEncodingMacRoman = 0, | ||
kCFStringEncodingWindowsLatin1 = 0x0500, | ||
kCFStringEncodingISOLatin1 = 0x0201, | ||
kCFStringEncodingNextStepLatin = 0x0B01, | ||
kCFStringEncodingASCII = 0x0600, | ||
kCFStringEncodingUnicode = 0x0100, | ||
kCFStringEncodingUTF8 = 0x08000100, | ||
kCFStringEncodingNonLossyASCII = 0x0BFF, | ||
|
||
kCFStringEncodingUTF16 = 0x0100, | ||
kCFStringEncodingUTF16BE = 0x10000100, | ||
kCFStringEncodingUTF16LE = 0x14000100, | ||
kCFStringEncodingUTF32 = 0x0c000100, | ||
kCFStringEncodingUTF32BE = 0x18000100, | ||
kCFStringEncodingUTF32LE = 0x1c000100 | ||
} | ||
|
||
/// <summary> | ||
/// Creates a CFStringRef from a 8-bit String object. Follows the "Create Rule" where if you create it, you delete it. | ||
/// </summary> | ||
/// <param name="allocator">Should be IntPtr.Zero</param> | ||
/// <param name="str">The string to get a CFStringRef for</param> | ||
/// <param name="encoding">The encoding of the str variable. This should be UTF 8 for OS X</param> | ||
/// <returns>Returns a pointer to a CFString on success; otherwise, returns IntPtr.Zero</returns> | ||
/// <remarks>For *nix systems, the CLR maps ANSI to UTF-8, so be explicit about that</remarks> | ||
[DllImport(Interop.Libraries.CoreFoundationLibrary, CharSet = CharSet.Ansi)] | ||
private static extern CFStringRef CFStringCreateWithCString( | ||
IntPtr allocator, | ||
string str, | ||
CFStringBuiltInEncodings encoding); | ||
|
||
/// <summary> | ||
/// Creates a CFStringRef from a 8-bit String object. Follows the "Create Rule" where if you create it, you delete it. | ||
/// </summary> | ||
/// <param name="str">The string to get a CFStringRef for</param> | ||
/// <returns>Returns a pointer to a CFString on success; otherwise, returns IntPtr.Zero</returns> | ||
internal static CFStringRef CFStringCreateWithCString(string str) | ||
{ | ||
return CFStringCreateWithCString(IntPtr.Zero, str, CFStringBuiltInEncodings.kCFStringEncodingUTF8); | ||
} | ||
|
||
/// <summary> | ||
/// Creates a pointer to an unmanaged CFArray containing the input values. Folows the "Create Rule" where if you create it, you delete it. | ||
/// </summary> | ||
/// <param name="allocator">Should be IntPtr.Zero</param> | ||
/// <param name="values">The values to put in the array</param> | ||
/// <param name="numValues">The number of values in the array</param> | ||
/// <param name="callbacks">Should be IntPtr.Zero</param> | ||
/// <returns>Returns a pointer to a CFArray on success; otherwise, returns IntPtr.Zero</returns> | ||
[DllImport(Interop.Libraries.CoreFoundationLibrary)] | ||
private static extern CFArrayRef CFArrayCreate( | ||
IntPtr allocator, | ||
[MarshalAs(UnmanagedType.LPArray)] | ||
IntPtr[] values, | ||
ulong numValues, | ||
IntPtr callbacks); | ||
|
||
/// <summary> | ||
/// Creates a pointer to an unmanaged CFArray containing the input values. Folows the "Create Rule" where if you create it, you delete it. | ||
/// </summary> | ||
/// <param name="values">The values to put in the array</param> | ||
/// <param name="numValues">The number of values in the array</param> | ||
/// <returns>Returns a pointer to a CFArray on success; otherwise, returns IntPtr.Zero</returns> | ||
internal static CFArrayRef CFArrayCreate(IntPtr[] values, ulong numValues) | ||
{ | ||
return CFArrayCreate(IntPtr.Zero, values, numValues, IntPtr.Zero); | ||
} | ||
|
||
/// <summary> | ||
/// Decrements the reference count on the specified object and, if the ref count hits 0, cleans up the object. | ||
/// </summary> | ||
/// <param name="ptr">The pointer on which to decrement the reference count.</param> | ||
[DllImport(Interop.Libraries.CoreFoundationLibrary)] | ||
internal extern static void CFRelease(IntPtr ptr); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
using System; | ||
using System.Runtime.InteropServices; | ||
|
||
using CFStringRef = System.IntPtr; | ||
using CFArrayRef = System.IntPtr; | ||
using FSEventStreamRef = System.IntPtr; | ||
using size_t = System.IntPtr; | ||
using FSEventStreamEventId = System.UInt64; | ||
using CFTimeInterval = System.Double; | ||
using CFRunLoopRef = System.IntPtr; | ||
|
||
internal static partial class Interop | ||
{ | ||
internal static partial class EventStream | ||
{ | ||
/// <summary> | ||
/// This constant specifies that we don't want historical file system events, only new ones | ||
/// </summary> | ||
internal const ulong kFSEventStreamEventIdSinceNow = ulong.MaxValue; | ||
|
||
/// <summary> | ||
/// Flags that describe what happened in the event that was received. These come from the FSEvents.h header file in the CoreServices framework. | ||
/// </summary> | ||
[Flags] | ||
internal enum FSEventStreamEventFlags : uint | ||
{ | ||
kFSEventStreamEventFlagNone = 0x00000000, | ||
kFSEventStreamEventFlagMustScanSubDirs = 0x00000001, | ||
kFSEventStreamEventFlagUserDropped = 0x00000002, | ||
kFSEventStreamEventFlagKernelDropped = 0x00000004, | ||
kFSEventStreamEventFlagEventIdsWrapped = 0x00000008, | ||
kFSEventStreamEventFlagHistoryDone = 0x00000010, | ||
kFSEventStreamEventFlagRootChanged = 0x00000020, | ||
kFSEventStreamEventFlagMount = 0x00000040, | ||
kFSEventStreamEventFlagUnmount = 0x00000080, /* These flags are only set if you specified the FileEvents */ | ||
/* flags when creating the stream. */ | ||
kFSEventStreamEventFlagItemCreated = 0x00000100, | ||
kFSEventStreamEventFlagItemRemoved = 0x00000200, | ||
kFSEventStreamEventFlagItemInodeMetaMod = 0x00000400, | ||
kFSEventStreamEventFlagItemRenamed = 0x00000800, | ||
kFSEventStreamEventFlagItemModified = 0x00001000, | ||
kFSEventStreamEventFlagItemFinderInfoMod = 0x00002000, | ||
kFSEventStreamEventFlagItemChangeOwner = 0x00004000, | ||
kFSEventStreamEventFlagItemXattrMod = 0x00008000, | ||
kFSEventStreamEventFlagItemIsFile = 0x00010000, | ||
kFSEventStreamEventFlagItemIsDir = 0x00020000, | ||
kFSEventStreamEventFlagItemIsSymlink = 0x00040000 | ||
} | ||
|
||
/// <summary> | ||
/// Flags that describe what kind of event stream should be created (and therefore what events should be | ||
/// piped into this stream). These come from the FSEvents.h header file in the CoreServices framework. | ||
/// </summary> | ||
[Flags] | ||
internal enum FSEventStreamCreateFlags : uint | ||
{ | ||
kFSEventStreamCreateFlagNone = 0x00000000, | ||
kFSEventStreamCreateFlagUseCFTypes = 0x00000001, | ||
kFSEventStreamCreateFlagNoDefer = 0x00000002, | ||
kFSEventStreamCreateFlagWatchRoot = 0x00000004, | ||
kFSEventStreamCreateFlagIgnoreSelf = 0x00000008, | ||
kFSEventStreamCreateFlagFileEvents = 0x00000010 | ||
} | ||
|
||
/// <summary> | ||
/// The EventStream callback that will be called for every event batch. | ||
/// </summary> | ||
/// <param name="streamReference">The stream that was created for this callback.</param> | ||
/// <param name="clientCallBackInfo">A pointer to optional context info; otherwise, IntPtr.Zero.</param> | ||
/// <param name="numEvents">The number of paths, events, and IDs. Path[2] corresponds to Event[2] and ID[2], etc.</param> | ||
/// <param name="eventPaths">The paths that have changed somehow, according to their corresponding event.</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> | ||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] | ||
internal delegate void FSEventStreamCallback( | ||
FSEventStreamRef streamReference, | ||
IntPtr clientCallBackInfo, | ||
size_t numEvents, | ||
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] | ||
String[] eventPaths, | ||
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] | ||
FSEventStreamEventFlags[] eventFlags, | ||
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] | ||
FSEventStreamEventId[] eventIds); | ||
|
||
/// <summary> | ||
/// Internal wrapper to create a new EventStream to listen to events from the core OS (such as File System events). | ||
/// </summary> | ||
/// <param name="allocator">Should be IntPtr.Zero</param> | ||
/// <param name="cb">A callback instance that will be called for every event batch.</param> | ||
/// <param name="context">Should be IntPtr.Zero</param> | ||
/// <param name="pathsToWatch">A CFArray of the path(s) to watch for events.</param> | ||
/// <param name="sinceWhen"> | ||
/// The start point to receive events from. This can be to retrieve historical events or only new events. | ||
/// To get historical events, pass in the corresponding ID of the event you want to start from. | ||
/// To get only new events, pass in kFSEventStreamEventIdSinceNow. | ||
/// </param> | ||
/// <param name="latency">Coalescing period to wait before sending events.</param> | ||
/// <param name="flags">Flags to say what kind of events should be sent through this stream.</param> | ||
/// <returns>On success, returns a pointer to an FSEventStream object; otherwise, returns IntPtr.Zero</returns> | ||
/// <remarks>For *nix systems, the CLR maps ANSI to UTF-8, so be explicit about that</remarks> | ||
[DllImport(Interop.Libraries.CoreServicesLibrary, CharSet = CharSet.Ansi)] | ||
private static extern FSEventStreamRef FSEventStreamCreate( | ||
IntPtr allocator, | ||
FSEventStreamCallback cb, | ||
IntPtr context, | ||
CFArrayRef pathsToWatch, | ||
FSEventStreamEventId sinceWhen, | ||
CFTimeInterval latency, | ||
FSEventStreamCreateFlags flags); | ||
|
||
/// <summary> | ||
/// Creates a new EventStream to listen to events from the core OS (such as File System events). | ||
/// </summary> | ||
/// <param name="cb">A callback instance that will be called for every event batch.</param> | ||
/// <param name="pathsToWatch">A CFArray of the path(s) to watch for events.</param> | ||
/// <param name="sinceWhen"> | ||
/// The start point to receive events from. This can be to retrieve historical events or only new events. | ||
/// To get historical events, pass in the corresponding ID of the event you want to start from. | ||
/// To get only new events, pass in kFSEventStreamEventIdSinceNow. | ||
/// </param> | ||
/// <param name="latency">Coalescing period to wait before sending events.</param> | ||
/// <param name="flags">Flags to say what kind of events should be sent through this stream.</param> | ||
/// <returns>On success, returns a pointer to an FSEventStream object; otherwise, returns IntPtr.Zero</returns> | ||
public static FSEventStreamRef FSEventStreamCreate( | ||
FSEventStreamCallback cb, | ||
CFArrayRef pathsToWatch, | ||
FSEventStreamEventId sinceWhen, | ||
CFTimeInterval latency, | ||
FSEventStreamCreateFlags flags) | ||
{ | ||
return FSEventStreamCreate(IntPtr.Zero, cb, IntPtr.Zero, pathsToWatch, sinceWhen, latency, flags); | ||
} | ||
|
||
/// <summary> | ||
/// Attaches an EventStream to a RunLoop so events can be received. This should usually be the current thread's RunLoop. | ||
/// </summary> | ||
/// <param name="streamRef">The stream to attach to the RunLoop</param> | ||
/// <param name="runLoop">The RunLoop to attach the stream to</param> | ||
/// <param name="runLoopMode">The mode of the RunLoop; this should usually be kCFRunLoopDefaultMode. See the documentation for RunLoops for more info.</param> | ||
[DllImport(Interop.Libraries.CoreServicesLibrary)] | ||
internal static extern void FSEventStreamScheduleWithRunLoop( | ||
FSEventStreamRef streamRef, | ||
CFRunLoopRef runLoop, | ||
CFStringRef runLoopMode); | ||
|
||
/// <summary> | ||
/// Starts receiving events on the specified stream. | ||
/// </summary> | ||
/// <param name="streamRef">The stream to receive events on.</param> | ||
/// <returns>Returns true if the stream was started; otherwise, returns false and no events will be received.</returns> | ||
[DllImport(Interop.Libraries.CoreServicesLibrary)] | ||
internal static extern bool FSEventStreamStart(FSEventStreamRef streamRef); | ||
|
||
/// <summary> | ||
/// Stops receiving events on the specified stream. The stream can be restarted and not miss any events. | ||
/// </summary> | ||
/// <param name="streamRef">The stream to stop receiving events on.</param> | ||
[DllImport(Interop.Libraries.CoreServicesLibrary)] | ||
internal static extern void FSEventStreamStop(FSEventStreamRef streamRef); | ||
|
||
/// <summary> | ||
/// Removes the event stream from the RunLoop. | ||
/// </summary> | ||
/// <param name="streamRef">The stream to remove from the RunLoop</param> | ||
/// <param name="runLoop">The RunLoop to remove the stream from.</param> | ||
/// <param name="runLoopMode">The mode of the RunLoop; this should usually be kCFRunLoopDefaultMode. See the documentation for RunLoops for more info.</param> | ||
[DllImport(Interop.Libraries.CoreServicesLibrary)] | ||
internal static extern void FSEventStreamUnscheduleFromRunLoop( | ||
FSEventStreamRef streamRef, | ||
CFRunLoopRef runLoop, | ||
CFStringRef runLoopMode); | ||
|
||
/// <summary> | ||
/// Releases a reference count on the specified EventStream and, if necessary, cleans the stream up. | ||
/// </summary> | ||
/// <param name="streamRef">The stream on which to decrement the reference count.</param> | ||
[DllImport(Interop.Libraries.CoreServicesLibrary)] | ||
internal static extern void FSEventStreamRelease(FSEventStreamRef streamRef); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
internal static partial class Interop | ||
{ | ||
private static partial class Libraries | ||
{ | ||
internal const string CoreFoundationLibrary = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation"; | ||
internal const string CoreServicesLibrary = "/System/Library/Frameworks/CoreServices.framework/CoreServices"; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
using System; | ||
using System.Runtime.InteropServices; | ||
|
||
using CFRunLoopRef = System.IntPtr; | ||
|
||
internal static partial class Interop | ||
{ | ||
internal static partial class RunLoop | ||
{ | ||
/// <summary> | ||
/// This constant specifies that we want to use the default Run mode for the thread's Run loop. | ||
/// </summary> | ||
/// <remarks> | ||
/// For more information, see the Apple documentation: https://developer.apple.com/library/mac/documentation/CoreFoundation/Reference/CFRunLoopRef/index.html | ||
/// </remarks> | ||
internal static IntPtr kCFRunLoopDefaultMode = Interop.CoreFoundation.CFStringCreateWithCString("kCFRunLoopDefaultMode"); | ||
|
||
/// <summary> | ||
/// Starts the current thread's RunLoop. If the RunLoop is already running, creates a new, nested, RunLoop in the same stack. | ||
/// </summary> | ||
[DllImport(Interop.Libraries.CoreFoundationLibrary)] | ||
internal extern static void CFRunLoopRun(); | ||
|
||
/// <summary> | ||
/// Notifies a RunLoop to stop and return control to the execution context that called CFRunLoopRun | ||
/// </summary> | ||
/// <param name="rl">The RunLoop to notify to stop</param> | ||
[DllImport(Interop.Libraries.CoreFoundationLibrary)] | ||
internal extern static void CFRunLoopStop(CFRunLoopRef rl); | ||
|
||
/// <summary> | ||
/// Retrieves the RunLoop associated with the current thread; all threads automatically have a RunLoop. | ||
/// Follows the "Get Rule" where you do not own the object unless you CFRetain it; in which case, you must also CFRelease it as well. | ||
/// </summary> | ||
/// <returns>Returns a pointer to a CFRunLoop on success; otherwise, returns IntPtr.Zero</returns> | ||
[DllImport(Interop.Libraries.CoreFoundationLibrary)] | ||
internal static extern CFRunLoopRef CFRunLoopGetCurrent(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If only one value makes sense, we could avoid the potential for bugs by making this DllImport private and then adding an internal overload that lacks the encoding parameter and delegates to the private overload with encoding hardcoded to UTF8.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(The same could apply to other places where your comment states that only one particular value should be passed.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea. Will do