using System.Collections.ObjectModel; using System.Diagnostics; using System.Windows; using System.Windows.Controls; using ClaudeDo.Installer.Core; using ClaudeDo.Installer.Steps; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Microsoft.Extensions.DependencyInjection; namespace ClaudeDo.Installer.Pages.InstallPage; public partial class InstallPageViewModel : ObservableObject, IInstallerPage { private readonly InstallContext _context; private readonly IServiceProvider _serviceProvider; private InstallPageView? _view; private CancellationTokenSource? _cts; public string Title => "Install"; public string Icon => "\uE896"; public int Order => 99; public bool ShowInWizard => true; public bool ShowInSettings => false; public UserControl View => _view ??= new InstallPageView { DataContext = this }; public ObservableCollection Steps { get; } = []; [ObservableProperty] private bool _isInstalling; [ObservableProperty] private bool _isComplete; [ObservableProperty] private bool _hasErrors; [ObservableProperty] private double _overallProgress; public InstallPageViewModel(InstallContext context, IServiceProvider serviceProvider) { _context = context; _serviceProvider = serviceProvider; } public Task LoadAsync() { Steps.Clear(); if (_context.Mode == InstallerMode.Update) { Steps.Add(new StepViewModel("Stop Worker Service")); Steps.Add(new StepViewModel("Download and Extract")); Steps.Add(new StepViewModel("Start Worker Service")); Steps.Add(new StepViewModel("Write Install Manifest")); } else { Steps.Add(new StepViewModel("Download and Extract")); Steps.Add(new StepViewModel("Write Configuration")); Steps.Add(new StepViewModel("Initialize Database")); Steps.Add(new StepViewModel("Register Windows Service")); Steps.Add(new StepViewModel("Create Shortcuts")); Steps.Add(new StepViewModel("Register in Add/Remove Programs")); Steps.Add(new StepViewModel("Write Install Manifest")); } return Task.CompletedTask; } public Task ApplyAsync() => RunInstallAsync(); public bool Validate() => true; [RelayCommand] private async Task RunInstallAsync() { if (IsInstalling) return; IsInstalling = true; IsComplete = false; HasErrors = false; OverallProgress = 0; _cts = new CancellationTokenSource(); var progress = new Progress(p => { var step = Steps.FirstOrDefault(s => s.Name == p.StepName); if (step is null) return; step.Status = p.Status; if (p.Message is not null) { // Messages starting with "\r" overwrite the previous line (live progress). if (p.Message.StartsWith('\r')) { var line = p.Message[1..]; if (step.Messages.Count > 0 && step.Messages[^1].StartsWith(" ")) step.Messages[^1] = line; else step.Messages.Add(line); } else { step.Messages.Add(p.Message); } } if (p.Status is StepStatus.Running && !step.IsExpanded) step.IsExpanded = true; if (p.Status is StepStatus.Done or StepStatus.Failed) { var completed = Steps.Count(s => s.Status is StepStatus.Done or StepStatus.Failed); OverallProgress = (double)completed / Steps.Count * 100; } }); try { IEnumerable steps; if (_context.Mode == InstallerMode.Update) { steps = new IInstallStep[] { _serviceProvider.GetRequiredService(), _serviceProvider.GetRequiredService(), _serviceProvider.GetRequiredService(), _serviceProvider.GetRequiredService(), }; } else { steps = _serviceProvider.GetServices(); } var runner = new InstallerService(steps); var results = await runner.ExecuteAsync(_context, progress, _cts.Token); HasErrors = results.Any(r => !r.Result.Success); } catch (OperationCanceledException) { HasErrors = true; } finally { IsInstalling = false; IsComplete = true; _cts.Dispose(); _cts = null; } } [RelayCommand] private void CancelInstall() { _cts?.Cancel(); } [RelayCommand] private void LaunchApp() { var appExe = System.IO.Path.Combine(_context.InstallDirectory, "app", "ClaudeDo.App.exe"); if (System.IO.File.Exists(appExe)) { Process.Start(new ProcessStartInfo(appExe) { UseShellExecute = true }); Application.Current.Shutdown(); } } }