1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Prerequisites - Finalized UI logic surrounding install/remove

This commit is contained in:
Robert 2021-05-02 23:00:48 +02:00
parent 21700aaad5
commit 77be79dde5
11 changed files with 260 additions and 191 deletions

View File

@ -7,32 +7,24 @@ namespace Artemis.Core
/// </summary> /// </summary>
public class ArtemisPluginPrerequisiteException : Exception public class ArtemisPluginPrerequisiteException : Exception
{ {
internal ArtemisPluginPrerequisiteException(Plugin plugin, PluginPrerequisite? pluginPrerequisite) internal ArtemisPluginPrerequisiteException(IPrerequisitesSubject subject)
{ {
Plugin = plugin; Subject = subject;
PluginPrerequisite = pluginPrerequisite;
} }
internal ArtemisPluginPrerequisiteException(Plugin plugin, PluginPrerequisite? pluginPrerequisite, string message) : base(message) internal ArtemisPluginPrerequisiteException(IPrerequisitesSubject subject, string message) : base(message)
{ {
Plugin = plugin; Subject = subject;
PluginPrerequisite = pluginPrerequisite;
} }
internal ArtemisPluginPrerequisiteException(Plugin plugin, PluginPrerequisite? pluginPrerequisite, string message, Exception inner) : base(message, inner) internal ArtemisPluginPrerequisiteException(IPrerequisitesSubject subject, string message, Exception inner) : base(message, inner)
{ {
Plugin = plugin; Subject = subject;
PluginPrerequisite = pluginPrerequisite;
} }
/// <summary> /// <summary>
/// Gets the plugin the error is related to /// Gets the subject the error is related to
/// </summary> /// </summary>
public Plugin Plugin { get; } public IPrerequisitesSubject Subject { get; }
/// <summary>
/// Gets the plugin prerequisite the error is related to
/// </summary>
public PluginPrerequisite? PluginPrerequisite { get; }
} }
} }

View File

@ -25,6 +25,7 @@ namespace Artemis.Core
Info = info; Info = info;
Directory = directory; Directory = directory;
Entity = pluginEntity ?? new PluginEntity {Id = Guid, IsEnabled = true}; Entity = pluginEntity ?? new PluginEntity {Id = Guid, IsEnabled = true};
Info.Plugin = this;
_features = new List<PluginFeatureInfo>(); _features = new List<PluginFeatureInfo>();
} }

View File

