diff --git a/src/CoreServices/FSEvents.cs b/src/CoreServices/FSEvents.cs
index deca713aad..7b7f1e18c8 100644
--- a/src/CoreServices/FSEvents.cs
+++ b/src/CoreServices/FSEvents.cs
@@ -11,6 +11,7 @@
#if MONOMAC
using System;
+using System.Collections.Generic;
using System.Runtime.InteropServices;
using ObjCRuntime;
@@ -163,6 +164,82 @@ namespace CoreServices
}
}
+ ///
+ /// Creation options for .
+ ///
+ public sealed class FSEventStreamCreateOptions
+ {
+ ///
+ /// The allocator to use to allocate memory for the stream. If null, the default
+ /// allocator will be used.
+ ///
+ public CFAllocator? Allocator { get; set; }
+
+ ///
+ /// A dev_t corresponding to the device which you want to receive notifications from.
+ /// The dev_t is the same as the st_dev field from a stat structure of a
+ /// file on that device or the f_fsid[0] field of a statfs structure.
+ ///
+ public ulong? DeviceToWatch { get; set; }
+
+ ///
+ /// A list of directory paths, signifying the root of a filesystem hierarchy to be watched
+ /// for modifications. If is set, the list of paths should be
+ /// relative to the root of the device. For example, if a volume "MyData" is mounted at
+ /// "/Volumes/MyData" and you want to watch "/Volumes/MyData/Pictures/July", specify a path
+ /// string of "Pictures/July". To watch the root of a volume pass a path of "" (the empty string).
+ ///
+ public IReadOnlyList? PathsToWatch { get; set; }
+
+ // NB. to be set only by the FSEventStream .ctors
+ internal NSArray? NSPathsToWatch { get; set; }
+
+ ///
+ /// The service will supply events that have happened after the given event ID. To ask for
+ /// events "since now," set to null or . Often, clients
+ /// will supply the highest-numbered event ID they have received in a callback, which they can
+ /// obtain via . Do not set to zero, unless you want to
+ /// receive events for every directory modified since "the beginning of time" -- an unlikely scenario.
+ ///
+ public ulong? SinceWhenId { get; set; }
+
+ ///
+ /// The amount of time the service should wait after hearing about an event from the kernel
+ /// before passing it along to the client via its callback. Specifying a larger value may result
+ /// in more effective temporal coalescing, resulting in fewer callbacks.
+ ///
+ public TimeSpan Latency { get; set; }
+
+ ///
+ /// Flags that modify the behavior of the stream being created.
+ /// See .
+ ///
+ public FSEventStreamCreateFlags Flags { get; set; }
+
+ public FSEventStreamCreateOptions ()
+ {
+ }
+
+ public FSEventStreamCreateOptions (FSEventStreamCreateFlags flags, TimeSpan latency,
+ params string [] pathsToWatch)
+ {
+ Flags = flags;
+ Latency = latency;
+ PathsToWatch = pathsToWatch;
+ }
+
+ public FSEventStreamCreateOptions (FSEventStreamCreateFlags flags, TimeSpan latency,
+ ulong deviceToWatch, params string [] pathsToWatchRelativeToDevice)
+ {
+ Flags = flags;
+ Latency = latency;
+ DeviceToWatch = deviceToWatch;
+ PathsToWatch = pathsToWatchRelativeToDevice;
+ }
+
+ public FSEventStream CreateStream () => new (this);
+ }
+
public class FSEventStream : NativeObject
{
[DllImport (Constants.CoreServicesLibrary)]
@@ -194,11 +271,33 @@ namespace CoreServices
ref FSEventStreamContext context, IntPtr pathsToWatch,
ulong sinceWhen, double latency, FSEventStreamCreateFlags flags);
- public FSEventStream (CFAllocator? allocator, NSArray pathsToWatch,
- ulong sinceWhenId, TimeSpan latency, FSEventStreamCreateFlags flags)
+ [DllImport (Constants.CoreServicesLibrary)]
+ unsafe static extern IntPtr FSEventStreamCreateRelativeToDevice (IntPtr allocator,
+#if NET
+ delegate* unmanaged callback,
+#else
+ FSEventStreamCallback callback,
+#endif
+ ref FSEventStreamContext context, ulong deviceToWatch, IntPtr pathsToWatchRelativeToDevice,
+ ulong sinceWhen, double latency, FSEventStreamCreateFlags flags);
+
+ public FSEventStream (FSEventStreamCreateOptions options)
{
- if (pathsToWatch is null)
- throw new ArgumentNullException (nameof (pathsToWatch));
+ if (options is null)
+ throw new ArgumentNullException (nameof (options));
+
+ NSArray pathsToWatch;
+
+ if (options.NSPathsToWatch is not null) {
+ pathsToWatch = options.NSPathsToWatch;
+ } else if (options.PathsToWatch?.Count == 0) {
+ throw new ArgumentException (
+ $"must specify at least one path to watch on " +
+ $"{nameof (FSEventStreamCreateOptions)}.{nameof (FSEventStreamCreateOptions.PathsToWatch)}",
+ nameof (options));
+ } else {
+ pathsToWatch = NSArray.FromStrings (options.PathsToWatch);
+ }
var gch = GCHandle.Alloc (this);
@@ -212,24 +311,58 @@ namespace CoreServices
context.Release = releaseContextCallback;
#endif
+ var allocator = options.Allocator.GetHandle ();
+ var sinceWhenId = options.SinceWhenId ?? FSEvent.SinceNowId;
+ var latency = options.Latency.TotalSeconds;
+ var flags = options.Flags |= (FSEventStreamCreateFlags)0x1 /* UseCFTypes */;
+
IntPtr handle;
unsafe {
- handle = FSEventStreamCreate (
- allocator.GetHandle (),
+ if (options.DeviceToWatch.HasValue) {
+ handle = FSEventStreamCreateRelativeToDevice (
+ allocator,
#if NET
- &EventsCallback,
+ &EventsCallback,
#else
- eventsCallback,
+ eventsCallback,
#endif
- ref context, pathsToWatch.Handle,
- sinceWhenId, latency.TotalSeconds, flags | (FSEventStreamCreateFlags)0x1 /* UseCFTypes */);
+ ref context,
+ options.DeviceToWatch.Value,
+ pathsToWatch.Handle, sinceWhenId, latency, flags);
+ } else {
+ handle = FSEventStreamCreate (
+ allocator,
+#if NET
+ &EventsCallback,
+#else
+ eventsCallback,
+#endif
+ ref context,
+ pathsToWatch.Handle, sinceWhenId, latency, flags);
+ }
}
InitializeHandle (handle);
}
+ public FSEventStream (CFAllocator? allocator, NSArray pathsToWatch,
+ ulong sinceWhenId, TimeSpan latency, FSEventStreamCreateFlags flags)
+ : this (new () {
+ Allocator = allocator,
+ NSPathsToWatch = pathsToWatch ?? throw new ArgumentNullException (nameof (pathsToWatch)),
+ SinceWhenId = sinceWhenId,
+ Latency = latency,
+ Flags = flags
+ })
+ {
+ }
+
public FSEventStream (string [] pathsToWatch, TimeSpan latency, FSEventStreamCreateFlags flags)
- : this (null, NSArray.FromStrings (pathsToWatch), FSEvent.SinceNowId, latency, flags)
+ : this (new () {
+ PathsToWatch = pathsToWatch ?? throw new ArgumentNullException (nameof (pathsToWatch)),
+ Latency = latency,
+ Flags = flags
+ })
{
}
@@ -409,6 +542,11 @@ namespace CoreServices
public void SetDispatchQueue (DispatchQueue? dispatchQueue)
=> FSEventStreamSetDispatchQueue (GetCheckedHandle (), dispatchQueue.GetHandle ());
+ [DllImport (Constants.CoreServicesLibrary)]
+ static extern ulong FSEventStreamGetDeviceBeingWatched (IntPtr handle);
+
+ public ulong DeviceBeingWatched => FSEventStreamGetDeviceBeingWatched (GetCheckedHandle ());
+
[DllImport (Constants.CoreServicesLibrary)]
static extern IntPtr FSEventStreamCopyPathsBeingWatched (IntPtr handle);
diff --git a/src/Foundation/NSArray.cs b/src/Foundation/NSArray.cs
index 14cfc0a50f..d9b071172b 100644
--- a/src/Foundation/NSArray.cs
+++ b/src/Foundation/NSArray.cs
@@ -197,14 +197,16 @@ namespace Foundation {
return arr;
}
- static public NSArray FromStrings (params string [] items)
+ static public NSArray FromStrings (params string [] items) => FromStrings ((IReadOnlyList)items);
+
+ static public NSArray FromStrings (IReadOnlyList items)
{
if (items == null)
throw new ArgumentNullException (nameof (items));
- IntPtr buf = Marshal.AllocHGlobal (items.Length * IntPtr.Size);
+ IntPtr buf = Marshal.AllocHGlobal (items.Count * IntPtr.Size);
try {
- for (int i = 0; i < items.Length; i++){
+ for (int i = 0; i < items.Count; i++){
IntPtr val;
if (items [i] == null)
@@ -215,7 +217,7 @@ namespace Foundation {
Marshal.WriteIntPtr (buf, i * IntPtr.Size, val);
}
- NSArray arr = Runtime.GetNSObject (NSArray.FromObjects (buf, items.Length));
+ NSArray arr = Runtime.GetNSObject (NSArray.FromObjects (buf, items.Count));
return arr;
} finally {
Marshal.FreeHGlobal (buf);
diff --git a/tests/monotouch-test/CoreServices/FSEventStreamTest.cs b/tests/monotouch-test/CoreServices/FSEventStreamTest.cs
index 07d7ef4138..f410fb2351 100644
--- a/tests/monotouch-test/CoreServices/FSEventStreamTest.cs
+++ b/tests/monotouch-test/CoreServices/FSEventStreamTest.cs
@@ -25,6 +25,46 @@ namespace MonoTouchFixtures.CoreServices {
[TestFixture]
[Preserve (AllMembers = true)]
public sealed class FSEventStreamTest {
+ [Test]
+ public void TestPathsBeingWatched ()
+ {
+ FSEventStreamCreateOptions createOptions = new () {
+ Flags = FileEvents | UseExtendedData,
+ PathsToWatch = new [] {
+ Xamarin.Cache.CreateTemporaryDirectory (),
+ Xamarin.Cache.CreateTemporaryDirectory (),
+ Xamarin.Cache.CreateTemporaryDirectory (),
+ Xamarin.Cache.CreateTemporaryDirectory ()
+ }
+ };
+
+ var stream = createOptions.CreateStream ();
+
+ CollectionAssert.AreEqual (
+ createOptions.PathsToWatch,
+ stream.PathsBeingWatched);
+
+ Assert.AreEqual (0, stream.DeviceBeingWatched);
+ }
+
+ [Test]
+ public void TestPathsBeingWatchedRelativeToDevice ()
+ {
+ FSEventStreamCreateOptions createOptions = new () {
+ Flags = FileEvents | UseExtendedData,
+ DeviceToWatch = 123456789,
+ PathsToWatch = new [] { string.Empty }
+ };
+
+ var stream = createOptions.CreateStream ();
+
+ CollectionAssert.AreEqual (
+ createOptions.PathsToWatch,
+ stream.PathsBeingWatched);
+
+ Assert.AreEqual (123456789, stream.DeviceBeingWatched);
+ }
+
[Test]
public void TestFileEvents ()
=> RunTest (FileEvents);
@@ -101,6 +141,8 @@ namespace MonoTouchFixtures.CoreServices {
while (isWorking)
NSRunLoop.Current.RunUntil (NSDate.Now.AddSeconds (0.1));
+ Invalidate ();
+
if (_exceptions.Count > 0) {
if (_exceptions.Count > 1)
throw new AggregateException (_exceptions);