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

Plugins - Reworked bootstrapper

This commit is contained in:
Robert 2021-04-30 17:39:58 +02:00
parent 5cae14efd3
commit 21700aaad5
13 changed files with 288 additions and 135 deletions

View File

@ -1,26 +0,0 @@
namespace Artemis.Core
{
/// <summary>
/// An optional entry point for your plugin
/// </summary>
public interface IPluginBootstrapper
{
/// <summary>
/// Called when the plugin is loaded
/// </summary>
/// <param name="plugin"></param>
void OnPluginLoaded(Plugin plugin);
/// <summary>
/// Called when the plugin is activated
/// </summary>
/// <param name="plugin">The plugin instance of your plugin</param>
void OnPluginEnabled(Plugin plugin);
/// <summary>
/// Called when the plugin is deactivated or when Artemis shuts down
/// </summary>
/// <param name="plugin">The plugin instance of your plugin</param>
void OnPluginDisabled(Plugin plugin);
}
}

View File

@ -71,7 +71,7 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Gets the plugin bootstrapper /// Gets the plugin bootstrapper
/// </summary> /// </summary>
public IPluginBootstrapper? Bootstrapper { get; internal set; } public PluginBootstrapper? Bootstrapper { get; internal set; }
/// <summary> /// <summary>
/// The Ninject kernel of the plugin /// The Ninject kernel of the plugin
@ -118,10 +118,11 @@ namespace Artemis.Core
/// Looks up the feature info the feature of type <typeparamref name="T" /> /// Looks up the feature info the feature of type <typeparamref name="T" />
/// </summary> /// </summary>
/// <typeparam name="T">The type of feature to find</typeparam> /// <typeparam name="T">The type of feature to find</typeparam>
/// <returns>If found, feature info of the feature</returns> /// <returns>Feature info of the feature</returns>
public PluginFeatureInfo? GetFeatureInfo<T>() where T : PluginFeature public PluginFeatureInfo GetFeatureInfo<T>() where T : PluginFeature
{ {
return _features.FirstOrDefault(i => i.FeatureType == typeof(T)); // This should be a safe assumption because any type of PluginFeature is registered and added
return _features.First(i => i.FeatureType == typeof(T));
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -0,0 +1,100 @@
namespace Artemis.Core
{
/// <summary>
/// An optional entry point for your plugin
/// </summary>
public abstract class PluginBootstrapper
{
private Plugin? _plugin;
/// <summary>
/// Called when the plugin is loaded
/// </summary>
/// <param name="plugin"></param>
public virtual void OnPluginLoaded(Plugin plugin)
{
}
/// <summary>
/// Called when the plugin is activated
/// </summary>
/// <param name="plugin">The plugin instance of your plugin</param>
public virtual void OnPluginEnabled(Plugin plugin)
{
}
/// <summary>
/// Called when the plugin is deactivated or when Artemis shuts down
/// </summary>
/// <param name="plugin">The plugin instance of your plugin</param>
public virtual void OnPluginDisabled(Plugin plugin)
{
}
/// <summary>
/// Adds the provided prerequisite to the plugin.
/// </summary>
/// <param name="prerequisite">The prerequisite to add</param>
public void AddPluginPrerequisite(PluginPrerequisite prerequisite)
{
// TODO: We can keep track of them and add them after load, same goes for the others
if (_plugin == null)
throw new ArtemisPluginException("Cannot add plugin prerequisites before the plugin is loaded");
if (!_plugin.Info.Prerequisites.Contains(prerequisite))
_plugin.Info.Prerequisites.Add(prerequisite);
}
/// <summary>
/// Removes the provided prerequisite from the plugin.
/// </summary>
/// <param name="prerequisite">The prerequisite to remove</param>
/// <returns>
/// <see langword="true" /> is successfully removed; otherwise <see langword="false" />. This method also returns
/// <see langword="false" /> if the prerequisite was not found.
/// </returns>
public bool RemovePluginPrerequisite(PluginPrerequisite prerequisite)
{
if (_plugin == null)
throw new ArtemisPluginException("Cannot add plugin prerequisites before the plugin is loaded");
return _plugin.Info.Prerequisites.Remove(prerequisite);
}
/// <summary>
/// Adds the provided prerequisite to the feature of type <typeparamref name="T"/>.
/// </summary>
/// <param name="prerequisite">The prerequisite to add</param>
public void AddFeaturePrerequisite<T>(PluginPrerequisite prerequisite) where T : PluginFeature
{
if (_plugin == null)
throw new ArtemisPluginException("Cannot add feature prerequisites before the plugin is loaded");
PluginFeatureInfo info = _plugin.GetFeatureInfo<T>();
if (!info.Prerequisites.Contains(prerequisite))
info.Prerequisites.Add(prerequisite);
}
/// <summary>
/// Removes the provided prerequisite from the feature of type <typeparamref name="T"/>.
/// </summary>
/// <param name="prerequisite">The prerequisite to remove</param>
/// <returns>
/// <see langword="true" /> is successfully removed; otherwise <see langword="false" />. This method also returns
/// <see langword="false" /> if the prerequisite was not found.
/// </returns>
public bool RemoveFeaturePrerequisite<T>(PluginPrerequisite prerequisite) where T : PluginFeature
{
if (_plugin == null)
throw new ArtemisPluginException("Cannot add feature prerequisites before the plugin is loaded");
return _plugin.GetFeatureInfo<T>().Prerequisites.Remove(prerequisite);
}
internal void InternalOnPluginLoaded(Plugin plugin)
{
_plugin = plugin;
OnPluginLoaded(plugin);
}
}
}

View File

@ -15,7 +15,7 @@ namespace Artemis.Core
/// Represents basic info about a plugin feature and contains a reference to the instance of said feature /// Represents basic info about a plugin feature and contains a reference to the instance of said feature
/// </summary> /// </summary>
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public class PluginFeatureInfo : CorePropertyChanged public class PluginFeatureInfo : CorePropertyChanged, IPrerequisitesSubject
{ {
private string? _description; private string? _description;
private string? _icon; private string? _icon;
@ -31,7 +31,7 @@ namespace Artemis.Core
Description = attribute?.Description; Description = attribute?.Description;
Icon = attribute?.Icon; Icon = attribute?.Icon;
AlwaysEnabled = attribute?.AlwaysEnabled ?? false; AlwaysEnabled = attribute?.AlwaysEnabled ?? false;
if (Icon != null) return; if (Icon != null) return;
if (typeof(BaseDataModelExpansion).IsAssignableFrom(featureType)) if (typeof(BaseDataModelExpansion).IsAssignableFrom(featureType))
Icon = "TableAdd"; Icon = "TableAdd";
@ -130,18 +130,11 @@ namespace Artemis.Core
internal set => SetAndNotify(ref _instance, value); internal set => SetAndNotify(ref _instance, value);
} }
/// <summary> /// <inheritdoc />
/// Gets a list of prerequisites for this plugin feature
/// </summary>
public List<PluginPrerequisite> Prerequisites { get; } = new(); public List<PluginPrerequisite> Prerequisites { get; } = new();
/// <summary> /// <inheritdoc />
/// Determines whether the prerequisites of this feature are met public bool ArePrerequisitesMet() => Prerequisites.All(p => p.IsMet());
/// </summary>
public bool ArePrerequisitesMet()
{
return Prerequisites.All(p => p.IsMet());
}
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() public override string ToString()

View File

@ -10,7 +10,7 @@ namespace Artemis.Core
/// Represents basic info about a plugin and contains a reference to the instance of said plugin /// Represents basic info about a plugin and contains a reference to the instance of said plugin
/// </summary> /// </summary>
[JsonObject(MemberSerialization.OptIn)] [JsonObject(MemberSerialization.OptIn)]
public class PluginInfo : CorePropertyChanged public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
{ {
private bool _autoEnableFeatures = true; private bool _autoEnableFeatures = true;
private string? _description; private string? _description;
@ -119,19 +119,11 @@ namespace Artemis.Core
internal set => SetAndNotify(ref _plugin, value); internal set => SetAndNotify(ref _plugin, value);
} }
/// <summary> /// <inheritdoc />
/// Gets a list of prerequisites for this plugin
/// </summary>
public List<PluginPrerequisite> Prerequisites { get; } = new(); public List<PluginPrerequisite> Prerequisites { get; } = new();
/// <inheritdoc />
/// <summary> public bool ArePrerequisitesMet() => Prerequisites.All(p => p.IsMet());
/// Determines whether the prerequisites of this plugin are met
/// </summary>
public bool ArePrerequisitesMet()
{
return Prerequisites.All(p => p.IsMet());
}
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() public override string ToString()

View File

@ -0,0 +1,21 @@
using System.Collections.Generic;
using System.Linq;
namespace Artemis.Core
{
/// <summary>
/// Represents a type that has prerequisites
/// </summary>
public interface IPrerequisitesSubject
{
/// <summary>
/// Gets a list of prerequisites for this plugin
/// </summary>
List<PluginPrerequisite> Prerequisites { get; }
/// <summary>
/// Determines whether the prerequisites of this plugin are met
/// </summary>
bool ArePrerequisitesMet();
}
}

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -11,24 +12,6 @@ namespace Artemis.Core
{ {
private PluginPrerequisiteAction? _currentAction; private PluginPrerequisiteAction? _currentAction;
/// <summary>
/// Creates a new instance of the <see cref="PluginPrerequisite" /> class
/// </summary>
/// <param name="plugin">The plugin this is a prerequisite for</param>
protected PluginPrerequisite(Plugin plugin)
{
Plugin = plugin;
}
/// <summary>
/// Creates a new instance of the <see cref="PluginPrerequisite" /> class
/// </summary>
/// <param name="pluginFeature">The plugin feature this is a prerequisite for</param>
protected PluginPrerequisite(PluginFeature pluginFeature)
{
PluginFeature = pluginFeature;
}
/// <summary> /// <summary>
/// Gets the name of the prerequisite /// Gets the name of the prerequisite
/// </summary> /// </summary>
@ -63,18 +46,6 @@ namespace Artemis.Core
private set => SetAndNotify(ref _currentAction, value); private set => SetAndNotify(ref _currentAction, value);
} }
/// <summary>
/// Gets or sets the plugin this prerequisite is for
/// <para>Note: Only one plugin or a plugin feature can be set at once</para>
/// </summary>
public Plugin? Plugin { get; }
/// <summary>
/// Gets or sets the feature this prerequisite is for
/// <para>Note: Only one plugin or a plugin feature can be set at once</para>
/// </summary>
public PluginFeature? PluginFeature { get; }
/// <summary> /// <summary>
/// Execute all install actions /// Execute all install actions
/// </summary> /// </summary>

View File

@ -345,13 +345,13 @@ namespace Artemis.Core.Services
if (!featureTypes.Any()) if (!featureTypes.Any())
_logger.Warning("Plugin {plugin} contains no features", plugin); _logger.Warning("Plugin {plugin} contains no features", plugin);
List<Type> bootstrappers = plugin.Assembly.GetTypes().Where(t => typeof(IPluginBootstrapper).IsAssignableFrom(t)).ToList(); List<Type> bootstrappers = plugin.Assembly.GetTypes().Where(t => typeof(PluginBootstrapper).IsAssignableFrom(t)).ToList();
if (bootstrappers.Count > 1) if (bootstrappers.Count > 1)
_logger.Warning($"{plugin} has more than one bootstrapper, only initializing {bootstrappers.First().FullName}"); _logger.Warning($"{plugin} has more than one bootstrapper, only initializing {bootstrappers.First().FullName}");
if (bootstrappers.Any()) if (bootstrappers.Any())
{ {
plugin.Bootstrapper = (IPluginBootstrapper?) Activator.CreateInstance(bootstrappers.First()); plugin.Bootstrapper = (PluginBootstrapper?) Activator.CreateInstance(bootstrappers.First());
plugin.Bootstrapper?.OnPluginLoaded(plugin); plugin.Bootstrapper?.InternalOnPluginLoaded(plugin);
} }
lock (_plugins) lock (_plugins)

View File

@ -20,19 +20,19 @@ namespace Artemis.UI.Screens.Plugins
private bool _isFinished; private bool _isFinished;
private CancellationTokenSource _tokenSource; private CancellationTokenSource _tokenSource;
public PluginPrerequisitesInstallDialogViewModel(object pluginOrFeature, IPrerequisitesVmFactory prerequisitesVmFactory, IDialogService dialogService) public PluginPrerequisitesInstallDialogViewModel(IPrerequisitesSubject subject, IPrerequisitesVmFactory prerequisitesVmFactory, IDialogService dialogService)
{ {
_dialogService = dialogService; _dialogService = dialogService;
// Constructor overloading doesn't work very well with Kernel.Get<T> :( // Constructor overloading doesn't work very well with Kernel.Get<T> :(
if (pluginOrFeature is Plugin plugin) if (subject is PluginInfo plugin)
{ {
Plugin = plugin; PluginInfo = plugin;
Prerequisites = new BindableCollection<PluginPrerequisiteViewModel>(plugin.Info.Prerequisites.Select(p => prerequisitesVmFactory.PluginPrerequisiteViewModel(p, false))); Prerequisites = new BindableCollection<PluginPrerequisiteViewModel>(plugin.Prerequisites.Select(p => prerequisitesVmFactory.PluginPrerequisiteViewModel(p, false)));
} }
else if (pluginOrFeature is PluginFeature feature) else if (subject is PluginFeatureInfo feature)
{ {
Feature = feature; FeatureInfo = feature;
Prerequisites = new BindableCollection<PluginPrerequisiteViewModel>(feature.Info.Prerequisites.Select(p => prerequisitesVmFactory.PluginPrerequisiteViewModel(p, false))); Prerequisites = new BindableCollection<PluginPrerequisiteViewModel>(feature.Prerequisites.Select(p => prerequisitesVmFactory.PluginPrerequisiteViewModel(p, false)));
} }
else else
throw new ArtemisUIException($"Expected plugin or feature to be passed to {nameof(PluginPrerequisitesInstallDialogViewModel)}"); throw new ArtemisUIException($"Expected plugin or feature to be passed to {nameof(PluginPrerequisitesInstallDialogViewModel)}");
@ -42,8 +42,8 @@ namespace Artemis.UI.Screens.Plugins
} }
public PluginFeature Feature { get; } public PluginInfo PluginInfo { get; }
public Plugin Plugin { get; } public PluginFeatureInfo FeatureInfo { get; }
public BindableCollection<PluginPrerequisiteViewModel> Prerequisites { get; } public BindableCollection<PluginPrerequisiteViewModel> Prerequisites { get; }
public PluginPrerequisiteViewModel ActivePrerequisite public PluginPrerequisiteViewModel ActivePrerequisite
@ -64,6 +64,9 @@ 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 />
@ -111,7 +114,7 @@ namespace Artemis.UI.Screens.Plugins
"Confirm", "Confirm",
"" ""
); );
await _dialogService.ShowDialog<PluginPrerequisitesInstallDialogViewModel>(new Dictionary<string, object> {{"pluginOrFeature", Plugin}}); await _dialogService.ShowDialog<PluginPrerequisitesInstallDialogViewModel>(new Dictionary<string, object> {{"subject", PluginInfo}});
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
@ -136,10 +139,10 @@ namespace Artemis.UI.Screens.Plugins
protected override void OnInitialActivate() protected override void OnInitialActivate()
{ {
CanInstall = false; CanInstall = false;
if (Plugin != null) if (PluginInfo != null)
Task.Run(() => CanInstall = !Plugin.Info.ArePrerequisitesMet()); Task.Run(() => CanInstall = !PluginInfo.ArePrerequisitesMet());
else else
Task.Run(() => CanInstall = !Feature.Info.ArePrerequisitesMet()); Task.Run(() => CanInstall = !FeatureInfo.ArePrerequisitesMet());
base.OnInitialActivate(); base.OnInitialActivate();
} }

