muzika-gromche/MuzikaGromche/Via/DevicePool.cs

170 lines
3.9 KiB
C#

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<T> : IDisposable
where T : IDevicePoolDelegate
{
private readonly IDevicePoolFactory<T> sidecarFactory;
// Async synchronization
private readonly SemaphoreSlim semaphore;
// Async access, use semaphore!
private readonly Dictionary<string, (HidDevice, T)> existing = [];
private bool updating = false;
public DevicePool(IDevicePoolFactory<T> 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<T> action)
{
WithSemaphore(() => ForEachUnsafe(action));
}
private void ForEachUnsafe(Action<T> 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>
{
T? Create(HidDevice hidDevice);
}