@ -6,6 +6,7 @@ using Artemis.Core.DeviceProviders;
using Artemis.Core.LayerBrushes; using Artemis.Core.LayerBrushes;
using Artemis.Core.LayerEffects; using Artemis.Core.LayerEffects;
using Artemis.Core.Modules; using Artemis.Core.Modules;
using Artemis.Storage.Entities.Plugins;
using Humanizer; using Humanizer;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -22,10 +23,11 @@ namespace Artemis.Core
private PluginFeature? _instance; private PluginFeature? _instance;
private string _name = null!; private string _name = null!;
internal PluginFeatureInfo(Plugin plugin, Type featureType, PluginFeatureAttribute? attribute) internal PluginFeatureInfo(Plugin plugin, Type featureType, PluginFeatureEntity pluginFeatureEntity, PluginFeatureAttribute? attribute)
{ {
Plugin = plugin ?? throw new ArgumentNullException(nameof(plugin)); Plugin = plugin ?? throw new ArgumentNullException(nameof(plugin));
FeatureType = featureType ?? throw new ArgumentNullException(nameof(featureType)); FeatureType = featureType ?? throw new ArgumentNullException(nameof(featureType));
Entity = pluginFeatureEntity;
Name = attribute?.Name ?? featureType.Name.Humanize(LetterCasing.Title); Name = attribute?.Name ?? featureType.Name.Humanize(LetterCasing.Title);
Description = attribute?.Description; Description = attribute?.Description;
@ -48,7 +50,7 @@ namespace Artemis.Core
else else
Icon = "Plugin"; Icon = "Plugin";
} }
internal PluginFeatureInfo(Plugin plugin, PluginFeatureAttribute? attribute, PluginFeature instance) internal PluginFeatureInfo(Plugin plugin, PluginFeatureAttribute? attribute, PluginFeature instance)
{ {
if (instance == null) throw new ArgumentNullException(nameof(instance)); if (instance == null) throw new ArgumentNullException(nameof(instance));
@ -121,6 +123,11 @@ namespace Artemis.Core
[JsonProperty] [JsonProperty]
public bool AlwaysEnabled { get; } public bool AlwaysEnabled { get; }
/// <summary>
/// Gets a boolean indicating whether the feature is enabled in persistent storage
/// </summary>
public bool EnabledInStorage => Entity.IsEnabled;
/// <summary> /// <summary>
/// Gets the feature this info is associated with /// Gets the feature this info is associated with
/// </summary> /// </summary>
@ -136,6 +143,8 @@ namespace Artemis.Core
/// <inheritdoc /> /// <inheritdoc />
public bool ArePrerequisitesMet() => Prerequisites.All(p => p.IsMet()); public bool ArePrerequisitesMet() => Prerequisites.All(p => p.IsMet());
internal PluginFeatureEntity Entity { get; }
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() public override string ToString()
{ {

View File

@ -0,0 +1,79 @@
using System;
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Humanizer;
namespace Artemis.Core
{
/// <summary>
/// Represents a plugin prerequisite action that downloads a file
/// </summary>
public class DownloadFileAction : PluginPrerequisiteAction
{
/// <summary>
/// Creates a new instance of a copy folder action
/// </summary>
/// <param name="name">The name of the action</param>
/// <param name="source">The source URL to download</param>
/// <param name="target">The target file to save as (will be created if needed)</param>
public DownloadFileAction(string name, string source, string target) : base(name)
{
Source = source ?? throw new ArgumentNullException(nameof(source));
Target = target ?? throw new ArgumentNullException(nameof(target));
ShowProgressBar = true;
}
/// <summary>
/// Gets the source URL to download
/// </summary>
public string Source { get; }
/// <summary>
/// Gets the target file to save as (will be created if needed)
/// </summary>
public string Target { get; }
/// <inheritdoc />
public override async Task Execute(CancellationToken cancellationToken)
{
using HttpClient client = new();
await using FileStream destinationStream = File.Create(Target);
void ProgressOnProgressReported(object? sender, EventArgs e)
{
if (Progress.ProgressPerSecond != 0)
Status = $"Downloading {Target} - {Progress.ProgressPerSecond.Bytes().Humanize("#.##")}/sec";
else
Status = $"Downloading {Target}";
}
Progress.ProgressReported += ProgressOnProgressReported;
// Get the http headers first to examine the content length
using HttpResponseMessage response = await client.GetAsync(Target, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
await using Stream download = await response.Content.ReadAsStreamAsync(cancellationToken);
long? contentLength = response.Content.Headers.ContentLength;
// Ignore progress reporting when no progress reporter was
// passed or when the content length is unknown
if (!contentLength.HasValue)
{
ProgressIndeterminate = true;
await download.CopyToAsync(destinationStream, Progress, cancellationToken);
}
else
{
ProgressIndeterminate = false;
await download.CopyToAsync(contentLength.Value, destinationStream, Progress, cancellationToken);
}
Progress.ProgressReported -= ProgressOnProgressReported;
Progress.Report((1, 1));
Status = "Finished downloading";
}
}
}

View File

@ -340,7 +340,12 @@ namespace Artemis.Core.Services
} }
foreach (Type featureType in featureTypes) foreach (Type featureType in featureTypes)
plugin.AddFeature(new PluginFeatureInfo(plugin, featureType, (PluginFeatureAttribute?) Attribute.GetCustomAttribute(featureType, typeof(PluginFeatureAttribute)))); {
// Load the enabled state and if not found, default to true
PluginFeatureEntity featureEntity = plugin.Entity.Features.FirstOrDefault(i => i.Type == featureType.FullName) ??
new PluginFeatureEntity { IsEnabled = plugin.Info.AutoEnableFeatures, Type = featureType.FullName! };
plugin.AddFeature(new PluginFeatureInfo(plugin, featureType, featureEntity, (PluginFeatureAttribute?) Attribute.GetCustomAttribute(featureType, typeof(PluginFeatureAttribute))));
}
if (!featureTypes.Any()) if (!featureTypes.Any())
_logger.Warning("Plugin {plugin} contains no features", plugin); _logger.Warning("Plugin {plugin} contains no features", plugin);
@ -382,7 +387,7 @@ namespace Artemis.Core.Services
} }
if (!plugin.Info.ArePrerequisitesMet()) if (!plugin.Info.ArePrerequisitesMet())
throw new ArtemisPluginPrerequisiteException(plugin, null, "Cannot enable a plugin whose prerequisites aren't all met"); throw new ArtemisPluginPrerequisiteException(plugin.Info, "Cannot enable a plugin whose prerequisites aren't all met");
// Create the Ninject child kernel and load the module // Create the Ninject child kernel and load the module
plugin.Kernel = new ChildKernel(_kernel, new PluginModule(plugin)); plugin.Kernel = new ChildKernel(_kernel, new PluginModule(plugin));
@ -406,10 +411,7 @@ namespace Artemis.Core.Services
featureInfo.Instance = instance; featureInfo.Instance = instance;
instance.Info = featureInfo; instance.Info = featureInfo;
instance.Plugin = plugin; instance.Plugin = plugin;
instance.Entity = featureInfo.Entity;
// Load the enabled state and if not found, default to true
instance.Entity = plugin.Entity.Features.FirstOrDefault(i => i.Type == featureInfo.FeatureType.FullName) ??
new PluginFeatureEntity {IsEnabled = plugin.Info.AutoEnableFeatures, Type = featureInfo.FeatureType.FullName!};
} }
catch (Exception e) catch (Exception e)
{ {
@ -418,17 +420,8 @@ namespace Artemis.Core.Services
} }
// Activate features after they are all loaded // Activate features after they are all loaded
foreach (PluginFeatureInfo pluginFeature in plugin.Features.Where(f => f.Instance != null && (f.Instance.Entity.IsEnabled || f.AlwaysEnabled))) foreach (PluginFeatureInfo pluginFeature in plugin.Features.Where(f => f.Instance != null && (f.EnabledInStorage || f.AlwaysEnabled)))
{ EnablePluginFeature(pluginFeature.Instance!, false, !ignorePluginLock);
try
{
EnablePluginFeature(pluginFeature.Instance!, false, !ignorePluginLock);
}
catch (Exception)
{
// ignored, logged in EnablePluginFeature
}
}
if (saveState) if (saveState)
{ {
@ -585,7 +578,10 @@ namespace Artemis.Core.Services
if (pluginFeature.Plugin.Info.RequiresAdmin && !_isElevated) if (pluginFeature.Plugin.Info.RequiresAdmin && !_isElevated)
{ {
if (!saveState) if (!saveState)
{
OnPluginFeatureEnableFailed(new PluginFeatureEventArgs(pluginFeature));
throw new ArtemisCoreException("Cannot enable a feature that requires elevation without saving it's state."); throw new ArtemisCoreException("Cannot enable a feature that requires elevation without saving it's state.");
}
pluginFeature.Entity.IsEnabled = true; pluginFeature.Entity.IsEnabled = true;
pluginFeature.Plugin.Entity.IsEnabled = true; pluginFeature.Plugin.Entity.IsEnabled = true;
@ -596,6 +592,12 @@ namespace Artemis.Core.Services
return; return;
} }
if (!pluginFeature.Info.ArePrerequisitesMet())
{
OnPluginFeatureEnableFailed(new PluginFeatureEventArgs(pluginFeature));
throw new ArtemisPluginPrerequisiteException(pluginFeature.Info, "Cannot enable a plugin feature whose prerequisites aren't all met");
}
try try
{ {
pluginFeature.SetEnabled(true, isAutoEnable); pluginFeature.SetEnabled(true, isAutoEnable);
@ -608,7 +610,6 @@ namespace Artemis.Core.Services
new ArtemisPluginException(pluginFeature.Plugin, $"Exception during SetEnabled(true) on {pluginFeature}", e), new ArtemisPluginException(pluginFeature.Plugin, $"Exception during SetEnabled(true) on {pluginFeature}", e),
"Failed to enable plugin" "Failed to enable plugin"
); );
throw;
} }
finally finally
{ {

View File

@ -24,7 +24,7 @@
<ColumnDefinition Width="500" /> <ColumnDefinition Width="500" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Style="{StaticResource MaterialDesignHeadline6TextBlock}" TextWrapping="Wrap" Margin="0 0 0 20"> <TextBlock Grid.Row="0" Style="{StaticResource MaterialDesignHeadline6TextBlock}" TextWrapping="Wrap" Margin="0 0 0 20">
Plugin prerequisites Plugin/feature prerequisites
</TextBlock> </TextBlock>
<ListBox Grid.Row="1" <ListBox Grid.Row="1"
@ -83,7 +83,7 @@
TextWrapping="Wrap" TextWrapping="Wrap"
Margin="10 0" Margin="10 0"
Visibility="{Binding ActivePrerequisite, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Inverted}"> Visibility="{Binding ActivePrerequisite, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Inverted}">
In order for this plugin to function certain prerequisites must be met. <LineBreak /><LineBreak /> In order for this plugin/feature to function certain prerequisites must be met. <LineBreak /><LineBreak />
On the left side you can see all prerequisites and whether they are currently met or not. On the left side you can see all prerequisites and whether they are currently met or not.
Clicking install prerequisites will automatically set everything up for you. Clicking install prerequisites will automatically set everything up for you.
</TextBlock> </TextBlock>

View File

@ -4,7 +4,6 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.UI.Exceptions;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using MaterialDesignThemes.Wpf; using MaterialDesignThemes.Wpf;
@ -15,35 +14,25 @@ namespace Artemis.UI.Screens.Plugins
public class PluginPrerequisitesInstallDialogViewModel : DialogViewModelBase public class PluginPrerequisitesInstallDialogViewModel : DialogViewModelBase
{ {
private readonly IDialogService _dialogService; private readonly IDialogService _dialogService;
private readonly List<IPrerequisitesSubject> _subjects;
private PluginPrerequisiteViewModel _activePrerequisite; private PluginPrerequisiteViewModel _activePrerequisite;
private bool _canInstall; private bool _canInstall;
private bool _isFinished; private bool _isFinished;
private CancellationTokenSource _tokenSource; private CancellationTokenSource _tokenSource;
public PluginPrerequisitesInstallDialogViewModel(IPrerequisitesSubject subject, IPrerequisitesVmFactory prerequisitesVmFactory, IDialogService dialogService) public PluginPrerequisitesInstallDialogViewModel(List<IPrerequisitesSubject> subjects, IPrerequisitesVmFactory prerequisitesVmFactory, IDialogService dialogService)
{ {
_subjects = subjects;
_dialogService = dialogService; _dialogService = dialogService;
// Constructor overloading doesn't work very well with Kernel.Get<T> :(
if (subject is PluginInfo plugin) Prerequisites = new BindableCollection<PluginPrerequisiteViewModel>();
{ foreach (IPrerequisitesSubject prerequisitesSubject in subjects)
PluginInfo = plugin; Prerequisites.AddRange(prerequisitesSubject.Prerequisites.Select(p => prerequisitesVmFactory.PluginPrerequisiteViewModel(p, false)));
Prerequisites = new BindableCollection<PluginPrerequisiteViewModel>(plugin.Prerequisites.Select(p => prerequisitesVmFactory.PluginPrerequisiteViewModel(p, false)));
}
else if (subject is PluginFeatureInfo feature)
{
FeatureInfo = feature;
Prerequisites = new BindableCollection<PluginPrerequisiteViewModel>(feature.Prerequisites.Select(p => prerequisitesVmFactory.PluginPrerequisiteViewModel(p, false)));
}
else
throw new ArtemisUIException($"Expected plugin or feature to be passed to {nameof(PluginPrerequisitesInstallDialogViewModel)}");
foreach (PluginPrerequisiteViewModel pluginPrerequisiteViewModel in Prerequisites) foreach (PluginPrerequisiteViewModel pluginPrerequisiteViewModel in Prerequisites)
pluginPrerequisiteViewModel.ConductWith(this); pluginPrerequisiteViewModel.ConductWith(this);
} }
public PluginInfo PluginInfo { get; }
public PluginFeatureInfo FeatureInfo { get; }
public BindableCollection<PluginPrerequisiteViewModel> Prerequisites { get; } public BindableCollection<PluginPrerequisiteViewModel> Prerequisites { get; }
public PluginPrerequisiteViewModel ActivePrerequisite public PluginPrerequisiteViewModel ActivePrerequisite
@ -64,9 +53,6 @@ namespace Artemis.UI.Screens.Plugins
set => SetAndNotify(ref _isFinished, value); set => SetAndNotify(ref _isFinished, value);
} }
public bool IsSubjectPlugin => PluginInfo != null;
public bool IsSubjectFeature => FeatureInfo != null;
#region Overrides of DialogViewModelBase #region Overrides of DialogViewModelBase
/// <inheritdoc /> /// <inheritdoc />
@ -114,7 +100,7 @@ namespace Artemis.UI.Screens.Plugins
"Confirm", "Confirm",
"" ""
); );
await _dialogService.ShowDialog<PluginPrerequisitesInstallDialogViewModel>(new Dictionary<string, object> {{"subject", PluginInfo}}); await Show(_dialogService, _subjects);
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
@ -133,16 +119,18 @@ namespace Artemis.UI.Screens.Plugins
Session?.Close(true); Session?.Close(true);
} }
public static Task<object> Show(IDialogService dialogService, List<IPrerequisitesSubject> subjects)
{
return dialogService.ShowDialog<PluginPrerequisitesInstallDialogViewModel>(new Dictionary<string, object> {{"subjects", subjects}});
}
#region Overrides of Screen #region Overrides of Screen
/// <inheritdoc /> /// <inheritdoc />
protected override void OnInitialActivate() protected override void OnInitialActivate()
{ {
CanInstall = false; CanInstall = false;
if (PluginInfo != null) Task.Run(() => CanInstall = Prerequisites.Any(p => !p.PluginPrerequisite.IsMet()));
Task.Run(() => CanInstall = !PluginInfo.ArePrerequisitesMet());
else
Task.Run(() => CanInstall = !FeatureInfo.ArePrerequisitesMet());
base.OnInitialActivate(); base.OnInitialActivate();
} }