View File

@ -22,22 +22,22 @@ namespace Artemis.UI.Screens.Plugins
private bool _isFinished; private bool _isFinished;
private CancellationTokenSource _tokenSource; private CancellationTokenSource _tokenSource;
public PluginPrerequisitesUninstallDialogViewModel(object pluginOrFeature, IPrerequisitesVmFactory prerequisitesVmFactory, IDialogService dialogService, public PluginPrerequisitesUninstallDialogViewModel(IPrerequisitesSubject subject, IPrerequisitesVmFactory prerequisitesVmFactory, IDialogService dialogService,
IPluginManagementService pluginManagementService) IPluginManagementService pluginManagementService)
{ {
_dialogService = dialogService; _dialogService = dialogService;
_pluginManagementService = pluginManagementService; _pluginManagementService = pluginManagementService;
// Constructor overloading doesn't work very well with Kernel.Get<T> :( // Constructor overloading doesn't work very well with Kernel.Get<T> :(
if (pluginOrFeature is Plugin plugin) if (subject is PluginInfo plugin)
{ {
Plugin = plugin; PluginInfo = plugin;
Prerequisites = new BindableCollection<PluginPrerequisiteViewModel>(plugin.Info.Prerequisites.Select(p => prerequisitesVmFactory.PluginPrerequisiteViewModel(p, true))); Prerequisites = new BindableCollection<PluginPrerequisiteViewModel>(plugin.Prerequisites.Select(p => prerequisitesVmFactory.PluginPrerequisiteViewModel(p, true)));
} }
else if (pluginOrFeature is PluginFeature feature) else if (subject is PluginFeatureInfo feature)
{ {
Feature = feature; FeatureInfo = feature;
Prerequisites = new BindableCollection<PluginPrerequisiteViewModel>(feature.Info.Prerequisites.Select(p => prerequisitesVmFactory.PluginPrerequisiteViewModel(p, true))); Prerequisites = new BindableCollection<PluginPrerequisiteViewModel>(feature.Prerequisites.Select(p => prerequisitesVmFactory.PluginPrerequisiteViewModel(p, true)));
} }
else else
throw new ArtemisUIException($"Expected plugin or feature to be passed to {nameof(PluginPrerequisitesInstallDialogViewModel)}"); throw new ArtemisUIException($"Expected plugin or feature to be passed to {nameof(PluginPrerequisitesInstallDialogViewModel)}");
@ -47,8 +47,8 @@ namespace Artemis.UI.Screens.Plugins
} }
public PluginFeature Feature { get; } public PluginFeatureInfo FeatureInfo { get; }
public Plugin Plugin { get; } public PluginInfo PluginInfo { get; }
public BindableCollection<PluginPrerequisiteViewModel> Prerequisites { get; } public BindableCollection<PluginPrerequisiteViewModel> Prerequisites { get; }
public PluginPrerequisiteViewModel ActivePrerequisite public PluginPrerequisiteViewModel ActivePrerequisite
@ -69,6 +69,9 @@ 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 />
@ -84,7 +87,10 @@ namespace Artemis.UI.Screens.Plugins
{ {
CanUninstall = false; CanUninstall = false;
_pluginManagementService.DisablePlugin(Plugin, true); if (PluginInfo != null)
_pluginManagementService.DisablePlugin(PluginInfo.Plugin, true);
else if (FeatureInfo?.Instance != null)
_pluginManagementService.DisablePluginFeature(FeatureInfo.Instance, true);
_tokenSource = new CancellationTokenSource(); _tokenSource = new CancellationTokenSource();
try try
@ -118,7 +124,7 @@ namespace Artemis.UI.Screens.Plugins
"Confirm", "Confirm",
"" ""
); );
await _dialogService.ShowDialog<PluginPrerequisitesUninstallDialogViewModel>(new Dictionary<string, object> {{"pluginOrFeature", Plugin}}); await _dialogService.ShowDialog<PluginPrerequisitesUninstallDialogViewModel>(new Dictionary<string, object> {{"subject", PluginInfo}});
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
@ -144,10 +150,10 @@ 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 (Plugin != null) if (PluginInfo != null)
Task.Run(() => CanUninstall = Plugin.Info.Prerequisites.Any(p => p.IsMet())); Task.Run(() => CanUninstall = PluginInfo.Prerequisites.Any(p => p.IsMet()));
else else
Task.Run(() => CanUninstall = Feature.Info.Prerequisites.Any(p => p.IsMet())); Task.Run(() => CanUninstall = FeatureInfo.Prerequisites.Any(p => p.IsMet()));
base.OnInitialActivate(); base.OnInitialActivate();
} }

