using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using HidSharp; namespace MuzikaGromche.Via; class DevicePool : IDisposable where T : IDevicePoolDelegate { private readonly IDevicePoolFactory sidecarFactory; // Async synchronization private readonly SemaphoreSlim semaphore; // Async access, use semaphore! private readonly Dictionary existing = []; private bool updating = false; public DevicePool(IDevicePoolFactory sidecarFactory) { semaphore = new SemaphoreSlim(1, 1); this.sidecarFactory = sidecarFactory; DeviceList.Local.Changed += (_sender, _args) => { Console.WriteLine($"Pool Changed"); _ = Task.Run(async () => { await Task.Delay(300); if (!updating) { updating = true; UpdateConnections(); updating = false; return; } }); }; UpdateConnections(); } private void WithSemaphore(Action action) { semaphore.Wait(); try { action.Invoke(); } finally { semaphore.Release(); } } public void Dispose() { Console.WriteLine($"Pool Dispose"); Clear(); } private void Clear() { WithSemaphore(ClearUnsafe); } private void ClearUnsafe() { Console.WriteLine($"Pool Clear"); ForEachUnsafe((sidecar) => sidecar.Dispose()); existing.Clear(); } private void ClearUnsafe(string path) { if (existing.Remove(path, out var data)) { var (_, sidecar) = data; sidecar.Dispose(); } } public void Restore() { Console.WriteLine($"Pool Restore"); ForEach((sidecar) => sidecar.Restore()); } private void UpdateConnections() { WithSemaphore(() => { // set of removed devices to be cleaned up var removed = existing.Keys.ToHashSet(); foreach (var hidDevice in DeviceList.Local.GetHidDevices()) { try { // surely path is enough to uniquely differentiate var path = hidDevice.DevicePath; if (existing.ContainsKey(path)) { // not gone anywhere removed.Remove(path); continue; } var sidecar = sidecarFactory.Create(hidDevice); if (sidecar != null) { Console.WriteLine($"Pool Added {path}"); existing[path] = (hidDevice, sidecar); } } catch (Exception) { } } foreach (var path in removed) { try { ClearUnsafe(path); } catch (Exception) { } } }); } public void ForEach(Action action) { WithSemaphore(() => ForEachUnsafe(action)); } private void ForEachUnsafe(Action action) { foreach (var (_, sidecar) in existing.Values) { try { action(sidecar); } // ignore. the faulty device will be removed soon. catch (IOException) { } catch (TimeoutException) { } } } } /// Dispose should call Restore interface IDevicePoolDelegate : IDisposable { void Restore(); } interface IDevicePoolFactory { T? Create(HidDevice hidDevice); }