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>
This commit is contained in:
@@ -603,6 +603,64 @@ public static class Strings
|
||||
public static string SettingsApiBackups => SettingsReportBackups;
|
||||
public static string SettingsApiMaxBackups => SettingsReportMaxBackups;
|
||||
|
||||
// ---- Cache LAN P2P ----
|
||||
public static string StatusDownloadingFromPeer(string host, string version) => T(
|
||||
$"📡 Téléchargement de v{version} depuis {host} (LAN)…",
|
||||
$"📡 Downloading v{version} from {host} (LAN)…",
|
||||
$"📡 正在从 {host} 下载 v{version} (局域网)…",
|
||||
$"📡 กำลังดาวน์โหลด v{version} จาก {host} (LAN)…",
|
||||
$"📡 جارٍ تنزيل v{version} من {host} (LAN)…"
|
||||
);
|
||||
public static string SettingsLanCache => T("CACHE LAN P2P", "LAN P2P CACHE", "局域网 P2P 缓存", "แคช LAN P2P", "ذاكرة التخزين المؤقت LAN P2P");
|
||||
public static string SettingsLanCacheServerEnabled => T(
|
||||
"Activer le mode serveur (partager les ZIPs aux PCs du LAN)",
|
||||
"Enable server mode (share ZIPs to LAN PCs)",
|
||||
"启用服务器模式(与局域网 PC 共享 ZIP)",
|
||||
"เปิดใช้โหมดเซิร์ฟเวอร์ (แชร์ ZIP ไปยัง PC ใน LAN)",
|
||||
"تمكين وضع الخادم (مشاركة ZIPs مع أجهزة الكمبيوتر في LAN)"
|
||||
);
|
||||
public static string SettingsLanCacheClientEnabled => T(
|
||||
"Activer le mode client (chercher les ZIPs sur le LAN avant OVH)",
|
||||
"Enable client mode (look for ZIPs on the LAN before OVH)",
|
||||
"启用客户端模式(在 OVH 之前在局域网上查找 ZIP)",
|
||||
"เปิดใช้โหมดไคลเอนต์ (ค้นหา ZIP บน LAN ก่อน OVH)",
|
||||
"تمكين وضع العميل (البحث عن ZIPs على LAN قبل OVH)"
|
||||
);
|
||||
public static string SettingsLanCacheServerPort => T("Port HTTP serveur", "Server HTTP port", "服务器 HTTP 端口", "พอร์ต HTTP เซิร์ฟเวอร์", "منفذ HTTP الخادم");
|
||||
public static string SettingsLanCacheDiscoveryPort => T("Port UDP découverte", "Discovery UDP port", "发现 UDP 端口", "พอร์ต UDP การค้นพบ", "منفذ UDP الاكتشاف");
|
||||
public static string SettingsLanCacheMaxCached => T("ZIPs en cache (N)", "Cached ZIPs (N)", "缓存 ZIP (N)", "ZIP ในแคช (N)", "ZIPs المخزنة (N)");
|
||||
public static string SettingsLanCacheDiscoveredPeers => T("Peers découverts automatiquement", "Auto-discovered peers", "自动发现的 Peers", "Peers ที่ค้นพบอัตโนมัติ", "النظراء المكتشفون تلقائياً");
|
||||
public static string SettingsLanCacheManualPeers => T("Peers manuels (1 URL par ligne, ex: http://10.0.0.5:47623)", "Manual peers (1 URL per line, e.g. http://10.0.0.5:47623)", "手动 Peers(每行 1 个 URL,例如 http://10.0.0.5:47623)", "Peers ด้วยตนเอง (1 URL ต่อบรรทัด เช่น http://10.0.0.5:47623)", "النظراء يدوياً (URL واحد لكل سطر، مثل http://10.0.0.5:47623)");
|
||||
public static string SettingsLanCacheNoDiscovered => T(
|
||||
"Aucun peer détecté pour le moment (les beacons sont émis toutes les 15 s).",
|
||||
"No peer detected yet (beacons are sent every 15 s).",
|
||||
"目前未检测到任何 peer (信标每 15 秒发送一次).",
|
||||
"ยังไม่มี peer ตรวจพบ (สัญญาณถูกส่งทุก 15 วินาที).",
|
||||
"لم يتم اكتشاف أي peer بعد (يتم إرسال إشارات كل 15 ثانية)."
|
||||
);
|
||||
public static string SettingsLanCacheServerStatus(string endpoints, int versions) => T(
|
||||
$"✓ Serveur actif sur {endpoints} — {versions} version(s) en cache",
|
||||
$"✓ Server running on {endpoints} — {versions} version(s) cached",
|
||||
$"✓ 服务器正在 {endpoints} 上运行 — 缓存了 {versions} 个版本",
|
||||
$"✓ เซิร์ฟเวอร์ทำงานบน {endpoints} — แคช {versions} เวอร์ชัน",
|
||||
$"✓ الخادم يعمل على {endpoints} — {versions} نسخة مخزنة"
|
||||
);
|
||||
public static string SettingsLanCacheServerOff => T(
|
||||
"Mode serveur désactivé — ce PC ne sert pas les ZIPs aux peers du LAN.",
|
||||
"Server mode disabled — this PC does not share ZIPs to LAN peers.",
|
||||
"服务器模式已禁用 — 此 PC 不会与局域网 peers 共享 ZIP。",
|
||||
"โหมดเซิร์ฟเวอร์ถูกปิด — PC นี้จะไม่แชร์ ZIP ไปยัง LAN peers",
|
||||
"وضع الخادم معطل — هذا الكمبيوتر لا يشارك ZIPs مع نظراء LAN."
|
||||
);
|
||||
public static string MsgBoxLanCacheChange => T("Cache LAN P2P", "LAN P2P cache", "局域网 P2P 缓存", "แคช LAN P2P", "ذاكرة التخزين المؤقت LAN P2P");
|
||||
public static string MsgLanCacheRestart => T(
|
||||
"Le démarrage / arrêt du serveur ou client LAN nécessite un redémarrage du launcher pour prendre effet.\n\nRedémarrer maintenant ?",
|
||||
"Starting / stopping the LAN server or client requires a launcher restart to take effect.\n\nRestart now?",
|
||||
"启动/停止局域网服务器或客户端需要重新启动启动器才能生效。\n\n现在重新启动?",
|
||||
"การเริ่ม/หยุดเซิร์ฟเวอร์หรือไคลเอนต์ LAN ต้องรีสตาร์ทตัวเปิดเพื่อให้มีผล\n\nรีสตาร์ทตอนนี้?",
|
||||
"يتطلب بدء / إيقاف خادم أو عميل LAN إعادة تشغيل المشغل لتفعيل التغيير.\n\nإعادة التشغيل الآن؟"
|
||||
);
|
||||
|
||||
// ---- Backups Report tool ----
|
||||
public static string SettingsReportBackups => T("Backups disponibles", "Available backups", "可用备份", "สำรองข้อมูลที่มี", "النسخ الاحتياطية المتاحة");
|
||||
public static string SettingsReportMaxBackups => T("Conserver N backups", "Keep N backups", "保留 N 个备份", "เก็บสำรอง N ชุด", "الاحتفاظ بـ N نسخة احتياطية");
|
||||
|
||||
Reference in New Issue
Block a user