View File

@ -12,7 +12,13 @@
d:DesignHeight="450" d:DesignWidth="800" d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:PluginFeatureViewModel}"> d:DataContext="{d:DesignInstance local:PluginFeatureViewModel}">
<UserControl.Resources> <UserControl.Resources>
<converters:NullToVisibilityConverter x:Key="NullToVisibilityConverter" /> <ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.PopupBox.xaml" />
</ResourceDictionary.MergedDictionaries>
<converters:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
</ResourceDictionary>
</UserControl.Resources> </UserControl.Resources>
<Grid Margin="-3 -8"> <Grid Margin="-3 -8">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
@ -23,11 +29,11 @@
<!-- Icon column --> <!-- Icon column -->
<shared:ArtemisIcon Grid.Column="0" <shared:ArtemisIcon Grid.Column="0"
Icon="{Binding FeatureInfo.Icon}" Icon="{Binding FeatureInfo.Icon}"
Width="20" Width="20"
VerticalAlignment="Center" VerticalAlignment="Center"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Visibility="{Binding LoadException, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Inverted, FallbackValue=Collapsed}" /> Visibility="{Binding LoadException, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Inverted, FallbackValue=Collapsed}" />
<Button Grid.Column="0" <Button Grid.Column="0"
Margin="-8" Margin="-8"
@ -42,16 +48,16 @@
</Button> </Button>
<!-- Display name column --> <!-- Display name column -->
<TextBlock Grid.Column="1" <TextBlock Grid.Column="1"
Text="{Binding FeatureInfo.Name}" Text="{Binding FeatureInfo.Name}"
Style="{StaticResource MaterialDesignTextBlock}" Style="{StaticResource MaterialDesignTextBlock}"
VerticalAlignment="Center" VerticalAlignment="Center"
TextWrapping="Wrap" TextWrapping="Wrap"
ToolTip="{Binding FeatureInfo.Description}" /> ToolTip="{Binding FeatureInfo.Description}" />
<!-- Enable toggle column --> <!-- Enable toggle column -->
<StackPanel Grid.Column="2" <StackPanel Grid.Column="2"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Margin="8" Margin="8"
Visibility="{Binding Enabling, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay, FallbackValue=Collapsed}" Visibility="{Binding Enabling, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay, FallbackValue=Collapsed}"
Orientation="Horizontal" Orientation="Horizontal"
@ -63,11 +69,31 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Margin="0 0 5 0" Margin="0 0 5 0"
Visibility="{Binding ShowShield, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}" /> Visibility="{Binding ShowShield, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}" />
<CheckBox Style="{StaticResource MaterialDesignCheckBox}" <CheckBox Style="{StaticResource MaterialDesignCheckBox}"
IsChecked="{Binding IsEnabled}" IsChecked="{Binding IsEnabled}"
IsEnabled="{Binding CanToggleEnabled}"> IsEnabled="{Binding CanToggleEnabled}">
Feature enabled Feature enabled
</CheckBox> </CheckBox>
<materialDesign:PopupBox Style="{StaticResource MaterialDesignToolPopupBox}"
Margin="0 -4 -10 -4"
Foreground="{StaticResource MaterialDesignBody}"
IsEnabled="{Binding IsPopupEnabled}">
<StackPanel>
<Button Command="{s:Action InstallPrerequisites}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="CheckAll" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Install prerequisites</TextBlock>
</StackPanel>
</Button>
<Button Command="{s:Action RemovePrerequisites}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="Delete" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Remove prerequisites</TextBlock>
</StackPanel>
</Button>
</StackPanel>
</materialDesign:PopupBox>
</StackPanel> </StackPanel>
<StackPanel Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="7" <StackPanel Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="7"
Visibility="{Binding Enabling, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay, FallbackValue=Collapsed}"> Visibility="{Binding Enabling, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay, FallbackValue=Collapsed}">