View File

@ -24,7 +24,7 @@
<ColumnDefinition Width="500" /> <ColumnDefinition Width="500" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Style="{StaticResource MaterialDesignHeadline6TextBlock}" TextWrapping="Wrap" Margin="0 0 0 20"> <TextBlock Grid.Row="0" Style="{StaticResource MaterialDesignHeadline6TextBlock}" TextWrapping="Wrap" Margin="0 0 0 20">
Plugin prerequisites Plugin/feature prerequisites
</TextBlock> </TextBlock>
<ListBox Grid.Row="1" <ListBox Grid.Row="1"
@ -83,8 +83,8 @@
TextWrapping="Wrap" TextWrapping="Wrap"
Margin="10 0" Margin="10 0"
Visibility="{Binding ActivePrerequisite, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Inverted}"> Visibility="{Binding ActivePrerequisite, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Inverted}">
This plugin installed certain prerequisites in order to function. <LineBreak /><LineBreak /> This plugin/feature installed certain prerequisites in order to function. <LineBreak /><LineBreak />
In this screen you can chose to remove these, this will mean the plugin will no longer work until you reinstall the prerequisites. In this screen you can chose to remove these, this will mean the plugin/feature will no longer work until you reinstall the prerequisites.
</TextBlock> </TextBlock>
<StackPanel Grid.Row="2" <StackPanel Grid.Row="2"
@ -98,7 +98,7 @@
Focusable="False" Focusable="False"
IsCancel="True" IsCancel="True"
Command="{s:Action Cancel}" Command="{s:Action Cancel}"
Content="CANCEL" /> Content="{Binding CancelLabel}" />
<Button x:Name="ConfirmButton" <Button x:Name="ConfirmButton"
Style="{StaticResource MaterialDesignFlatButton}" Style="{StaticResource MaterialDesignFlatButton}"
IsDefault="True" IsDefault="True"

