forked from nikita/muzika-gromche
170 lines
3.9 KiB
C#
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);
|
|
}
|