diff --git a/installer/PSLauncher.iss b/installer/PSLauncher.iss index a98dcdc..4e9d4d9 100644 --- a/installer/PSLauncher.iss +++ b/installer/PSLauncher.iss @@ -11,7 +11,7 @@ #define MyAppName "PROSERVE Launcher" #define MyAppShortName "PSLauncher" -#define MyAppVersion "0.11.0" +#define MyAppVersion "0.12.0" #define MyAppPublisher "ASTERION VR" #define MyAppURL "https://asterionvr.com" #define MyAppExeName "PSLauncher.exe" diff --git a/src/PSLauncher.App/App.xaml.cs b/src/PSLauncher.App/App.xaml.cs index 5a8f3e6..24ee7d6 100644 --- a/src/PSLauncher.App/App.xaml.cs +++ b/src/PSLauncher.App/App.xaml.cs @@ -18,6 +18,7 @@ using PSLauncher.Core.Localization; using PSLauncher.Core.Manifests; using PSLauncher.Core.Migrations; using PSLauncher.Core.Process; +using PSLauncher.Core.ReportTool; using PSLauncher.Core.Updates; using PSLauncher.Models; using Serilog; @@ -174,6 +175,13 @@ public partial class App : Application sp.GetRequiredService().Database.EncryptedPassword), sp.GetRequiredService>())); + // Déploiement de l'outil Report (page web XAMPP) à l'install d'une + // nouvelle version : copie atomique de _report/ → C:\xampp\htdocs\ProserveReport\ + services.AddSingleton(sp => + new ReportToolDeployer( + configProvider: () => sp.GetRequiredService().ReportTool, + sp.GetRequiredService>())); + services.AddTransient(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/PSLauncher.App/PSLauncher.App.csproj b/src/PSLauncher.App/PSLauncher.App.csproj index 750aa69..6fb5fc6 100644 --- a/src/PSLauncher.App/PSLauncher.App.csproj +++ b/src/PSLauncher.App/PSLauncher.App.csproj @@ -15,9 +15,9 @@ PROSERVE Launcher © 2026 ASTERION VR — All rights reserved PSLauncher.App - 0.11.0 - 0.11.0.0 - 0.11.0.0 + 0.12.0 + 0.12.0.0 + 0.12.0.0 true diff --git a/src/PSLauncher.App/ViewModels/MainViewModel.cs b/src/PSLauncher.App/ViewModels/MainViewModel.cs index 3f24bb4..291654e 100644 --- a/src/PSLauncher.App/ViewModels/MainViewModel.cs +++ b/src/PSLauncher.App/ViewModels/MainViewModel.cs @@ -16,6 +16,7 @@ using PSLauncher.Core.Localization; using PSLauncher.Core.Manifests; using PSLauncher.Core.Migrations; using PSLauncher.Core.Process; +using PSLauncher.Core.ReportTool; using PSLauncher.Core.Updates; using PSLauncher.Models; @@ -41,6 +42,7 @@ public sealed partial class MainViewModel : ObservableObject private readonly ILauncherSelfUpdater _selfUpdater; private readonly IToastService _toastService; private readonly IDatabaseMigrationService _migrationService; + private readonly IReportToolDeployer _reportDeployer; private readonly IServiceProvider _serviceProvider; private readonly ILogger _logger; @@ -223,6 +225,7 @@ public sealed partial class MainViewModel : ObservableObject ILauncherSelfUpdater selfUpdater, IToastService toastService, IDatabaseMigrationService migrationService, + IReportToolDeployer reportDeployer, IServiceProvider serviceProvider, ILogger logger) { @@ -238,6 +241,7 @@ public sealed partial class MainViewModel : ObservableObject _selfUpdater = selfUpdater; _toastService = toastService; _migrationService = migrationService; + _reportDeployer = reportDeployer; _serviceProvider = serviceProvider; _logger = logger; @@ -745,6 +749,50 @@ public sealed partial class MainViewModel : ObservableObject } } + // 6) Déploiement de l'outil Report (page web XAMPP). + // Cherche _report/ dans le ZIP extrait et copie vers htdocs en + // atomic rename. Skip silencieux si _report/ absent du ZIP. + if (_config.ReportTool.AutoDeploy) + { + StatusMessage = Strings.StatusDeployingReport(row.Version); + ProgressDetail = null; + ProgressPercent = 0; + var rdProgress = new Progress(dp => + { + if (dp.FilesTotal > 0) + { + var pct = (double)dp.FilesDone / dp.FilesTotal * 100.0; + ProgressPercent = pct; + row.ProgressPercent = pct; + } + var label = Strings.ProgressReportDeploy(dp.FilesDone, dp.FilesTotal, dp.CurrentFilename); + ProgressDetail = label; + row.ProgressDetail = label; + }); + var rdResult = await _reportDeployer.DeployAsync(target, rdProgress, ct); + switch (rdResult.Status) + { + case DeployStatus.Deployed: + _logger.LogInformation("Report tool deployed ({N} files) for v{Version}", rdResult.FilesDeployed, row.Version); + break; + case DeployStatus.SkippedNoSourceFolder: + _logger.LogInformation("No _report/ in v{Version} ZIP, report tool unchanged", row.Version); + break; + case DeployStatus.XamppNotFound: + MessageBox.Show( + Strings.MsgReportXamppNotFound(_config.ReportTool.HtdocsRoot), + Strings.MsgBoxReportDeployFailed, + MessageBoxButton.OK, MessageBoxImage.Warning); + break; + case DeployStatus.Failed: + MessageBox.Show( + Strings.MsgReportDeployFailed(rdResult.ErrorMessage ?? "?"), + Strings.MsgBoxReportDeployFailed, + MessageBoxButton.OK, MessageBoxImage.Warning); + break; + } + } + StatusMessage = Strings.StatusInstalledSuccess(row.Version); ProgressDetail = null; ProgressPercent = 0; diff --git a/src/PSLauncher.App/ViewModels/SettingsViewModel.cs b/src/PSLauncher.App/ViewModels/SettingsViewModel.cs index 1444269..d14c23b 100644 --- a/src/PSLauncher.App/ViewModels/SettingsViewModel.cs +++ b/src/PSLauncher.App/ViewModels/SettingsViewModel.cs @@ -12,6 +12,7 @@ using PSLauncher.Core.Installations; using PSLauncher.Core.Licensing; using PSLauncher.Core.Localization; using PSLauncher.Core.Migrations; +using PSLauncher.Core.ReportTool; using PSLauncher.Models; namespace PSLauncher.App.ViewModels; @@ -30,6 +31,7 @@ public sealed partial class SettingsViewModel : ObservableObject private readonly ILicenseService _licenseService; private readonly IDownloadStateStore _downloadStore; private readonly IDatabaseMigrationService _migrationService; + private readonly IReportToolDeployer _reportDeployer; private readonly IInstallationRegistry _registry; private readonly HttpClient _http; private readonly ILogger _logger; @@ -57,6 +59,13 @@ public sealed partial class SettingsViewModel : ObservableObject [ObservableProperty] private string? _migrationsReplayStatus; [ObservableProperty] private bool _isReplayingMigrations; + // ----- Report Tool deploy settings ----- + [ObservableProperty] private string _reportHtdocsRoot; + [ObservableProperty] private string _reportFolderName; + [ObservableProperty] private bool _reportAutoDeploy; + [ObservableProperty] private string? _reportDeployStatus; + [ObservableProperty] private bool _isDeployingReport; + public IReadOnlyList AvailableLanguages { get; } = PSLauncher.Core.Localization.Strings.Available .Select(t => new LanguageOption(t.Code, t.Name)) @@ -124,6 +133,7 @@ public sealed partial class SettingsViewModel : ObservableObject ILicenseService licenseService, IDownloadStateStore downloadStore, IDatabaseMigrationService migrationService, + IReportToolDeployer reportDeployer, IInstallationRegistry registry, HttpClient http, ILogger logger) @@ -133,6 +143,7 @@ public sealed partial class SettingsViewModel : ObservableObject _licenseService = licenseService; _downloadStore = downloadStore; _migrationService = migrationService; + _reportDeployer = reportDeployer; _registry = registry; _http = http; _logger = logger; @@ -150,6 +161,10 @@ public sealed partial class SettingsViewModel : ObservableObject _dbName = config.Database.Database; _dbAutoApplyMigrations = config.Database.AutoApplyMigrations; + _reportHtdocsRoot = config.ReportTool.HtdocsRoot; + _reportFolderName = config.ReportTool.FolderName; + _reportAutoDeploy = config.ReportTool.AutoDeploy; + LauncherVersion = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "0.0.0"; MachineId = licenseService.GetMachineId(); LogsDirectory = App.LogsDirectory; @@ -269,6 +284,10 @@ public sealed partial class SettingsViewModel : ObservableObject _config.Database.Database = DbName.Trim(); _config.Database.AutoApplyMigrations = DbAutoApplyMigrations; + _config.ReportTool.HtdocsRoot = ReportHtdocsRoot.Trim(); + _config.ReportTool.FolderName = ReportFolderName.Trim(); + _config.ReportTool.AutoDeploy = ReportAutoDeploy; + _configStore.Save(_config); _logger.LogInformation("Settings saved (language={Lang}, changed={Changed})", _config.Language, languageChanged); @@ -334,6 +353,53 @@ public sealed partial class SettingsViewModel : ObservableObject private static string FormatSize(long bytes) => Strings.FormatSize(bytes); + /// + /// Re-déploie l'outil Report depuis la dernière version PROSERVE installée + /// vers htdocs. Utile si le déploiement post-install avait échoué (XAMPP + /// déplacé, htdocs absent), ou pour resync après changement du chemin htdocs + /// dans Settings. + /// + [RelayCommand] + private async Task RedeployReportAsync() + { + var installed = _registry.Scan().OrderByDescending(v => v.Version).FirstOrDefault(); + if (installed is null) + { + ReportDeployStatus = "Aucune version installée — installe d'abord une version qui contient _report/."; + return; + } + IsDeployingReport = true; + ReportDeployStatus = $"Déploiement depuis v{installed.Version}…"; + try + { + // Persiste les éventuels changements de chemin avant le deploy + Save(); + var progress = new Progress(dp => + { + if (dp.FilesTotal > 0) + ReportDeployStatus = $"{dp.FilesDone}/{dp.FilesTotal} : {dp.CurrentFilename}"; + }); + var result = await _reportDeployer.DeployAsync(installed.FolderPath, progress, CancellationToken.None); + ReportDeployStatus = result.Status switch + { + DeployStatus.Deployed => $"✓ {result.FilesDeployed} fichiers déployés vers {result.TargetPath}", + DeployStatus.SkippedNoSourceFolder => $"⚠ La version v{installed.Version} ne contient pas de _report/ — rien à déployer", + DeployStatus.XamppNotFound => $"✗ {result.ErrorMessage}", + DeployStatus.Failed => $"✗ {result.ErrorMessage}", + _ => "?", + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Redeploy report failed"); + ReportDeployStatus = $"✗ {ex.Message}"; + } + finally + { + IsDeployingReport = false; + } + } + // ===================== DB MIGRATIONS ===================== /// diff --git a/src/PSLauncher.App/Views/SettingsDialog.xaml b/src/PSLauncher.App/Views/SettingsDialog.xaml index 2975549..6c5967c 100644 --- a/src/PSLauncher.App/Views/SettingsDialog.xaml +++ b/src/PSLauncher.App/Views/SettingsDialog.xaml @@ -413,6 +413,56 @@ + + + + + + + + + + + + + + + + + + + +