using System.Collections.ObjectModel; using System.ComponentModel; using System.Runtime.CompilerServices; using System.Windows; using System.Windows.Media; using Everything2Everything.Core.Converters; namespace Everything2Everything.App.ViewModels; public enum ToolInstallState { NotInstalled, Installed, Busy, Failed, } /// 설치 마법사 목록의 한 행. 카탈로그 정의 + 선택/상태/진행 표현. public sealed class ToolInstallItem : INotifyPropertyChanged { public ExternalToolDefinition Definition { get; } public ToolInstallItem(ExternalToolDefinition definition) { Definition = definition; RefreshStatus(); } public string Name => Definition.DisplayName; public string Description => Definition.Description; private bool _isSelected = true; public bool IsSelected { get => _isSelected; set { _isSelected = value; OnPropertyChanged(); } } private ToolInstallState _state; public ToolInstallState State { get => _state; private set { _state = value; OnPropertyChanged(); OnPropertyChanged(nameof(IsBusy)); OnPropertyChanged(nameof(StatusText)); OnPropertyChanged(nameof(StatusBrush)); } } public string StatusText => State switch { ToolInstallState.Installed => "설치됨", ToolInstallState.Busy => "설치 중…", ToolInstallState.Failed => "실패", _ => "미설치", }; public bool IsBusy => State == ToolInstallState.Busy; public Brush StatusBrush => State switch { ToolInstallState.Installed => BrushOf("FsStatusSuccess"), ToolInstallState.Busy => BrushOf("FsStatusInfo"), ToolInstallState.Failed => BrushOf("FsStatusDanger"), _ => BrushOf("FsStatusWarn"), }; private string? _detail; public string? Detail { get => _detail; private set { _detail = value; OnPropertyChanged(); } } public void RefreshStatus() => State = Definition.IsInstalled() ? ToolInstallState.Installed : ToolInstallState.NotInstalled; public void ApplyResult(bool success, string? detail) { Detail = detail; State = success ? ToolInstallState.Installed : ToolInstallState.Failed; } public void BeginInstall() => State = ToolInstallState.Busy; private static Brush BrushOf(string key) => (Application.Current?.TryFindResource(key) as Brush) ?? _fallbacks.GetValueOrDefault(key, _warn); private static readonly Brush _info = Fixed(0x1565C0); private static readonly Brush _success = Fixed(0x2E7D32); private static readonly Brush _warn = Fixed(0xC77700); private static readonly Brush _danger = Fixed(0xC62828); private static readonly Dictionary _fallbacks = new() { ["FsStatusInfo"] = _info, ["FsStatusSuccess"] = _success, ["FsStatusWarn"] = _warn, ["FsStatusDanger"] = _danger, }; private static Brush Fixed(uint rgb) { var c = (int)rgb; return new SolidColorBrush(Color.FromRgb((byte)(c >> 16), (byte)(c >> 8), (byte)c)); } public event PropertyChangedEventHandler? PropertyChanged; private void OnPropertyChanged([CallerMemberName] string? name = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); } /// 설치 마법사의 루트 뷰모델: 목록 + 전체 선택 + 선택분 순차 설치. public sealed class ToolSetupViewModel : INotifyPropertyChanged { private readonly ExternalToolInstaller _installer; private bool _isInstalling; private bool _selectAll = true; public ObservableCollection Tools { get; } = new(); public ToolSetupViewModel(IEnumerable? definitions = null, ExternalToolInstaller? installer = null) { _installer = installer ?? new ExternalToolInstaller(); foreach (var def in definitions ?? ExternalToolCatalog.All) Tools.Add(new ToolInstallItem(def)); } public bool IsInstalling { get => _isInstalling; private set { _isInstalling = value; OnPropertyChanged(); } } public bool SelectAll { get => _selectAll; set { _selectAll = value; foreach (var t in Tools) t.IsSelected = value; OnPropertyChanged(); } } public void RefreshStatus() { foreach (var t in Tools) t.RefreshStatus(); } public async Task InstallSelectedAsync(CancellationToken ct) { if (IsInstalling) return; IsInstalling = true; try { // 이미 설치된 도구는 재설치하지 않고 건너뛴다(감지 상태가 Installed인 항목 제외). foreach (var item in Tools.Where(t => t.IsSelected && t.State != ToolInstallState.Installed).ToList()) await InstallOneAsync(item, ct).ConfigureAwait(true); } finally { IsInstalling = false; } } public async Task InstallOneAsync(ToolInstallItem item, CancellationToken ct) { item.BeginInstall(); var result = await _installer.InstallAsync(item.Definition, ct).ConfigureAwait(true); var detail = string.IsNullOrWhiteSpace(result.Output) ? result.Message : Tail(result.Output, 600); item.ApplyResult(result.Success, detail); } private static string Tail(string s, int max) => s.Length <= max ? s : "…" + s[^max..]; public event PropertyChangedEventHandler? PropertyChanged; private void OnPropertyChanged([CallerMemberName] string? name = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); }