View File

@ -5,7 +5,6 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Exceptions;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using MaterialDesignThemes.Wpf; using MaterialDesignThemes.Wpf;
@ -17,38 +16,29 @@ namespace Artemis.UI.Screens.Plugins
{ {
private readonly IDialogService _dialogService; private readonly IDialogService _dialogService;
private readonly IPluginManagementService _pluginManagementService; private readonly IPluginManagementService _pluginManagementService;
private readonly List<IPrerequisitesSubject> _subjects;
private PluginPrerequisiteViewModel _activePrerequisite; private PluginPrerequisiteViewModel _activePrerequisite;
private bool _canUninstall; private bool _canUninstall;
private bool _isFinished; private bool _isFinished;
private CancellationTokenSource _tokenSource; private CancellationTokenSource _tokenSource;
public PluginPrerequisitesUninstallDialogViewModel(IPrerequisitesSubject subject, IPrerequisitesVmFactory prerequisitesVmFactory, IDialogService dialogService, public PluginPrerequisitesUninstallDialogViewModel(List<IPrerequisitesSubject> subjects, string cancelLabel, IPrerequisitesVmFactory prerequisitesVmFactory,
IPluginManagementService pluginManagementService) IDialogService dialogService, IPluginManagementService pluginManagementService)
{ {
_subjects = subjects;
_dialogService = dialogService; _dialogService = dialogService;
_pluginManagementService = pluginManagementService; _pluginManagementService = pluginManagementService;
// Constructor overloading doesn't work very well with Kernel.Get<T> :( CancelLabel = cancelLabel;
if (subject is PluginInfo plugin) Prerequisites = new BindableCollection<PluginPrerequisiteViewModel>();
{ foreach (IPrerequisitesSubject prerequisitesSubject in subjects)
PluginInfo = plugin; Prerequisites.AddRange(prerequisitesSubject.Prerequisites.Select(p => prerequisitesVmFactory.PluginPrerequisiteViewModel(p, true)));
Prerequisites = new BindableCollection<PluginPrerequisiteViewModel>(plugin.Prerequisites.Select(p => prerequisitesVmFactory.PluginPrerequisiteViewModel(p, true)));
}
else if (subject is PluginFeatureInfo feature)
{
FeatureInfo = feature;
Prerequisites = new BindableCollection<PluginPrerequisiteViewModel>(feature.Prerequisites.Select(p => prerequisitesVmFactory.PluginPrerequisiteViewModel(p, true)));
}
else
throw new ArtemisUIException($"Expected plugin or feature to be passed to {nameof(PluginPrerequisitesInstallDialogViewModel)}");
foreach (PluginPrerequisiteViewModel pluginPrerequisiteViewModel in Prerequisites) foreach (PluginPrerequisiteViewModel pluginPrerequisiteViewModel in Prerequisites)
pluginPrerequisiteViewModel.ConductWith(this); pluginPrerequisiteViewModel.ConductWith(this);
} }
public string CancelLabel { get; }
public PluginFeatureInfo FeatureInfo { get; }
public PluginInfo PluginInfo { get; }
public BindableCollection<PluginPrerequisiteViewModel> Prerequisites { get; } public BindableCollection<PluginPrerequisiteViewModel> Prerequisites { get; }
public PluginPrerequisiteViewModel ActivePrerequisite public PluginPrerequisiteViewModel ActivePrerequisite
@ -69,9 +59,6 @@ namespace Artemis.UI.Screens.Plugins
set => SetAndNotify(ref _isFinished, value); set => SetAndNotify(ref _isFinished, value);
} }
public bool IsSubjectPlugin => PluginInfo != null;
public bool IsSubjectFeature => FeatureInfo != null;
#region Overrides of DialogViewModelBase #region Overrides of DialogViewModelBase
/// <inheritdoc /> /// <inheritdoc />
@ -87,10 +74,26 @@ namespace Artemis.UI.Screens.Plugins
{ {
CanUninstall = false; CanUninstall = false;
if (PluginInfo != null) // Disable all subjects that are plugins, this will disable their features too
_pluginManagementService.DisablePlugin(PluginInfo.Plugin, true); foreach (IPrerequisitesSubject prerequisitesSubject in _subjects)
else if (FeatureInfo?.Instance != null) {
_pluginManagementService.DisablePluginFeature(FeatureInfo.Instance, true); if (prerequisitesSubject is PluginInfo pluginInfo)
_pluginManagementService.DisablePlugin(pluginInfo.Plugin, true);
}
// Disable all subjects that are features if still required
foreach (IPrerequisitesSubject prerequisitesSubject in _subjects)
{
if (prerequisitesSubject is not PluginFeatureInfo featureInfo)
continue;
// Disable the parent plugin if the feature is AlwaysEnabled
if (featureInfo.AlwaysEnabled)
_pluginManagementService.DisablePlugin(featureInfo.Plugin, true);
else if (featureInfo.Instance != null)
_pluginManagementService.DisablePluginFeature(featureInfo.Instance, true);
}
_tokenSource = new CancellationTokenSource(); _tokenSource = new CancellationTokenSource();
try try
@ -124,7 +127,7 @@ namespace Artemis.UI.Screens.Plugins
"Confirm", "Confirm",
"" ""
); );
await _dialogService.ShowDialog<PluginPrerequisitesUninstallDialogViewModel>(new Dictionary<string, object> {{"subject", PluginInfo}}); await Show(_dialogService, _subjects);
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
@ -143,6 +146,15 @@ namespace Artemis.UI.Screens.Plugins
Session?.Close(true); Session?.Close(true);
} }
public static Task<object> Show(IDialogService dialogService, List<IPrerequisitesSubject> subjects, string cancelLabel = "CANCEL")
{
return dialogService.ShowDialog<PluginPrerequisitesUninstallDialogViewModel>(new Dictionary<string, object>
{
{"subjects", subjects},
{"cancelLabel", cancelLabel},
});
}
#region Overrides of Screen #region Overrides of Screen
/// <inheritdoc /> /// <inheritdoc />
@ -150,10 +162,7 @@ namespace Artemis.UI.Screens.Plugins
{ {
CanUninstall = false; CanUninstall = false;
// Could be slow so take it off of the UI thread // Could be slow so take it off of the UI thread
if (PluginInfo != null) Task.Run(() => CanUninstall = Prerequisites.Any(p => p.PluginPrerequisite.IsMet()));
Task.Run(() => CanUninstall = PluginInfo.Prerequisites.Any(p => p.IsMet()));
else
Task.Run(() => CanUninstall = FeatureInfo.Prerequisites.Any(p => p.IsMet()));
base.OnInitialActivate(); base.OnInitialActivate();
} }

