57 lines
2.0 KiB
C#
57 lines
2.0 KiB
C#
using CoreFoundation;
|
|||
|
|
using Network;
|
||
|
|
using VoiceCat.Core;
|
||
|
|
|
||
|
|
namespace VoiceCat.iOS;
|
||
|
|
|
||
|
|
// Watches which interface actually carries traffic so a handover can be acted on the instant it
|
||
|
|
// happens instead of after the control keepalive proves the old path dead.
|
||
|
|
//
|
||
|
|
// The signal must be narrow. Roaming between access points, a lift ride, a minute of bad
|
||
|
|
// cellular: those keep the same interface and the same source address, so TCP survives them and
|
||
|
|
// a reconnect would be pure damage. Only the set of satisfied interfaces changing — Wi-Fi to
|
||
|
|
// cellular, one physical link to another — strands the existing sockets on a source address that
|
||
|
|
// no longer exists, and that is the only thing reported here.
|
||
|
|
internal sealed class IosNetworkPathMonitor : IDisposable
|
||
|
|
{
|
||
|
|
internal static IosNetworkPathMonitor Shared { get; } = new();
|
||
|
|
private readonly DispatchQueue queue = new("voicecat.path");
|
||
|
|
private readonly object gate = new();
|
||
|
|
private readonly ControlPathWatcher watcher = new();
|
||
|
|
private NWPathMonitor? monitor;
|
||
|
|
private bool started;
|
||
|
|
|
||
|
|
// Raised on the monitor queue when the carrying interface changed and the new path is usable.
|
||
|
|
internal event Action<string>? InterfaceChanged;
|
||
|
|
|
||
|
|
internal void Start()
|
||
|
|
{
|
||
|
|
lock (gate)
|
||
|
|
{
|
||
|
|
if (started) return;
|
||
|
|
started = true;
|
||
|
|
monitor = new NWPathMonitor();
|
||
|
|
monitor.SetQueue(queue);
|
||
|
|
monitor.SnapshotHandler = OnPath;
|
||
|
|
monitor.Start();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void OnPath(NWPath path)
|
||
|
|
{
|
||
|
|
List<string> interfaces = [];
|
||
|
|
if (path.Status == NWPathStatus.Satisfied)
|
||
|
|
path.EnumerateInterfaces(item => { interfaces.Add($"{item.InterfaceType}:{item.Name}"); return true; });
|
||
|
|
if (watcher.Observe(path.Status == NWPathStatus.Satisfied, interfaces))
|
||
|
|
InterfaceChanged?.Invoke(string.Join(",", interfaces));
|
||
|
|
}
|
||
|
|
|
||
|
|
public void Dispose()
|
||
|
|
{
|
||
|
|
lock (gate)
|
||
|
|
{
|
||
|
|
monitor?.Cancel(); monitor?.Dispose(); monitor = null; started = false; watcher.Reset();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|