View File

@ -1,9 +1,12 @@
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq;
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.Screens.Plugins;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Stylet; using Stylet;
@ -16,6 +19,9 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
private readonly IPluginManagementService _pluginManagementService; private readonly IPluginManagementService _pluginManagementService;
private bool _enabling; private bool _enabling;
private readonly IMessageService _messageService; 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,
@ -51,6 +57,9 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
} }
public bool CanToggleEnabled => FeatureInfo.Plugin.IsEnabled && !FeatureInfo.AlwaysEnabled; public bool CanToggleEnabled => FeatureInfo.Plugin.IsEnabled && !FeatureInfo.AlwaysEnabled;
public bool CanInstallPrerequisites => FeatureInfo.Prerequisites.Any();
public bool CanRemovePrerequisites => FeatureInfo.Prerequisites.Any(p => p.UninstallActions.Any());
public bool IsPopupEnabled => CanInstallPrerequisites || CanRemovePrerequisites;
public void ShowLogsFolder() public void ShowLogsFolder()
{ {
@ -96,6 +105,21 @@ 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)
@ -120,7 +144,18 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
} }
} }
await Task.Run(() => _pluginManagementService.EnablePluginFeature(FeatureInfo.Instance, true)); // Check if all prerequisites are met async
if (!FeatureInfo.ArePrerequisitesMet())
{
await ShowPrerequisitesDialog(false, FeatureInfo);
if (!FeatureInfo.ArePrerequisitesMet())
{
NotifyOfPropertyChange(nameof(IsEnabled));
return;
}
}
await Task.Run(() => _pluginManagementService.EnablePluginFeature(FeatureInfo.Instance!, true));
} }
catch (Exception e) catch (Exception e)
{ {
@ -138,6 +173,13 @@ 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 #region Event handlers
private void OnFeatureEnabling(object sender, PluginFeatureEventArgs e) private void OnFeatureEnabling(object sender, PluginFeatureEventArgs e)

View File

@ -136,24 +136,26 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
if (wasEnabled) if (wasEnabled)
await UpdateEnabled(true); await UpdateEnabled(true);
_messageService.ShowMessage("Reloaded plugin.");
} }
public async Task InstallPrerequisites() public async Task InstallPrerequisites()
{ {
if (Plugin.Info.Prerequisites.Any()) if (Plugin.Info.Prerequisites.Any())
await ShowPrerequisitesDialog(false); await ShowPrerequisitesDialog(false, Plugin.Info);
} }
public async Task RemovePrerequisites() public async Task RemovePrerequisites()
{ {
if (Plugin.Info.Prerequisites.Any(p => p.UninstallActions.Any())) if (Plugin.Info.Prerequisites.Any(p => p.UninstallActions.Any()))
{ {
await ShowPrerequisitesDialog(true); await ShowPrerequisitesDialog(true, Plugin.Info);
NotifyOfPropertyChange(nameof(IsEnabled)); NotifyOfPropertyChange(nameof(IsEnabled));
NotifyOfPropertyChange(nameof(CanOpenSettings)); NotifyOfPropertyChange(nameof(CanOpenSettings));
} }
} }
public async Task RemoveSettings() public async Task RemoveSettings()
{ {
bool confirmed = await _dialogService.ShowConfirmDialog("Clear plugin settings", "Are you sure you want to clear the settings of this plugin?"); bool confirmed = await _dialogService.ShowConfirmDialog("Clear plugin settings", "Are you sure you want to clear the settings of this plugin?");
@ -180,9 +182,31 @@ 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();
if (Plugin.Info.Prerequisites.Any(p => p.UninstallActions.Any()) || Plugin.Features.Any(f => f.Prerequisites.Any(fp => fp.UninstallActions.Any()))) if (Plugin.Info.Prerequisites.Any(p => p.UninstallActions.Any()) || Plugin.Features.Any(f => f.Prerequisites.Any(fp => fp.UninstallActions.Any())))
{ {
bool remove = await _dialogService.ShowConfirmDialog(
"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
@ -244,7 +268,7 @@ 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()) if (!Plugin.Info.ArePrerequisitesMet())
{ {
await ShowPrerequisitesDialog(false); await ShowPrerequisitesDialog(false, Plugin.Info);
if (!Plugin.Info.ArePrerequisitesMet()) if (!Plugin.Info.ArePrerequisitesMet())
{ {
CancelEnable(); CancelEnable();
@ -285,11 +309,11 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
CanRemovePrerequisites = Plugin.Info.Prerequisites.Any(p => p.UninstallActions.Any()); CanRemovePrerequisites = Plugin.Info.Prerequisites.Any(p => p.UninstallActions.Any());
} }
private async Task<object> ShowPrerequisitesDialog(bool uninstall) private async Task<object> ShowPrerequisitesDialog(bool uninstall, IPrerequisitesSubject subject)
{ {
if (uninstall) if (uninstall)
return await _dialogService.ShowDialog<PluginPrerequisitesUninstallDialogViewModel>(new Dictionary<string, object> { { "pluginOrFeature", Plugin } }); return await _dialogService.ShowDialog<PluginPrerequisitesUninstallDialogViewModel>(new Dictionary<string, object> {{"subject", subject } });
return await _dialogService.ShowDialog<PluginPrerequisitesInstallDialogViewModel>(new Dictionary<string, object> { { "pluginOrFeature", Plugin } }); return await _dialogService.ShowDialog<PluginPrerequisitesInstallDialogViewModel>(new Dictionary<string, object> {{ "subject", subject } });
} }
} }
} }