namespace PSLauncher.Core.Lan; /// /// URL d'un peer LAN qui a déjà la version demandée et a passé la vérification /// (taille + SHA-256 du manifest match la réponse /v1/has/). À utiliser /// dans le DownloadJob comme URL initiale au lieu de l'URL OVH. /// public sealed record PeerSource(Uri ZipUrl, string PeerHost); /// /// Helper interne aux probers (PeerSourceResolver, PeerManifestFetcher) pour /// fusionner peers actifs (beacons UDP récents), peers connus (cache disque), /// peers manuels (config). Évite la duplication entre les 2 fetchers. /// internal static class PeerCandidates { public static List Build(ILanDiscoveryService discovery, IEnumerable manualUrls) { var candidates = new List(); var seen = new HashSet(StringComparer.OrdinalIgnoreCase); // 1) Peers actifs (UDP beacon < 60s) — les plus fiables foreach (var p in discovery.ListDiscovered()) { var url = $"http://{p.Host}:{p.Port}"; if (seen.Add(url)) candidates.Add(url); } // 2) Peers connus (cache disque, runs précédents) — essayés rapidement même // si pas encore reçu de beacon ce run (cold start, < 15s d'uptime) foreach (var p in discovery.ListKnown()) { var url = $"http://{p.Host}:{p.Port}"; if (seen.Add(url)) candidates.Add(url); } // 3) Peers manuels (config user) — fallback / override explicite foreach (var url in manualUrls) { var trimmed = url.Trim().TrimEnd('/'); if (string.IsNullOrEmpty(trimmed)) continue; if (seen.Add(trimmed)) candidates.Add(trimmed); } return candidates; } } /// /// Trouve un peer LAN qui a la version demandée. Combine peers découverts auto /// (UDP discovery) et peers manuels (config), probe chacun avec un timeout court, /// retourne le premier qui matche le SHA-256 attendu. Null si aucun → fallback OVH. /// /// Budget total : ProbeTimeoutMs × max 5 peers = ~5 s pire cas. Ne ralentit /// jamais le path OVH au-delà. /// public interface IPeerSourceResolver { Task TryResolveAsync( string version, long expectedSize, string expectedSha256, CancellationToken ct); }