From 164696f0b43464e7f781f47c84a62e356c764755 Mon Sep 17 00:00:00 2001 From: Pireax Date: Thu, 7 Nov 2024 15:04:20 +0100 Subject: [PATCH] Implement the WaitForPropertyValue extension for other objects --- .../Extensions/DeviceExtensions.cs | 69 ------- .../Extensions/WatchableExtensions.cs | 174 ++++++++++++++++++ 2 files changed, 174 insertions(+), 69 deletions(-) create mode 100644 src/Linux.Bluetooth/Extensions/WatchableExtensions.cs diff --git a/src/Linux.Bluetooth/Extensions/DeviceExtensions.cs b/src/Linux.Bluetooth/Extensions/DeviceExtensions.cs index 544a1de..c76c865 100644 --- a/src/Linux.Bluetooth/Extensions/DeviceExtensions.cs +++ b/src/Linux.Bluetooth/Extensions/DeviceExtensions.cs @@ -56,75 +56,6 @@ public static Task> GetServicesAsync(this IDevice1 return BlueZManager.GetProxiesAsync(BluezConstants.GattServiceInterface, device); } - /// Wait for Device's Property and specified value to resolve. - /// Type of value. - /// Device. - /// Name of the property. - /// Value to wait for. - /// TimeSpan to wait for. - /// Task or exception. - /// On timeout a is thrown. - public static async Task WaitForPropertyValueAsync(this IDevice1 obj, string propertyName, T value, TimeSpan timeout) - { - // TODO: Make this available to other generated interfaces too, not just IDevice1. - // TODO: Change to Task versus throwing an error. - var (watchTask, watcher) = WaitForPropertyValueInternal(obj, propertyName, value); - var currentValue = await obj.GetAsync(propertyName); - - // https://stackoverflow.com/questions/390900/cant-operator-be-applied-to-generic-types-in-c - if (EqualityComparer.Default.Equals(currentValue, value)) - { - watcher.Dispose(); - return; - } - - await Task.WhenAny(new Task[] { watchTask, Task.Delay(timeout) }); - if (!watchTask.IsCompleted) - { - throw new TimeoutException($"Timed out waiting for '{propertyName}' to change to '{value}'."); - } - - // propogate any exceptions. - await watchTask; - } - - /// Wait for property and value. - /// Type of the value. - /// Device object. - /// Name of the property. - /// Value to wait for. - /// containing the Task ans Watcher. - private static (Task, IDisposable) WaitForPropertyValueInternal(IDevice1 obj, string propertyName, T value) - { - var taskSource = new TaskCompletionSource(); - - IDisposable watcher = null; - watcher = obj.WatchPropertiesAsync(propertyChanges => - { - try - { - if (propertyChanges.Changed.Any(kvp => kvp.Key == propertyName)) - { - var pair = propertyChanges.Changed.Single(kvp => kvp.Key == propertyName); - if (pair.Value.Equals(value)) - { - // Console.WriteLine($"[CHG] {propertyName}: {pair.Value}."); - taskSource.TrySetResult(true); - watcher.Dispose(); - } - } - } - catch (Exception ex) - { - Console.WriteLine($"Exception: {ex}"); - taskSource.SetException(ex); - watcher.Dispose(); - } - }); - - return (taskSource.Task, watcher); - } - private static async Task GetBatteryInternalAsync(string batteryInterface, IDevice1 device) { var battery = await Task.Run(() => diff --git a/src/Linux.Bluetooth/Extensions/WatchableExtensions.cs b/src/Linux.Bluetooth/Extensions/WatchableExtensions.cs new file mode 100644 index 0000000..6db4963 --- /dev/null +++ b/src/Linux.Bluetooth/Extensions/WatchableExtensions.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Tmds.DBus; + +namespace Linux.Bluetooth.Extensions +{ + public static class WatchableExtensions + { + /// Wait for Adapter's Property and specified value to resolve. + /// Type of value. + /// Adapter. + /// Name of the property. + /// Value to wait for. + /// TimeSpan to wait for. + /// 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); + + /// Wait for AdvertisingManager's Property and specified value to resolve. + /// Type of value. + /// AdvertisingManager. + /// Name of the property. + /// Value to wait for. + /// TimeSpan to wait for. + /// 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); + + /// Wait for Device's Property and specified value to resolve. + /// Type of value. + /// Device. + /// Name of the property. + /// Value to wait for. + /// TimeSpan to wait for. + /// 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); + + /// Wait for Battery's Property and specified value to resolve. + /// Type of value. + /// Battery. + /// Name of the property. + /// Value to wait for. + /// TimeSpan to wait for. + /// 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); + + /// Wait for GattService's Property and specified value to resolve. + /// Type of value. + /// GattService. + /// Name of the property. + /// Value to wait for. + /// TimeSpan to wait for. + /// 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); + + /// Wait for GattCharacteristic's Property and specified value to resolve. + /// Type of value. + /// GattCharacteristic. + /// Name of the property. + /// Value to wait for. + /// TimeSpan to wait for. + /// 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); + + /// Wait for GattDescriptor's Property and specified value to resolve. + /// Type of value. + /// GattDescriptor. + /// Name of the property. + /// Value to wait for. + /// TimeSpan to wait for. + /// 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); + + /// Wait for MediaControl's Property and specified value to resolve. + /// Type of value. + /// MediaControl. + /// Name of the property. + /// Value to wait for. + /// TimeSpan to wait for. + /// 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); + + /// + /// Wait for watchable objects property and specified value to resolve. + /// The and functions should be of the same object. + /// + /// Type of value. + /// GetAsync method of the watchable object. + /// WatchPropertiesAsync function of the watchable object. + /// Name of the property. + /// Value to wait for. + /// TimeSpan to wait for. + /// Task or exception. + /// On timeout a is thrown. + private static async Task WaitForPropertyValueInternalAsync( + Func> getAsync, + Func, Task> watchPropertiesAsync, + string propertyName, T value, TimeSpan timeout) + { + // TODO: Change to Task versus throwing an error. + var (watchTask, watcher) = WaitForPropertyValueInternal(watchPropertiesAsync, propertyName, value); + var currentValue = await getAsync(propertyName); + + // https://stackoverflow.com/questions/390900/cant-operator-be-applied-to-generic-types-in-c + if (EqualityComparer.Default.Equals(currentValue, value)) + { + watcher.Dispose(); + return; + } + + await Task.WhenAny(new Task[] { watchTask, Task.Delay(timeout) }); + if (!watchTask.IsCompleted) + { + throw new TimeoutException($"Timed out waiting for '{propertyName}' to change to '{value}'."); + } + + // propogate any exceptions. + await watchTask; + } + + /// Wait for property and value. + /// Type of the value. + /// Device object. + /// Name of the property. + /// Value to wait for. + /// containing the Task ans Watcher. + private static (Task, IDisposable) WaitForPropertyValueInternal(Func, Task> watchPropertiesAsync, string propertyName, T value) + { + var taskSource = new TaskCompletionSource(); + + IDisposable watcher = null; + watcher = watchPropertiesAsync(propertyChanges => + { + try + { + if (propertyChanges.Changed.Any(kvp => kvp.Key == propertyName)) + { + var pair = propertyChanges.Changed.Single(kvp => kvp.Key == propertyName); + if (pair.Value.Equals(value)) + { + // Console.WriteLine($"[CHG] {propertyName}: {pair.Value}."); + taskSource.TrySetResult(true); + watcher.Dispose(); + } + } + } + catch (Exception ex) + { + Console.WriteLine($"Exception: {ex}"); + taskSource.SetException(ex); + watcher.Dispose(); + } + }); + + return (taskSource.Task, watcher); + } + } +}