View File

@ -16,14 +16,11 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
{ {
private readonly ICoreService _coreService; private readonly ICoreService _coreService;
private readonly IDialogService _dialogService; private readonly IDialogService _dialogService;
private readonly IMessageService _messageService;
private readonly IPluginManagementService _pluginManagementService; private readonly IPluginManagementService _pluginManagementService;
private bool _enabling; private bool _enabling;
private readonly IMessageService _messageService;
private bool _isSettingsPopupOpen;
private bool _canInstallPrerequisites;
private bool _canRemovePrerequisites;
public PluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo, public PluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo,
bool showShield, bool showShield,
ICoreService coreService, ICoreService coreService,
IDialogService dialogService, IDialogService dialogService,
@ -81,6 +78,21 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
_dialogService.ShowExceptionDialog("Feature failed to enable", LoadException); _dialogService.ShowExceptionDialog("Feature failed to enable", LoadException);
} }
public async Task InstallPrerequisites()
{
if (FeatureInfo.Prerequisites.Any())
await PluginPrerequisitesInstallDialogViewModel.Show(_dialogService, new List<IPrerequisitesSubject> { FeatureInfo });
}
public async Task RemovePrerequisites()
{
if (FeatureInfo.Prerequisites.Any(p => p.UninstallActions.Any()))
{
await PluginPrerequisitesUninstallDialogViewModel.Show(_dialogService, new List<IPrerequisitesSubject> {FeatureInfo});
NotifyOfPropertyChange(nameof(IsEnabled));
}
}
protected override void OnInitialActivate() protected override void OnInitialActivate()
{ {
_pluginManagementService.PluginFeatureEnabling += OnFeatureEnabling; _pluginManagementService.PluginFeatureEnabling += OnFeatureEnabling;
@ -89,7 +101,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
FeatureInfo.Plugin.Enabled += PluginOnToggled; FeatureInfo.Plugin.Enabled += PluginOnToggled;
FeatureInfo.Plugin.Disabled += PluginOnToggled; FeatureInfo.Plugin.Disabled += PluginOnToggled;
base.OnInitialActivate(); base.OnInitialActivate();
} }
@ -105,21 +117,6 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
base.OnClose(); base.OnClose();
} }
public async Task InstallPrerequisites()
{
if (FeatureInfo.Prerequisites.Any())
await ShowPrerequisitesDialog(false, FeatureInfo);
}
public async Task RemovePrerequisites()
{
if (FeatureInfo.Prerequisites.Any(p => p.UninstallActions.Any()))
{
await ShowPrerequisitesDialog(true, FeatureInfo);
NotifyOfPropertyChange(nameof(IsEnabled));
}
}
private async Task UpdateEnabled(bool enable) private async Task UpdateEnabled(bool enable)
{ {
if (IsEnabled == enable || FeatureInfo.Instance == null) if (IsEnabled == enable || FeatureInfo.Instance == null)
@ -147,7 +144,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
// Check if all prerequisites are met async // Check if all prerequisites are met async
if (!FeatureInfo.ArePrerequisitesMet()) if (!FeatureInfo.ArePrerequisitesMet())
{ {
await ShowPrerequisitesDialog(false, FeatureInfo); await PluginPrerequisitesInstallDialogViewModel.Show(_dialogService, new List<IPrerequisitesSubject> { FeatureInfo });
if (!FeatureInfo.ArePrerequisitesMet()) if (!FeatureInfo.ArePrerequisitesMet())
{ {
NotifyOfPropertyChange(nameof(IsEnabled)); NotifyOfPropertyChange(nameof(IsEnabled));
@ -173,15 +170,6 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
} }
} }
private async Task<object> ShowPrerequisitesDialog(bool uninstall, IPrerequisitesSubject subject)
{
if (uninstall)
return await _dialogService.ShowDialog<PluginPrerequisitesUninstallDialogViewModel>(new Dictionary<string, object> { { "subject", subject } });
return await _dialogService.ShowDialog<PluginPrerequisitesInstallDialogViewModel>(new Dictionary<string, object> { { "subject", subject } });
}
#region Event handlers
private void OnFeatureEnabling(object sender, PluginFeatureEventArgs e) private void OnFeatureEnabling(object sender, PluginFeatureEventArgs e)
{ {
if (e.PluginFeature != FeatureInfo.Instance) return; if (e.PluginFeature != FeatureInfo.Instance) return;
@ -201,7 +189,5 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
{ {
NotifyOfPropertyChange(nameof(CanToggleEnabled)); NotifyOfPropertyChange(nameof(CanToggleEnabled));
} }
#endregion
} }
} }

