Commit Graph

2 Commits

Author SHA1 Message Date
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
65ff8541b7 v0.25.4 — Cache LAN P2P : un PC sert les ZIPs aux autres du LAN
Feature : sur un site multi-PCs (salle VR), un PC peut servir les ZIPs PROSERVE
qu'il a déjà téléchargés aux autres PCs du LAN au lieu de les forcer à re-DL
14 Go depuis OVH. Auto-discovery par UDP broadcast, fallback transparent OVH
si aucun peer ne répond, intégrité préservée par SHA-256 du manifest signé Ed25519.

Architecture (src/PSLauncher.Core/Lan/) :
- ZipCacheStore : cache local des ZIPs téléchargés (sidecar SHA-256 pour servir
  sans re-hash). Pruning auto au top-N par mtime, protège les versions encore
  installées. Remplace le `File.Delete(zipPath)` post-install historique.
- LanCacheServer : serveur HTTP local sur TcpListener (PAS HttpListener — il
  exigeait une URL ACL admin qui aurait obligé à coder un netsh dans l'installer).
  Routes GET /v1/has/{ver} (probe) + GET /v1/zip/{ver} (Range support manuel,
  ~80 LOC de parsing HTTP). Filtre RFC1918/link-local sur RemoteEndPoint avant
  toute IO, regex stricte sur version (anti-path-traversal), SemaphoreSlim(4)
  pour rate-limiter à 4 DL concurrents.
- LanDiscoveryService : émet beacon UDP (255.255.255.255:47624, JSON avec host+
  port+versions) toutes les 15 s en mode serveur ; écoute en mode client/serveur
  avec TTL 60 s sur les peers. Pure .NET, zero NuGet (pas de mDNS deps).
- PeerSourceResolver : avant chaque DL, probe les peers (auto-découverts +
  manuels) avec timeout 1 s × max 5 peers. Vérifie size + SHA-256 contre le
  manifest avant de retourner une URL peer. Si null → fallback OVH transparent.

Modifs flow d'install (MainViewModel) :
- Avant GetSignedDownloadUrlAsync, tente PeerSourceResolver. Si peer trouvé,
  utilise son URL directement (le DownloadManager multi-segment marche tel quel).
- RefreshUrlAsync handler : pour les peers, retourne la même URL (pas d'expiration
  HMAC). Pour OVH, refresh du HMAC inchangé.
- Post-install : remplace File.Delete(zipPath) par ZipCacheStore.Promote + Prune.

Sidebar info (visible en permanence dans le coin bas-gauche) :
- Lignes "Serveur ON/KO/—", "Client ON/—", "Peers N" — refresh auto toutes les
  10 s via DispatcherTimer. Diagnostic instantané sans naviguer dans Settings.

Footer pendant le DL : préfixe différencié pour rendre la source évidente —
"📡 LAN 10.0.0.5 • v… : … Mo/s" vs "⬇ v… : … Mo/s" (OVH).

Settings → Cache LAN P2P : checkboxes ServerEnabled/ClientEnabled, ports HTTP/UDP,
MaxCachedVersions, statut serveur live, liste peers découverts, champ multi-line
peers manuels (override/fallback de l'auto-discovery). Toggle des flags →
prompt "Restart launcher requis" car les hosted services lisent leur config au
boot uniquement.

Fix critique au passage : App.OnStartup appelait .Build() mais JAMAIS .StartAsync()
sur l'IHost — du coup AUCUN HostedService n'a jamais tourné depuis l'introduction
du pattern. Ajout de StartAsync (fire-and-forget avec log d'erreur) et StopAsync
propre dans OnExit (timeout 5 s pour libérer les sockets sans TIME_WAIT bloquant).

Installer (PSLauncher.iss) : règles firewall TCP 47623 + UDP 47624 préposées
via netsh advfirewall, profile=private,domain (PAS public — safety net même
si l'utilisateur connecte le PC à un Wi-Fi public). Inutile si firewall OFF
(cas du déploiement actuel) mais pose les rails pour les déploiements futurs.

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