Files
PS_Launcher/src/PSLauncher.Core/Lan/IPeerSourceResolver.cs
j.foucher 3d691c3e38 v0.25.10 — LAN P2P offline-friendly : manifest fallback + perf
Fonctionne entièrement offline si un PC du LAN a fetché OVH récemment :
les autres PCs récupèrent le manifest + les ZIPs depuis lui sans Internet.
Discovery des peers quasi-instantanée au cold start, fail-fast partout sur
les fetch OVH pour ne pas bloquer la UI.

== Manifest fallback offline ==

- IManifestCache + ManifestCache (Core/Manifests/) : cache disque du dernier
  manifest réussi (JSON brut byte-for-byte pour préserver la signature Ed25519).
  Path : %LocalAppData%\PSLauncher\manifest-cache.json
- IPeerManifestFetcher + PeerManifestFetcher (Core/Lan/) : récupère le
  manifest brut depuis un peer LAN via GET /v1/manifest. Probe parallèle
  avec timeout court par peer.
- LanCacheServer : nouvelle route GET /v1/manifest qui sert le cache disque
  byte-for-byte aux clients sans Internet.
- ManifestService.FetchAsync : online check d'abord (ping ICMP 8.8.8.8 +
  fallback TCP connect au serveur OVH si firewall bloque ICMP, timeout
  800ms × 2). Si online → OVH only (canonique, voit toutes les versions).
  Si offline → peer LAN puis cache disque local.
- ManifestSource enum exposé via IManifestService.LastSource → call sites
  peuvent prendre des décisions éclairées (skip release notes, badge UI).

== Discovery active des peers (cold start instantané) ==

- LanCacheServer : nouvelle route GET /v1/info qui retourne {host, port,
  versions} (même payload que les beacons UDP).
- LanDiscoveryService : bootstrap au démarrage qui probe en parallèle tous
  les peers connus (cache disque) + manuels (config) via /v1/info, timeout
  1s par peer. Les peers qui répondent sont injectés dans _peers avec
  lastSeen=now → apparaissent immédiatement dans ListDiscovered() et donc
  dans la sidebar + PeerSourceResolver. Plus besoin d'attendre les 15s du
  prochain beacon UDP.
- ILanDiscoveryService.ListKnown() exposé pour que les fetchers puissent
  utiliser le cache disque comme seed.
- LocalConfig.LanCache.ManualPeerUrls : default ["http://10.0.4.100:47623"]
  (IP fixe du serveur ASTERION dans toutes les salles VR).

== Timeouts courts sur tous les fetch OVH (fail-fast offline) ==

- ManifestService : 3s pour le manifest fetch (vs 100s par défaut)
- LicenseService.ValidateAsync : 3s (résolvait le délai de ~15s pendant
  CheckForUpdates en offline — HttpClient global a Timeout=Infinite,
  sans CTS court ça hangait jusqu'à l'abandon SYN TCP par l'OS)
- LicenseService.GetSignedDownloadUrlAsync : 5s
- ManifestService.FetchReleaseNotesAsync : 5s

== UI ==

- StatusMessage post-Check Updates suffixé avec la source du manifest :
  "🌐 OVH (en ligne)" / "📡 Peer LAN (hors ligne)" / "💾 Cache local (hors ligne)"
- InstallVersionAsync : skip le fetch release notes si manifest source
  != OVH → dialog s'ouvre instantanément avec "indisponible" au lieu
  d'attendre 5s de timeout.

== Latences attendues ==

| Scénario | Avant | Après |
|---|---|---|
| Vérifier MAJ online | ~5s | ~500ms |
| Vérifier MAJ offline | ~15-20s | ~5s |
| Cold start discovery (1 peer connu) | jusqu'à 15s | ~200ms |
| Click Install offline | ~5s release notes timeout | instantané |

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 17:51:50 +02:00

63 lines
2.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
namespace PSLauncher.Core.Lan;
/// <summary>
/// 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 <c>/v1/has/</c>). À utiliser
/// dans le DownloadJob comme URL initiale au lieu de l'URL OVH.
/// </summary>
public sealed record PeerSource(Uri ZipUrl, string PeerHost);
/// <summary>
/// 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.
/// </summary>
internal static class PeerCandidates
{
public static List<string> Build(ILanDiscoveryService discovery, IEnumerable<string> manualUrls)
{
var candidates = new List<string>();
var seen = new HashSet<string>(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;
}
}
/// <summary>
/// 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 : <c>ProbeTimeoutMs</c> × max 5 peers = ~5 s pire cas. Ne ralentit
/// jamais le path OVH au-delà.
/// </summary>
public interface IPeerSourceResolver
{
Task<PeerSource?> TryResolveAsync(
string version,
long expectedSize,
string expectedSha256,
CancellationToken ct);
}