View File

@ -23,11 +23,11 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
private readonly IPluginManagementService _pluginManagementService; private readonly IPluginManagementService _pluginManagementService;
private readonly ISettingsVmFactory _settingsVmFactory; private readonly ISettingsVmFactory _settingsVmFactory;
private readonly IWindowManager _windowManager; private readonly IWindowManager _windowManager;
private bool _enabling;
private Plugin _plugin;
private bool _isSettingsPopupOpen;
private bool _canInstallPrerequisites; private bool _canInstallPrerequisites;
private bool _canRemovePrerequisites; private bool _canRemovePrerequisites;
private bool _enabling;
private bool _isSettingsPopupOpen;
private Plugin _plugin;
public PluginSettingsViewModel(Plugin plugin, public PluginSettingsViewModel(Plugin plugin,
ISettingsVmFactory settingsVmFactory, ISettingsVmFactory settingsVmFactory,
@ -142,15 +142,21 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
public async Task InstallPrerequisites() public async Task InstallPrerequisites()
{ {
if (Plugin.Info.Prerequisites.Any()) List<IPrerequisitesSubject> subjects = new() {Plugin.Info};
await ShowPrerequisitesDialog(false, Plugin.Info); subjects.AddRange(Plugin.Features.Where(f => f.AlwaysEnabled));
if (subjects.Any(s => s.Prerequisites.Any()))
await PluginPrerequisitesInstallDialogViewModel.Show(_dialogService, subjects);
} }
public async Task RemovePrerequisites() public async Task RemovePrerequisites(bool forPluginRemoval = false)
{ {
if (Plugin.Info.Prerequisites.Any(p => p.UninstallActions.Any())) List<IPrerequisitesSubject> subjects = new() {Plugin.Info};
subjects.AddRange(!forPluginRemoval ? Plugin.Features.Where(f => f.AlwaysEnabled) : Plugin.Features);
if (subjects.Any(s => s.Prerequisites.Any(p => p.UninstallActions.Any())))
{ {
await ShowPrerequisitesDialog(true, Plugin.Info); await PluginPrerequisitesUninstallDialogViewModel.Show(_dialogService, subjects, forPluginRemoval ? "SKIP, REMOVE PLUGIN" : "CANCEL");
NotifyOfPropertyChange(nameof(IsEnabled)); NotifyOfPropertyChange(nameof(IsEnabled));
NotifyOfPropertyChange(nameof(CanOpenSettings)); NotifyOfPropertyChange(nameof(CanOpenSettings));
} }
@ -182,32 +188,10 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
return; return;
// If the plugin or any of its features has uninstall actions, offer to run these // If the plugin or any of its features has uninstall actions, offer to run these
List<PluginFeatureInfo> featuresToUninstall = Plugin.Features.Where(f => f.Prerequisites.Any(fp => fp.UninstallActions.Any())).ToList(); List<IPrerequisitesSubject> subjects = new() {Plugin.Info};
if (Plugin.Info.Prerequisites.Any(p => p.UninstallActions.Any()) || Plugin.Features.Any(f => f.Prerequisites.Any(fp => fp.UninstallActions.Any()))) subjects.AddRange(Plugin.Features);
{ if (subjects.Any(s => s.Prerequisites.Any(p => p.UninstallActions.Any())))
bool remove = await _dialogService.ShowConfirmDialog( await RemovePrerequisites(true);
"Remove plugin",
"This plugin installed one or more prerequisites.\r\nDo you want to remove these?",
"Uninstall",
"Skip"
);
if (remove)
{
if (Plugin.Info.Prerequisites.Any(p => p.UninstallActions.Any()))
{
object result = await ShowPrerequisitesDialog(true, Plugin.Info);
if (result is bool resultBool && !resultBool)
return;
}
foreach (PluginFeatureInfo pluginFeatureInfo in featuresToUninstall)
{
object result = await ShowPrerequisitesDialog(true, pluginFeatureInfo);
if (result is bool resultBool && !resultBool)
return;
}
}
}
try try
{ {
@ -235,12 +219,10 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
} }
} }
protected override void OnInitialActivate() private void PluginManagementServiceOnPluginToggled(object? sender, PluginEventArgs e)
{ {
foreach (PluginFeatureInfo pluginFeatureInfo in Plugin.Features) NotifyOfPropertyChange(nameof(IsEnabled));
Items.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeatureInfo, false)); NotifyOfPropertyChange(nameof(CanOpenSettings));
base.OnInitialActivate();
} }
private async Task UpdateEnabled(bool enable) private async Task UpdateEnabled(bool enable)
@ -266,28 +248,34 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
} }
// Check if all prerequisites are met async // Check if all prerequisites are met async
if (!Plugin.Info.ArePrerequisitesMet()) List<IPrerequisitesSubject> subjects = new() {Plugin.Info};
subjects.AddRange(Plugin.Features.Where(f => f.AlwaysEnabled || f.EnabledInStorage));
if (subjects.Any(s => !s.ArePrerequisitesMet()))
{ {
await ShowPrerequisitesDialog(false, Plugin.Info); await PluginPrerequisitesInstallDialogViewModel.Show(_dialogService, subjects);
if (!Plugin.Info.ArePrerequisitesMet()) if (!subjects.All(s => s.ArePrerequisitesMet()))
{ {
CancelEnable(); CancelEnable();
return; return;
} }
} }
try await Task.Run(() =>
{ {
await Task.Run(() => _pluginManagementService.EnablePlugin(Plugin, true, true)); try
} {
catch (Exception e) _pluginManagementService.EnablePlugin(Plugin, true, true);
{ }
_messageService.ShowMessage($"Failed to enable plugin {Plugin.Info.Name}\r\n{e.Message}", "VIEW LOGS", ShowLogsFolder); catch (Exception e)
} {
finally _messageService.ShowMessage($"Failed to enable plugin {Plugin.Info.Name}\r\n{e.Message}", "VIEW LOGS", ShowLogsFolder);
{ }
Enabling = false; finally
} {
Enabling = false;
}
});
} }
else else
_pluginManagementService.DisablePlugin(Plugin, true); _pluginManagementService.DisablePlugin(Plugin, true);
@ -305,15 +293,31 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
private void CheckPrerequisites() private void CheckPrerequisites()
{ {
CanInstallPrerequisites = Plugin.Info.Prerequisites.Any(); CanInstallPrerequisites = Plugin.Info.Prerequisites.Any() ||
CanRemovePrerequisites = Plugin.Info.Prerequisites.Any(p => p.UninstallActions.Any()); Plugin.Features.Where(f => f.AlwaysEnabled).Any(f => f.Prerequisites.Any());
CanRemovePrerequisites = Plugin.Info.Prerequisites.Any(p => p.UninstallActions.Any()) ||
Plugin.Features.Where(f => f.AlwaysEnabled).Any(f => f.Prerequisites.Any(p => p.UninstallActions.Any()));
} }
private async Task<object> ShowPrerequisitesDialog(bool uninstall, IPrerequisitesSubject subject) #region Overrides of Screen
protected override void OnInitialActivate()
{ {
if (uninstall) foreach (PluginFeatureInfo pluginFeatureInfo in Plugin.Features)
return await _dialogService.ShowDialog<PluginPrerequisitesUninstallDialogViewModel>(new Dictionary<string, object> {{"subject", subject } }); Items.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeatureInfo, false));
return await _dialogService.ShowDialog<PluginPrerequisitesInstallDialogViewModel>(new Dictionary<string, object> {{ "subject", subject } });
_pluginManagementService.PluginDisabled += PluginManagementServiceOnPluginToggled;
_pluginManagementService.PluginEnabled += PluginManagementServiceOnPluginToggled;
base.OnInitialActivate();
} }
protected override void OnClose()
{
_pluginManagementService.PluginDisabled -= PluginManagementServiceOnPluginToggled;
_pluginManagementService.PluginEnabled -= PluginManagementServiceOnPluginToggled;
base.OnClose();
}
#endregion
} }
} }