Bug rapporté : après update client vers 1.0.9, un opérateur ne voyait qu'UNE
ligne alors que le manifest en contenait deux au même numéro. Root cause :
mon refactor v1.0.8 keyait les rows par folder name résolu via
GetInstallFolderName(). Sur les manifests existants (où l'opérateur n'avait
pas encore migré vers un installFolderTemplate distinct par channel), les
deux entries résolvent au même dossier « PROSERVE v1.5.4.32 » — le dico
TryAdd droppait la seconde silencieusement (juste un log warn).
Fix : row identity passe sur l'Id de l'entrée manifest (unique par entry,
auto-généré server-side depuis longtemps via generate_entry_id()). Le folder
name reste utilisé pour le matching installed ↔ remote quand l'entryId
manque (installs d'avant v1.0.8 qui n'ont pas encore été re-installés).
Bénéfices :
• Les deux entries s'affichent MÊME si elles partagent un
installFolderTemplate. L'install guard côté client bloquera l'écrasement
au moment de l'install avec le message clair habituel.
• Migration transparente : les installs existants continuent d'être matchés
par folder name tant qu'ils n'ont pas d'EntryId dans leur meta. Au
ré-install, le meta reçoit son EntryId et le matching devient canonique.
Détails :
── Model ─────────────────────────────────────────────────────────────
• InstalledVersion : nouveau champ optionnel EntryId (default null pour la
rétro-compat des call sites existants).
── Registry ──────────────────────────────────────────────────────────
• Scan() populate EntryId via TryReadEntryId(dir). null si le fichier meta
n'existe pas ou si la clé est absente (install antérieur à v1.0.8).
── MainViewModel.RebuildList ─────────────────────────────────────────
• remoteByRowKey : keyé par Id de l'entry (fallback folder name si le
manifest est très vieux et n'a pas d'id).
• installedByRowKey : keyé par EntryId lu du meta ; fallback = folder name
; fallback ultime = héritage de l'Id d'un remote match si un install
legacy pointe vers un remote qui, lui, a un Id.
• Tri VersionOrder : lookup version + isBeta par rowKey via deux dicos
rawByRowKey/installedByRowKey (au lieu de folder name).
── VersionRowViewModel ───────────────────────────────────────────────
• RowKey : priorité (Remote.Id → Installed.EntryId → folder name → Version).
Aligné avec la logique RebuildList pour que les lookups par RowKey
trouvent la row correcte.
Bump : 1.0.9 → 1.0.10 (fix critique du refactor v1.0.8).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
136 lines
7.6 KiB
Plaintext
136 lines
7.6 KiB
Plaintext
; Script Inno Setup pour PS_Launcher
|
|
; Compilation : ouvrir avec Inno Setup Compiler (https://jrsoftware.org/isdl.php)
|
|
; ou en ligne de commande :
|
|
; "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" PSLauncher.iss
|
|
;
|
|
; Avant compile :
|
|
; 1. Faire `dotnet publish src/PSLauncher.App -c Release` (génère PS_Launcher.exe ~77 Mo)
|
|
; 2. Faire `dotnet publish src/PSLauncher.Updater -c Release` (génère PS_Launcher.Updater.exe ~10 Mo)
|
|
;
|
|
; Le script picore directement les binaires dans bin/Release/.../publish/.
|
|
|
|
#define MyAppName "PROSERVE Launcher"
|
|
#define MyAppShortName "PS_Launcher"
|
|
#define MyAppVersion "1.0.10"
|
|
#define MyAppPublisher "ASTERION VR"
|
|
#define MyAppURL "https://asterionvr.com"
|
|
#define MyAppExeName "PS_Launcher.exe"
|
|
#define MyUpdaterExeName "PS_Launcher.Updater.exe"
|
|
|
|
[Setup]
|
|
; AppId est UNIQUE pour PSLauncher — ne pas changer entre deux versions sinon
|
|
; chaque release créera une nouvelle entrée dans Programmes et fonctionnalités.
|
|
AppId={{8A4F3E1B-9D2C-4A6B-8F5E-3C7A1D9B4E2F}
|
|
AppName={#MyAppName}
|
|
AppVersion={#MyAppVersion}
|
|
AppVerName={#MyAppName} {#MyAppVersion}
|
|
AppPublisher={#MyAppPublisher}
|
|
AppPublisherURL={#MyAppURL}
|
|
AppSupportURL={#MyAppURL}
|
|
AppUpdatesURL={#MyAppURL}
|
|
; Installation system-wide dans Program Files. {autopf} résout vers
|
|
; "C:\Program Files" sur cible 64-bit (cf. ArchitecturesInstallIn64BitMode=x64
|
|
; ci-dessous). PrivilegesRequired=admin → UAC à l'install, et UAC à chaque
|
|
; self-update du launcher (LauncherSelfUpdater détecte automatiquement le
|
|
; dossier non-writable et lance PS_Launcher.Updater.exe avec Verb="runas").
|
|
; Le staging du nouveau .exe avant swap reste dans %LocalAppData%\PSLauncher\
|
|
; selfupdate\, user-writable et lisible depuis l'updater élevé.
|
|
DefaultDirName={autopf}\{#MyAppPublisher}\{#MyAppShortName}
|
|
DefaultGroupName={#MyAppName}
|
|
DisableProgramGroupPage=yes
|
|
OutputDir=output
|
|
OutputBaseFilename=PS_Launcher-Setup-{#MyAppVersion}
|
|
SetupIconFile=..\src\PSLauncher.App\Resources\favicon.ico
|
|
Compression=lzma2/ultra
|
|
SolidCompression=yes
|
|
WizardStyle=modern
|
|
PrivilegesRequired=admin
|
|
UsedUserAreasWarning=no
|
|
ArchitecturesAllowed=x64
|
|
ArchitecturesInstallIn64BitMode=x64
|
|
UninstallDisplayName={#MyAppName}
|
|
UninstallDisplayIcon={app}\{#MyAppExeName}
|
|
VersionInfoVersion={#MyAppVersion}
|
|
VersionInfoCompany={#MyAppPublisher}
|
|
VersionInfoProductName={#MyAppName}
|
|
|
|
[Languages]
|
|
Name: "french"; MessagesFile: "compiler:Languages\French.isl"
|
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
|
|
|
[Tasks]
|
|
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"
|
|
|
|
[Files]
|
|
Source: "..\src\PSLauncher.App\bin\Release\net8.0-windows10.0.17763.0\win-x64\publish\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
|
Source: "..\src\PSLauncher.Updater\bin\Release\net8.0-windows\win-x64\publish\{#MyUpdaterExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
|
Source: "..\docs\PS_Launcher-Guide-Utilisateur.pptx"; DestDir: "{app}\docs"; Flags: ignoreversion
|
|
; WebView2 Evergreen Bootstrapper (Microsoft, redistribuable). 1.6 MB.
|
|
; Téléchargé depuis https://go.microsoft.com/fwlink/p/?LinkId=2124703
|
|
; Exécuté en silencieux à l'install SI WebView2 Runtime n'est pas déjà présent
|
|
; (vérification registre via la fonction Pascal IsWebView2Installed ci-dessous).
|
|
; Sans ça, sur un Win10/Server sans WebView2 préinstallé, la fenêtre du launcher
|
|
; reste toute blanche au démarrage parce que les contrôles WebView2 (Reports +
|
|
; Documentation) ne peuvent pas s'initialiser et plantent en silence le rendu UI.
|
|
Source: "redists\MicrosoftEdgeWebview2Setup.exe"; DestDir: "{tmp}"; Flags: deleteafterinstall; Check: not IsWebView2Installed
|
|
|
|
[Icons]
|
|
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
|
|
Name: "{group}\Guide utilisateur"; Filename: "{app}\docs\PS_Launcher-Guide-Utilisateur.pptx"; IconFilename: "{app}\{#MyAppExeName}"
|
|
Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"
|
|
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
|
|
|
|
[Run]
|
|
; Installe WebView2 Runtime si pas déjà présent. /silent /install = install
|
|
; non-interactif. Le bootstrapper télécharge ~150 MB depuis Microsoft (1-2 min
|
|
; selon la connexion). StatusMsg pour que l'utilisateur voie ce qu'il se passe.
|
|
; Code exit attendu : 0 (déjà installé OU install réussie). On ne fail pas le
|
|
; setup global même si ça échoue — le launcher logue ensuite l'absence si ça
|
|
; pose problème, plutôt que de bloquer toute l'installation pour ça.
|
|
Filename: "{tmp}\MicrosoftEdgeWebview2Setup.exe"; Parameters: "/silent /install"; StatusMsg: "Installation du composant Microsoft Edge WebView2 Runtime (~150 MB, peut prendre 1-2 min)..."; Check: not IsWebView2Installed; Flags: waituntilterminated
|
|
|
|
; Règles firewall pour le cache LAN P2P. Profile=private,domain (PAS public) =
|
|
; safety net : même si l'utilisateur connecte le PC à un Wi-Fi public, le port
|
|
; reste fermé. Le filtre RFC1918 dans LanCacheServer.cs est la première barrière,
|
|
; ces règles sont la deuxième. runhidden = pas de console visible à l'install.
|
|
Filename: "{sys}\netsh.exe"; Parameters: "advfirewall firewall add rule name=""PSLauncher LAN Cache HTTP"" dir=in action=allow program=""{app}\{#MyAppExeName}"" protocol=TCP localport=47623 profile=private,domain"; Flags: runhidden
|
|
Filename: "{sys}\netsh.exe"; Parameters: "advfirewall firewall add rule name=""PSLauncher LAN Discovery"" dir=in action=allow program=""{app}\{#MyAppExeName}"" protocol=UDP localport=47624 profile=private,domain"; Flags: runhidden
|
|
|
|
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#MyAppName}}"; Flags: nowait postinstall skipifsilent
|
|
|
|
[UninstallRun]
|
|
Filename: "{sys}\netsh.exe"; Parameters: "advfirewall firewall delete rule name=""PSLauncher LAN Cache HTTP"""; Flags: runhidden
|
|
Filename: "{sys}\netsh.exe"; Parameters: "advfirewall firewall delete rule name=""PSLauncher LAN Discovery"""; Flags: runhidden
|
|
|
|
[UninstallDelete]
|
|
; Ne touche PAS au cache utilisateur (%LocalAppData%\PSLauncher) ni aux versions installées
|
|
; (qui peuvent vivre dans n'importe quel installRoot configuré). On supprime uniquement les
|
|
; binaires installés et le dossier d'install s'il est vide après.
|
|
Type: filesandordirs; Name: "{app}\*.bak"
|
|
Type: dirifempty; Name: "{app}"
|
|
|
|
[Code]
|
|
// Détecte si Microsoft Edge WebView2 Runtime est déjà installé sur le système.
|
|
// Référence : https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution
|
|
//
|
|
// 3 emplacements registre possibles selon le contexte d'install du runtime :
|
|
// - HKLM 64-bit (machine-wide, install par admin)
|
|
// - HKLM 32-bit (machine-wide, parfois utilisé par les versions plus anciennes)
|
|
// - HKCU (user-only, install par utilisateur sans admin)
|
|
//
|
|
// La clé contient une valeur 'pv' avec la version installée. Présence non vide
|
|
// = runtime présent. Le check Edge classique (HKLM\...\Edge\BLBeacon) ne suffit
|
|
// pas parce qu'Edge stable ≠ WebView2 Runtime (deux composants distincts).
|
|
function IsWebView2Installed(): Boolean;
|
|
var
|
|
PV: String;
|
|
begin
|
|
Result := False;
|
|
if RegQueryStringValue(HKLM, 'SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}', 'pv', PV) and (PV <> '') and (PV <> '0.0.0.0') then
|
|
Result := True
|
|
else if RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}', 'pv', PV) and (PV <> '') and (PV <> '0.0.0.0') then
|
|
Result := True
|
|
else if RegQueryStringValue(HKCU, 'Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}', 'pv', PV) and (PV <> '') and (PV <> '0.0.0.0') then
|
|
Result := True;
|
|
end;
|