diff --git a/src/Linux.Bluetooth/AdvertisementMonitor.cs b/src/Linux.Bluetooth/AdvertisementMonitor.cs index aa9f2c2..4db39c2 100644 --- a/src/Linux.Bluetooth/AdvertisementMonitor.cs +++ b/src/Linux.Bluetooth/AdvertisementMonitor.cs @@ -9,13 +9,23 @@ namespace Linux.Bluetooth /// Advertisement Monitor class. /// Requires 'Experimental = true' and 'KernelExperimental = true' in BlueZ main.conf /// - public class AdvertisementMonitor : IAdvertisementMonitor1, IObjectManager + public class AdvertisementMonitor : IAdvertisementMonitor1, IObjectManager, IDisposable { public ObjectPath ObjectPath { get; } public event EventHandler? DeviceFoundEvent; public event EventHandler? DeviceLostEvent; + /// + /// Raised when the D-Bus connection backing this monitor drops. + /// + /// + /// The connection is created by this monitor and never reconnects, so BlueZ loses the monitor + /// registration for good: no further DeviceFoundEvent or DeviceLostEvent is ever raised. Dispose this + /// monitor and create a new one to resume monitoring. + /// + public event EventHandler? ConnectionLost; + private readonly Connection _conn; private readonly AdvertisementMonitor1Properties _properties; private readonly IAdvertisementMonitorManager1 _manager; @@ -40,14 +50,37 @@ public async Task StartAsync() await _conn.ConnectAsync(); await _conn.RegisterObjectAsync(this); await _manager.RegisterMonitorAsync(ObjectPath); + + _conn.StateChanged += OnConnectionStateChanged; } public async Task StopAsync() { + _conn.StateChanged -= OnConnectionStateChanged; + await _manager.UnregisterMonitorAsync(ObjectPath); _conn.UnregisterObject(this); } + private void OnConnectionStateChanged(object sender, ConnectionStateChangedEventArgs e) + { + if (e.State == ConnectionState.Disconnected) + { + ConnectionLost?.Invoke(this, EventArgs.Empty); + } + } + + /// + /// Closes the D-Bus connection opened by the constructor. Each monitor owns its own connection, so a + /// monitor dropped without disposing leaks its socket until finalization. + /// + public void Dispose() + { + _conn.StateChanged -= OnConnectionStateChanged; + _conn.Dispose(); + GC.SuppressFinalize(this); + } + public Task ActivateAsync() { Console.WriteLine("Advertisement monitoring activated"); diff --git a/src/Linux.Bluetooth/Extensions/WatchableExtensions.cs b/src/Linux.Bluetooth/Extensions/WatchableExtensions.cs index 8dd2a81..5887802 100644 --- a/src/Linux.Bluetooth/Extensions/WatchableExtensions.cs +++ b/src/Linux.Bluetooth/Extensions/WatchableExtensions.cs @@ -17,7 +17,7 @@ public static class WatchableExtensions /// Task or exception. /// On timeout a is thrown. public static Task WaitForPropertyValueAsync(this IAdapter1 obj, string propertyName, T value, TimeSpan timeout) - => WaitForPropertyValueInternalAsync(obj.GetAsync, obj.WatchPropertiesAsync, propertyName, value, timeout); + => WaitForPropertyValueAsync(obj.GetAsync, obj.WatchPropertiesAsync, propertyName, value, timeout); /// Wait for AdvertisingManager's Property and specified value to resolve. /// Type of value. @@ -28,7 +28,7 @@ public static Task WaitForPropertyValueAsync(this IAdapter1 obj, string prope /// Task or exception. /// On timeout a is thrown. public static Task WaitForPropertyValueAsync(this ILEAdvertisingManager1 obj, string propertyName, T value, TimeSpan timeout) - => WaitForPropertyValueInternalAsync(obj.GetAsync, obj.WatchPropertiesAsync, propertyName, value, timeout); + => WaitForPropertyValueAsync(obj.GetAsync, obj.WatchPropertiesAsync, propertyName, value, timeout); /// Wait for Device's Property and specified value to resolve. /// Type of value. @@ -39,7 +39,7 @@ public static Task WaitForPropertyValueAsync(this ILEAdvertisingManager1 obj, /// Task or exception. /// On timeout a is thrown. public static Task WaitForPropertyValueAsync(this IDevice1 obj, string propertyName, T value, TimeSpan timeout) - => WaitForPropertyValueInternalAsync(obj.GetAsync, obj.WatchPropertiesAsync, propertyName, value, timeout); + => WaitForPropertyValueAsync(obj.GetAsync, obj.WatchPropertiesAsync, propertyName, value, timeout); /// Wait for Battery's Property and specified value to resolve. /// Type of value. @@ -50,7 +50,7 @@ public static Task WaitForPropertyValueAsync(this IDevice1 obj, string proper /// Task or exception. /// On timeout a is thrown. public static Task WaitForPropertyValueAsync(this IBattery1 obj, string propertyName, T value, TimeSpan timeout) - => WaitForPropertyValueInternalAsync(obj.GetAsync, obj.WatchPropertiesAsync, propertyName, value, timeout); + => WaitForPropertyValueAsync(obj.GetAsync, obj.WatchPropertiesAsync, propertyName, value, timeout); /* /// Wait for GattService's Property and specified value to resolve. @@ -62,7 +62,7 @@ public static Task WaitForPropertyValueAsync(this IBattery1 obj, string prope /// Task or exception. /// On timeout a is thrown. public static Task WaitForPropertyValueAsync(this IGattService1 obj, string propertyName, T value, TimeSpan timeout) - => WaitForPropertyValueInternalAsync(obj.GetAsync, obj.WatchPropertiesAsync, propertyName, value, timeout); + => WaitForPropertyValueAsync(obj.GetAsync, obj.WatchPropertiesAsync, propertyName, value, timeout); /// Wait for GattCharacteristic's Property and specified value to resolve. /// Type of value. @@ -73,7 +73,7 @@ public static Task WaitForPropertyValueAsync(this IGattService1 obj, string p /// Task or exception. /// On timeout a is thrown. public static Task WaitForPropertyValueAsync(this IGattCharacteristic1 obj, string propertyName, T value, TimeSpan timeout) - => WaitForPropertyValueInternalAsync(obj.GetAsync, obj.WatchPropertiesAsync, propertyName, value, timeout); + => WaitForPropertyValueAsync(obj.GetAsync, obj.WatchPropertiesAsync, propertyName, value, timeout); /// Wait for GattDescriptor's Property and specified value to resolve. /// Type of value. @@ -84,7 +84,7 @@ public static Task WaitForPropertyValueAsync(this IGattCharacteristic1 obj, s /// Task or exception. /// On timeout a is thrown. public static Task WaitForPropertyValueAsync(this IGattDescriptor1 obj, string propertyName, T value, TimeSpan timeout) - => WaitForPropertyValueInternalAsync(obj.GetAsync, obj.WatchPropertiesAsync, propertyName, value, timeout); + => WaitForPropertyValueAsync(obj.GetAsync, obj.WatchPropertiesAsync, propertyName, value, timeout); */ /// Wait for MediaControl's Property and specified value to resolve. @@ -96,7 +96,7 @@ public static Task WaitForPropertyValueAsync(this IGattDescriptor1 obj, strin /// Task or exception. /// On timeout a is thrown. public static Task WaitForPropertyValueAsync(this IMediaControl1 obj, string propertyName, T value, TimeSpan timeout) - => WaitForPropertyValueInternalAsync(obj.GetAsync, obj.WatchPropertiesAsync, propertyName, value, timeout); + => WaitForPropertyValueAsync(obj.GetAsync, obj.WatchPropertiesAsync, propertyName, value, timeout); /// /// Wait for watchable objects property and specified value to resolve. @@ -110,10 +110,13 @@ public static Task WaitForPropertyValueAsync(this IMediaControl1 obj, string /// TimeSpan to wait for. /// Task or exception. /// On timeout a is thrown. - private static async Task WaitForPropertyValueInternalAsync( + public static async Task WaitForPropertyValueAsync( Func> getAsync, - Func, Task> watchPropertiesAsync, - string propertyName, T value, TimeSpan timeout) + Func, + Task> watchPropertiesAsync, + string propertyName, + T value, + TimeSpan timeout) { // TODO: Change to Task versus throwing an error. var (watchTask, watcher) = WaitForPropertyValueInternal(watchPropertiesAsync, propertyName, value);