Skip to content
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 3 commits into from Mar 25, 2015
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
95 changes: 95 additions & 0 deletions src/Common/src/Interop/OSX/Interop.CoreFoundation.cs
@@ -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>
Copy link
Member

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.

Copy link
Member

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.)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. Will do

/// <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);
}
}
184 changes: 184 additions & 0 deletions src/Common/src/Interop/OSX/Interop.EventStream.cs
@@ -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);
}
}
11 changes: 11 additions & 0 deletions src/Common/src/Interop/OSX/Interop.Libraries.cs
@@ -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";
}
}
42 changes: 42 additions & 0 deletions src/Common/src/Interop/OSX/Interop.RunLoop.cs
@@ -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();
}
}
2 changes: 1 addition & 1 deletion src/Common/src/Interop/Unix/Interop.Libraries.cs
Expand Up @@ -3,7 +3,7 @@

internal static partial class Interop
{
private static class Libraries
private static partial class Libraries
{
internal const string Libc = "libc"; // C library
internal const string LibCoreClr = "libcoreclr"; // CoreCLR runtime
Expand Down