using System; using System.Collections.Generic; using System.Linq; using System.Reactive.Disposables; using System.Threading; using System.Threading.Tasks; using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.DryIoc.Factories; using Artemis.UI.Screens.Plugins; using Artemis.UI.Screens.Workshop.EntryReleases.Dialogs; using Artemis.UI.Shared; using Artemis.UI.Shared.Routing; using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services.Builders; using Artemis.UI.Shared.Utilities; using Artemis.WebClient.Workshop; using Artemis.WebClient.Workshop.Handlers.InstallationHandlers; using Artemis.WebClient.Workshop.Models; using Artemis.WebClient.Workshop.Services; using PropertyChanged.SourceGenerator; using ReactiveUI; namespace Artemis.UI.Screens.Workshop.EntryReleases; public partial class EntryReleaseInfoViewModel : ActivatableViewModelBase { private readonly IRouter _router; private readonly INotificationService _notificationService; private readonly IWindowService _windowService; private readonly IWorkshopService _workshopService; private readonly IPluginManagementService _pluginManagementService; private readonly ISettingsVmFactory _settingsVmFactory; private readonly Progress _progress = new(); [Notify] private IReleaseDetails? _release; [Notify] private float _installProgress; [Notify] private bool _isCurrentVersion; [Notify] private bool _installationInProgress; [Notify] private bool _inDetailsScreen; private CancellationTokenSource? _cts; public EntryReleaseInfoViewModel(IRouter router, INotificationService notificationService, IWindowService windowService, IWorkshopService workshopService, IPluginManagementService pluginManagementService, ISettingsVmFactory settingsVmFactory) { _router = router; _notificationService = notificationService; _windowService = windowService; _workshopService = workshopService; _pluginManagementService = pluginManagementService; _settingsVmFactory = settingsVmFactory; _progress.ProgressChanged += (_, f) => InstallProgress = f.ProgressPercentage; this.WhenActivated(d => { _workshopService.OnEntryInstalled += WorkshopServiceOnOnEntryInstalled; _workshopService.OnEntryUninstalled += WorkshopServiceOnOnEntryInstalled; Disposable.Create(() => { _workshopService.OnEntryInstalled -= WorkshopServiceOnOnEntryInstalled; _workshopService.OnEntryUninstalled -= WorkshopServiceOnOnEntryInstalled; }).DisposeWith(d); IsCurrentVersion = Release != null && _workshopService.GetInstalledEntry(Release.Entry.Id)?.ReleaseId == Release.Id; }); this.WhenAnyValue(vm => vm.Release).Subscribe(r => IsCurrentVersion = r != null && _workshopService.GetInstalledEntry(r.Entry.Id)?.ReleaseId == r.Id); InDetailsScreen = true; } private void WorkshopServiceOnOnEntryInstalled(object? sender, InstalledEntry e) { IsCurrentVersion = Release != null && _workshopService.GetInstalledEntry(Release.Entry.Id)?.ReleaseId == Release.Id; } public async Task Close() { await _router.GoUp(); } public async Task Install() { if (Release == null) return; // If the entry has missing dependencies, show a dialog List missing = Release.Dependencies.Where(d => _workshopService.GetInstalledEntry(d.Id) == null).Cast().ToList(); if (missing.Count > 0) { await _windowService.CreateContentDialog() .WithTitle("Requirements missing") .WithViewModel(out DependenciesDialogViewModel _, Release.Entry, missing) .WithCloseButtonText("Cancel installation") .ShowAsync(); return; } _cts = new CancellationTokenSource(); InstallProgress = 0; InstallationInProgress = true; try { EntryInstallResult result = await _workshopService.InstallEntry(Release.Entry, Release, _progress, _cts.Token); if (result.IsSuccess) { _notificationService.CreateNotification().WithTitle("Installation succeeded").WithSeverity(NotificationSeverity.Success).Show(); InstallationInProgress = false; await Manage(); } else if (!_cts.IsCancellationRequested) _notificationService.CreateNotification().WithTitle("Installation failed").WithMessage(result.Message).WithSeverity(NotificationSeverity.Error).Show(); } catch (Exception e) { _windowService.ShowExceptionDialog("Failed to install workshop entry", e); } finally { InstallationInProgress = false; } } public async Task Manage() { if (Release?.Entry.EntryType != EntryType.Profile) await _router.Navigate("../../manage", new RouterNavigationOptions {AdditionalArguments = true}); } public async Task Reinstall() { if (await _windowService.ShowConfirmContentDialog("Reinstall entry", "Are you sure you want to reinstall this entry?")) await Install(); } public async Task Uninstall() { InstalledEntry? installedEntry = _workshopService.GetInstalledEntry(Release!.Entry.Id); if (installedEntry == null) return; InstallationInProgress = true; try { bool confirmed = await _windowService.ShowConfirmContentDialog("Do you want to uninstall this entry?", "Both the entry and its contents will be removed."); if (!confirmed) return; // Ideally the installation handler does this but it doesn't have access to the required view models if (installedEntry.EntryType == EntryType.Plugin) await UninstallPluginPrerequisites(installedEntry); await _workshopService.UninstallEntry(installedEntry, CancellationToken.None); _notificationService.CreateNotification().WithTitle("Entry uninstalled").WithSeverity(NotificationSeverity.Success).Show(); } finally { InstallationInProgress = false; } } public void Cancel() { _cts?.Cancel(); } private async Task UninstallPluginPrerequisites(InstalledEntry installedEntry) { if (!installedEntry.TryGetMetadata("PluginId", out Guid pluginId)) return; Plugin? plugin = _pluginManagementService.GetAllPlugins().FirstOrDefault(p => p.Guid == pluginId); if (plugin == null) return; PluginViewModel pluginViewModel = _settingsVmFactory.PluginViewModel(plugin, ReactiveCommand.Create(() => { })); await pluginViewModel.ExecuteRemovePrerequisites(true); } }