/
ImplementationDiscovery.cs
91 lines (76 loc) · 3.19 KB
/
ImplementationDiscovery.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
// Copyright Bastian Eicher et al.
// Licensed under the GNU Lesser Public License
using Makaretu.Dns;
using NanoByte.Common.Threading;
using ZeroInstall.Archives;
namespace ZeroInstall.Services.Fetchers;
/// <summary>
/// Discovers implementations in implementation stores on other machines in the local network.
/// </summary>
public class ImplementationDiscovery : IImplementationDiscovery, IDisposable
{
private readonly ServiceDiscovery _serviceDiscovery = new();
private readonly Timer _queryTimer;
private readonly ConcurrentSet<ImplementationDiscoveryInstance> _instances = [];
private event Action<ImplementationDiscoveryInstance> InstanceDiscovered;
/// <summary>
/// Starts discovering implementation stores on other machines in the local network.
/// </summary>
public ImplementationDiscovery()
{
InstanceDiscovered += _instances.Add;
_serviceDiscovery.ServiceInstanceDiscovered += OnInstanceDiscovered;
_queryTimer = new(_ =>
{
try
{
_serviceDiscovery.QueryServiceInstances(ImplementationServer.DnsServiceName);
}
#region Error handling
catch
{
// _serviceDiscovery might already be disposed
}
#endregion
}, null, TimeSpan.Zero, period: TimeSpan.FromSeconds(2));
}
/// <summary>
/// Stops discovering implementation stores.
/// </summary>
public void Dispose()
{
_queryTimer.Dispose();
_serviceDiscovery.Dispose();
}
/// <summary>
/// Controls whether implementation stores hosted on the local machine should be excluded from discovery.
/// </summary>
internal static bool ExcludeLocalMachine = true;
private void OnInstanceDiscovered(object? sender, ServiceInstanceDiscoveryEventArgs e)
{
if (!e.ServiceInstanceName.ToString()!.EndsWith(ImplementationServer.DnsServiceName + ".local")
|| e.Message.AdditionalRecords.OfType<SRVRecord>().FirstOrDefault() is not {Port: var port}) return;
var ips = e.Message.AdditionalRecords.OfType<AddressRecord>().Select(x => x.Address);
if (ExcludeLocalMachine) ips = ips.Except(MulticastService.GetIPAddresses());
ips = ips.ToList();
if (ips.Any()) InstanceDiscovered(new(port, ips, e.ServiceInstanceName));
}
/// <inheritdoc/>
public Uri GetImplementation(ManifestDigest manifestDigest, CancellationToken cancellationToken)
{
var racer = new ResultRacer<Uri>(cancellationToken);
void FindImplementation(ImplementationDiscoveryInstance instance)
=> racer.TrySetResultAsync(innerCancellationToken => instance.GetImplementationAsync(manifestDigest, innerCancellationToken));
try
{
InstanceDiscovered += FindImplementation;
foreach (var instance in _instances.AsEnumerable())
Task.Run(() => FindImplementation(instance), cancellationToken);
return racer.GetResult();
}
finally
{
InstanceDiscovered -= FindImplementation;
}
}
}