Plugins - Added platform support

Settings - Fixed updating issues in plugin tab
UI - Don't initialize in design mode
UI - Move ReactiveCoreWindow to Artemis.Shared
Window service - Return window created by ShowWindow
Window service - Added CreateOpenFileDialog API
This commit is contained in:
Robert committed 2022-07-22 20:28:07 +02:00
1 parent e385165200
commit 75d85985a9
37 files changed
+600 -375

No files matched your search

@@ -4,8 +4,10 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:plugins="clr-namespace:Artemis.UI.Screens.Plugins"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Plugins.PluginFeatureView">
x:Class="Artemis.UI.Screens.Plugins.PluginFeatureView"
x:DataType="plugins:PluginFeatureViewModel">
<Grid ColumnDefinitions="30,*,Auto">
<Grid.ContextFlyout>
<MenuFlyout>
@@ -24,14 +26,15 @@
<!-- Icon column -->
<shared:ArtemisIcon Grid.Column="0"
Icon="{Binding FeatureInfo.ResolvedIcon}"
Icon="{CompiledBinding FeatureInfo.ResolvedIcon}"
Fill="False"
Width="20"
Height="20"
IsVisible="{Binding LoadException, Converter={x:Static ObjectConverters.IsNull}}" />
IsVisible="{CompiledBinding LoadException, Converter={x:Static ObjectConverters.IsNull}}" />
<Button Grid.Column="0"
Classes="AppBarButton icon-button"
IsVisible="{Binding LoadException, Converter={x:Static ObjectConverters.IsNotNull}}"
IsVisible="{CompiledBinding LoadException, Converter={x:Static ObjectConverters.IsNotNull}}"
Foreground="#E74C4C"
ToolTip.Tip="An exception occurred while enabling this feature, click to view"
Command="{Binding ViewLoadException}">
@@ -40,15 +43,15 @@
<!-- Display name column -->
<TextBlock Grid.Column="1"
Text="{Binding FeatureInfo.Name}"
Text="{CompiledBinding FeatureInfo.Name}"
TextWrapping="Wrap"
VerticalAlignment="Center"
ToolTip.Tip="{Binding FeatureInfo.Description}" />
ToolTip.Tip="{CompiledBinding FeatureInfo.Description}" />
<!-- Enable toggle column -->
<StackPanel Grid.Column="2"
HorizontalAlignment="Right"
IsVisible="{Binding !Enabling}"
IsVisible="{CompiledBinding !Enabling}"
Orientation="Horizontal"
ToolTip.Tip="This feature cannot be disabled without disabling the whole plugin">
<avalonia:MaterialIcon Kind="ShieldHalfFull"
@@ -57,12 +60,12 @@
Margin="0 0 5 0"
IsVisible="{Binding ShowShield}" />
<CheckBox IsChecked="{Binding IsEnabled}" IsEnabled="{Binding CanToggleEnabled}">
Feature enabled
<CheckBox IsChecked="{CompiledBinding IsEnabled}" IsEnabled="{CompiledBinding CanToggleEnabled}">
Enable feature
</CheckBox>
</StackPanel>
<StackPanel Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="7" IsVisible="{Binding Enabling}">
<StackPanel Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="7" IsVisible="{CompiledBinding Enabling}">
<ProgressBar Value="0" IsIndeterminate="True" />
</StackPanel>
@@ -71,7 +71,7 @@ namespace Artemis.UI.Screens.Plugins
public bool IsEnabled
{
get => FeatureInfo.Instance != null && FeatureInfo.Instance.IsEnabled;
get => FeatureInfo.AlwaysEnabled || FeatureInfo.Instance != null && FeatureInfo.Instance.IsEnabled;
set => Dispatcher.UIThread.InvokeAsync(() => UpdateEnabled(value));
}
@@ -0,0 +1,11 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:plugins="clr-namespace:Artemis.UI.Screens.Plugins"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Plugins.PluginPlatformView"
x:DataType="plugins:PluginPlatformViewModel">
<avalonia:MaterialIcon Kind="{CompiledBinding Icon}" ToolTip.Tip="{CompiledBinding DisplayName}"/>
</UserControl>
@@ -0,0 +1,18 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Screens.Plugins;
public partial class PluginPlatformView : UserControl
{
public PluginPlatformView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
@@ -0,0 +1,16 @@
using Material.Icons;
using ReactiveUI;
namespace Artemis.UI.Screens.Plugins;
public class PluginPlatformViewModel : ReactiveObject
{
public PluginPlatformViewModel(string displayName, MaterialIconKind icon)
{
DisplayName = displayName;
Icon = icon;
}
public string DisplayName { get; set; }
public MaterialIconKind Icon { get; set; }
}
@@ -9,10 +9,11 @@
mc:Ignorable="d" d:DesignWidth="900" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Plugins.PluginSettingsView"
x:DataType="plugins:PluginSettingsViewModel">
<Border Classes="card" Padding="15" Margin="0 5">
<Border Classes="card" Padding="15" Margin="0 5">
<Grid RowDefinitions="*,Auto" ColumnDefinitions="4*,5*">
<Grid Grid.Row="0" RowDefinitions="Auto,Auto,*" ColumnDefinitions="80,*">
<shared:ArtemisIcon Icon="{CompiledBinding Plugin.Info.ResolvedIcon}"
<Grid Grid.Row="0" RowDefinitions="Auto,Auto,*" ColumnDefinitions="80,*, Auto">
<shared:ArtemisIcon Icon="{CompiledBinding Plugin.Info.ResolvedIcon}"
Fill="False"
Width="48"
Height="48"
Margin="0 5 0 0"
@@ -22,13 +23,23 @@
<TextBlock Grid.Column="1" Grid.Row="0" Classes="h5 no-margin" Text="{CompiledBinding Plugin.Info.Name}" />
<ItemsControl Grid.Column="2" Grid.Row="0" IsVisible="{CompiledBinding Platforms.Count}" Items="{CompiledBinding Platforms}" HorizontalAlignment="Right">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Spacing="5" Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<TextBlock Grid.Column="1"
Grid.ColumnSpan="2"
Grid.Row="1"
Classes="subtitle"
Text="{CompiledBinding Plugin.Info.Author}"
IsVisible="{CompiledBinding Plugin.Info.Author, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
<TextBlock Grid.Column="1"
Grid.ColumnSpan="2"
Grid.Row="2"
TextWrapping="Wrap"
Margin="0 5"
@@ -88,13 +99,15 @@
</controls:HyperlinkButton>
</StackPanel>
<CheckBox Grid.Row="0"
<CheckBox Name="EnabledToggle"
Grid.Row="0"
Grid.Column="1"
HorizontalAlignment="Right"
IsVisible="{CompiledBinding !Enabling}"
IsChecked="{CompiledBinding IsEnabled}">
IsChecked="{CompiledBinding IsEnabled, Mode=OneWay}"
IsEnabled="{CompiledBinding Plugin.Info.IsCompatible}">
<StackPanel x:Name="EnableText" Orientation="Horizontal">
<TextBlock>Plugin enabled</TextBlock>
<TextBlock>Enable plugin</TextBlock>
<avalonia:MaterialIcon Kind="ShieldHalfFull"
Margin="5 0 0 0"
ToolTip.Tip="Plugin requires admin rights"
@@ -1,18 +1,30 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using Avalonia.Threading;
namespace Artemis.UI.Screens.Plugins
{
public partial class PluginSettingsView : ReactiveUserControl<PluginSettingsViewModel>
{
private readonly CheckBox _enabledToggle;
public PluginSettingsView()
{
InitializeComponent();
_enabledToggle = this.Find<CheckBox>("EnabledToggle");
_enabledToggle.Click += EnabledToggleOnClick;
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void EnabledToggleOnClick(object? sender, RoutedEventArgs e)
{
Dispatcher.UIThread.Post(() => ViewModel?.UpdateEnabled(!ViewModel.Plugin.IsEnabled));
}
}
}
}
@@ -12,326 +12,347 @@ using Artemis.UI.Exceptions;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders;
using Avalonia.Controls;
using Avalonia.Threading;
using Material.Icons;
using Material.Icons.Avalonia;
using Ninject;
using ReactiveUI;
namespace Artemis.UI.Screens.Plugins
namespace Artemis.UI.Screens.Plugins;
public class PluginSettingsViewModel : ActivatableViewModelBase
{
public class PluginSettingsViewModel : ActivatableViewModelBase
private readonly ICoreService _coreService;
private readonly INotificationService _notificationService;
private readonly IPluginManagementService _pluginManagementService;
private readonly ISettingsVmFactory _settingsVmFactory;
private readonly IWindowService _windowService;
private bool _canInstallPrerequisites;
private bool _canRemovePrerequisites;
private bool _enabling;
private bool _isSettingsPopupOpen;
private Plugin _plugin;
private Window? _window;
public PluginSettingsViewModel(Plugin plugin,
ISettingsVmFactory settingsVmFactory,
ICoreService coreService,
IWindowService windowService,
INotificationService notificationService,
IPluginManagementService pluginManagementService)
{
private readonly ICoreService _coreService;
private readonly INotificationService _notificationService;
private readonly IPluginManagementService _pluginManagementService;
private readonly ISettingsVmFactory _settingsVmFactory;
private readonly IWindowService _windowService;
private bool _canInstallPrerequisites;
private bool _canRemovePrerequisites;
private bool _enabling;
private bool _isSettingsPopupOpen;
private Plugin _plugin;
_plugin = plugin;
public PluginSettingsViewModel(Plugin plugin,
ISettingsVmFactory settingsVmFactory,
ICoreService coreService,
IWindowService windowService,
INotificationService notificationService,
IPluginManagementService pluginManagementService)
_settingsVmFactory = settingsVmFactory;
_coreService = coreService;
_windowService = windowService;
_notificationService = notificationService;
_pluginManagementService = pluginManagementService;
PluginFeatures = new ObservableCollection<PluginFeatureViewModel>();
foreach (PluginFeatureInfo pluginFeatureInfo in Plugin.Features)
PluginFeatures.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeatureInfo, false));
Platforms = new ObservableCollection<PluginPlatformViewModel>();
if (Plugin.Info.Platforms != null)
{
_plugin = plugin;
if (Plugin.Info.Platforms.Value.HasFlag(PluginPlatform.Windows))
Platforms.Add(new PluginPlatformViewModel("Windows", MaterialIconKind.MicrosoftWindows));
if (Plugin.Info.Platforms.Value.HasFlag(PluginPlatform.Linux))
Platforms.Add(new PluginPlatformViewModel("Linux", MaterialIconKind.Linux));
if (Plugin.Info.Platforms.Value.HasFlag(PluginPlatform.OSX))
Platforms.Add(new PluginPlatformViewModel("OSX", MaterialIconKind.Apple));
}
_settingsVmFactory = settingsVmFactory;
_coreService = coreService;
_windowService = windowService;
_notificationService = notificationService;
_pluginManagementService = pluginManagementService;
Reload = ReactiveCommand.CreateFromTask(ExecuteReload);
OpenSettings = ReactiveCommand.Create(ExecuteOpenSettings, this.WhenAnyValue(vm => vm.IsEnabled, e => e && Plugin.ConfigurationDialog != null));
RemoveSettings = ReactiveCommand.CreateFromTask(ExecuteRemoveSettings);
Remove = ReactiveCommand.CreateFromTask(ExecuteRemove);
InstallPrerequisites = ReactiveCommand.CreateFromTask(ExecuteInstallPrerequisites, this.WhenAnyValue(x => x.CanInstallPrerequisites));
RemovePrerequisites = ReactiveCommand.CreateFromTask<bool>(ExecuteRemovePrerequisites, this.WhenAnyValue(x => x.CanRemovePrerequisites));
ShowLogsFolder = ReactiveCommand.Create(ExecuteShowLogsFolder);
OpenPluginDirectory = ReactiveCommand.Create(ExecuteOpenPluginDirectory);
PluginFeatures = new ObservableCollection<PluginFeatureViewModel>();
foreach (PluginFeatureInfo pluginFeatureInfo in Plugin.Features)
PluginFeatures.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeatureInfo, false));
Reload = ReactiveCommand.CreateFromTask(ExecuteReload);
OpenSettings = ReactiveCommand.Create(ExecuteOpenSettings, this.WhenAnyValue(x => x.IsEnabled).Select(isEnabled => isEnabled && Plugin.ConfigurationDialog != null));
RemoveSettings = ReactiveCommand.CreateFromTask(ExecuteRemoveSettings);
Remove = ReactiveCommand.CreateFromTask(ExecuteRemove);
InstallPrerequisites = ReactiveCommand.CreateFromTask(ExecuteInstallPrerequisites, this.WhenAnyValue(x => x.CanInstallPrerequisites));
RemovePrerequisites = ReactiveCommand.CreateFromTask<bool>(ExecuteRemovePrerequisites, this.WhenAnyValue(x => x.CanRemovePrerequisites));
ShowLogsFolder = ReactiveCommand.Create(ExecuteShowLogsFolder);
OpenPluginDirectory = ReactiveCommand.Create(ExecuteOpenPluginDirectory);
this.WhenActivated(d =>
this.WhenActivated(d =>
{
Plugin.Enabled += OnPluginToggled;
Plugin.Disabled += OnPluginToggled;
Disposable.Create(() =>
{
_pluginManagementService.PluginDisabled += PluginManagementServiceOnPluginToggled;
_pluginManagementService.PluginEnabled += PluginManagementServiceOnPluginToggled;
Plugin.Enabled -= OnPluginToggled;
Plugin.Disabled -= OnPluginToggled;
_window?.Close();
}).DisposeWith(d);
});
}
Disposable.Create(() =>
{
_pluginManagementService.PluginDisabled -= PluginManagementServiceOnPluginToggled;
_pluginManagementService.PluginEnabled -= PluginManagementServiceOnPluginToggled;
}).DisposeWith(d);
});
}
public ReactiveCommand<Unit, Unit> Reload { get; }
public ReactiveCommand<Unit, Unit> OpenSettings { get; }
public ReactiveCommand<Unit, Unit> RemoveSettings { get; }
public ReactiveCommand<Unit, Unit> Remove { get; }
public ReactiveCommand<Unit, Unit> InstallPrerequisites { get; }
public ReactiveCommand<bool, Unit> RemovePrerequisites { get; }
public ReactiveCommand<Unit, Unit> ShowLogsFolder { get; }
public ReactiveCommand<Unit, Unit> OpenPluginDirectory { get; }
public ReactiveCommand<Unit,Unit> Reload { get; }
public ReactiveCommand<Unit, Unit> OpenSettings { get; }
public ReactiveCommand<Unit,Unit> RemoveSettings { get; }
public ReactiveCommand<Unit,Unit> Remove { get;}
public ReactiveCommand<Unit, Unit> InstallPrerequisites { get; }
public ReactiveCommand<bool, Unit> RemovePrerequisites { get; }
public ReactiveCommand<Unit,Unit> ShowLogsFolder { get; }
public ReactiveCommand<Unit,Unit> OpenPluginDirectory { get; }
public ObservableCollection<PluginFeatureViewModel> PluginFeatures { get; }
public ObservableCollection<PluginFeatureViewModel> PluginFeatures { get; }
public ObservableCollection<PluginPlatformViewModel> Platforms { get; }
public Plugin Plugin
public Plugin Plugin
{
get => _plugin;
set => RaiseAndSetIfChanged(ref _plugin, value);
}
public bool Enabling
{
get => _enabling;
set => RaiseAndSetIfChanged(ref _enabling, value);
}
public string Type => Plugin.GetType().BaseType?.Name ?? Plugin.GetType().Name;
public bool IsEnabled => Plugin.IsEnabled;
public bool IsSettingsPopupOpen
{
get => _isSettingsPopupOpen;
set
{
get => _plugin;
set => RaiseAndSetIfChanged(ref _plugin, value);
if (!RaiseAndSetIfChanged(ref _isSettingsPopupOpen, value)) return;
CheckPrerequisites();
}
}
public bool Enabling
public bool CanInstallPrerequisites
{
get => _canInstallPrerequisites;
set => RaiseAndSetIfChanged(ref _canInstallPrerequisites, value);
}
public bool CanRemovePrerequisites
{
get => _canRemovePrerequisites;
set => RaiseAndSetIfChanged(ref _canRemovePrerequisites, value);
}
private void ExecuteOpenSettings()
{
if (Plugin.ConfigurationDialog == null)
return;
if (_window != null)
{
get => _enabling;
set => RaiseAndSetIfChanged(ref _enabling, value);
_window.WindowState = WindowState.Normal;
_window.Activate();
return;
}
public string Type => Plugin.GetType().BaseType?.Name ?? Plugin.GetType().Name;
public bool IsEnabled
try
{
get => Plugin.IsEnabled;
set => Task.Run(() => UpdateEnabled(value));
}
PluginConfigurationViewModel? viewModel = Plugin.Kernel!.Get(Plugin.ConfigurationDialog.Type) as PluginConfigurationViewModel;
if (viewModel == null)
throw new ArtemisUIException($"The type of a plugin configuration dialog must inherit {nameof(PluginConfigurationViewModel)}");
public bool IsSettingsPopupOpen
_window = _windowService.ShowWindow(new PluginSettingsWindowViewModel(viewModel));
_window.Closed += (_, _) => _window = null;
}
catch (Exception e)
{
get => _isSettingsPopupOpen;
set
{
if (!RaiseAndSetIfChanged(ref _isSettingsPopupOpen, value)) return;
CheckPrerequisites();
}
_windowService.ShowExceptionDialog("An exception occured while trying to show the plugin's settings window", e);
throw;
}
}
public bool CanInstallPrerequisites
private void ExecuteOpenPluginDirectory()
{
try
{
get => _canInstallPrerequisites;
set => RaiseAndSetIfChanged(ref _canInstallPrerequisites, value);
Utilities.OpenFolder(Plugin.Directory.FullName);
}
public bool CanRemovePrerequisites
catch (Exception e)
{
get => _canRemovePrerequisites;
set => RaiseAndSetIfChanged(ref _canRemovePrerequisites, value);
_windowService.ShowExceptionDialog("Welp, we couldn't open the device's plugin folder for you", e);
}
}
private void ExecuteOpenSettings()
private async Task ExecuteReload()
{
bool wasEnabled = IsEnabled;
await Task.Run(() => _pluginManagementService.UnloadPlugin(Plugin));
PluginFeatures.Clear();
Plugin = _pluginManagementService.LoadPlugin(Plugin.Directory);
foreach (PluginFeatureInfo pluginFeatureInfo in Plugin.Features)
PluginFeatures.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeatureInfo, false));
if (wasEnabled)
await UpdateEnabled(true);
_notificationService.CreateNotification().WithTitle("Reloaded plugin.").Show();
}
private async Task ExecuteInstallPrerequisites()
{
List<IPrerequisitesSubject> subjects = new() {Plugin.Info};
subjects.AddRange(Plugin.Features.Where(f => f.AlwaysEnabled));
if (subjects.Any(s => s.PlatformPrerequisites.Any()))
await PluginPrerequisitesInstallDialogViewModel.Show(_windowService, subjects);
}
private async Task ExecuteRemovePrerequisites(bool forPluginRemoval = false)
{
List<IPrerequisitesSubject> subjects = new() {Plugin.Info};
subjects.AddRange(!forPluginRemoval ? Plugin.Features.Where(f => f.AlwaysEnabled) : Plugin.Features);
if (subjects.Any(s => s.PlatformPrerequisites.Any(p => p.UninstallActions.Any())))
{
if (Plugin.ConfigurationDialog == null)
return;
await PluginPrerequisitesUninstallDialogViewModel.Show(_windowService, subjects, forPluginRemoval ? "Skip, remove plugin" : "Cancel");
}
}
try
{
PluginConfigurationViewModel? viewModel = Plugin.Kernel!.Get(Plugin.ConfigurationDialog.Type) as PluginConfigurationViewModel;
if (viewModel == null)
throw new ArtemisUIException($"The type of a plugin configuration dialog must inherit {nameof(PluginConfigurationViewModel)}");
private async Task ExecuteRemoveSettings()
{
bool confirmed = await _windowService.ShowConfirmContentDialog("Clear plugin settings", "Are you sure you want to clear the settings of this plugin?");
if (!confirmed)
return;
_windowService.ShowWindow(new PluginSettingsWindowViewModel(viewModel));
}
catch (Exception e)
{
_windowService.ShowExceptionDialog("An exception occured while trying to show the plugin's settings window", e);
throw;
}
bool wasEnabled = IsEnabled;
if (IsEnabled)
await UpdateEnabled(false);
_pluginManagementService.RemovePluginSettings(Plugin);
if (wasEnabled)
await UpdateEnabled(true);
_notificationService.CreateNotification().WithTitle("Cleared plugin settings.").Show();
}
private async Task ExecuteRemove()
{
bool confirmed = await _windowService.ShowConfirmContentDialog("Remove plugin", "Are you sure you want to remove this plugin?");
if (!confirmed)
return;
// If the plugin or any of its features has uninstall actions, offer to run these
List<IPrerequisitesSubject> subjects = new() {Plugin.Info};
subjects.AddRange(Plugin.Features);
if (subjects.Any(s => s.PlatformPrerequisites.Any(p => p.UninstallActions.Any())))
await ExecuteRemovePrerequisites(true);
try
{
_pluginManagementService.RemovePlugin(Plugin, false);
}
catch (Exception e)
{
_windowService.ShowExceptionDialog("Failed to remove plugin", e);
throw;
}
private void ExecuteOpenPluginDirectory()
_notificationService.CreateNotification().WithTitle("Removed plugin.").Show();
}
private void ExecuteShowLogsFolder()
{
try
{
Utilities.OpenFolder(Constants.LogsFolder);
}
catch (Exception e)
{
_windowService.ShowExceptionDialog("Welp, we couldn\'t open the logs folder for you", e);
}
}
public async Task UpdateEnabled(bool enable)
{
if (Enabling)
return;
if (!enable)
{
try
{
Utilities.OpenFolder(Plugin.Directory.FullName);
await Task.Run(() => _pluginManagementService.DisablePlugin(Plugin, true));
}
catch (Exception e)
{
_windowService.ShowExceptionDialog("Welp, we couldn't open the device's plugin folder for you", e);
await Dispatcher.UIThread.InvokeAsync(() => _notificationService.CreateNotification()
.WithSeverity(NotificationSeverity.Error)
.WithMessage($"Failed to disable plugin {Plugin.Info.Name}\r\n{e.Message}")
.HavingButton(b => b.WithText("View logs").WithCommand(ShowLogsFolder))
.Show());
}
finally
{
this.RaisePropertyChanged(nameof(IsEnabled));
}
return;
}
private async Task ExecuteReload()
try
{
bool wasEnabled = IsEnabled;
Enabling = true;
if (Plugin.Info.RequiresAdmin && !_coreService.IsElevated)
{
bool confirmed = await _windowService.ShowConfirmContentDialog("Enable plugin", "This plugin requires admin rights, are you sure you want to enable it?");
if (!confirmed)
return;
}
await Task.Run(() => _pluginManagementService.UnloadPlugin(Plugin));
PluginFeatures.Clear();
Plugin = _pluginManagementService.LoadPlugin(Plugin.Directory);
foreach (PluginFeatureInfo pluginFeatureInfo in Plugin.Features)
PluginFeatures.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeatureInfo, false));
if (wasEnabled)
await UpdateEnabled(true);
_notificationService.CreateNotification().WithTitle("Reloaded plugin.").Show();
}
private async Task ExecuteInstallPrerequisites()
{
// Check if all prerequisites are met async
List<IPrerequisitesSubject> subjects = new() {Plugin.Info};
subjects.AddRange(Plugin.Features.Where(f => f.AlwaysEnabled));
subjects.AddRange(Plugin.Features.Where(f => f.AlwaysEnabled || f.EnabledInStorage));
if (subjects.Any(s => s.PlatformPrerequisites.Any()))
if (subjects.Any(s => !s.ArePrerequisitesMet()))
{
await PluginPrerequisitesInstallDialogViewModel.Show(_windowService, subjects);
}
if (!subjects.All(s => s.ArePrerequisitesMet()))
return;
}
private async Task ExecuteRemovePrerequisites(bool forPluginRemoval = false)
await Task.Run(() => _pluginManagementService.EnablePlugin(Plugin, true, true));
}
catch (Exception e)
{
List<IPrerequisitesSubject> subjects = new() {Plugin.Info};
subjects.AddRange(!forPluginRemoval ? Plugin.Features.Where(f => f.AlwaysEnabled) : Plugin.Features);
if (subjects.Any(s => s.PlatformPrerequisites.Any(p => p.UninstallActions.Any())))
{
await PluginPrerequisitesUninstallDialogViewModel.Show(_windowService, subjects, forPluginRemoval ? "Skip, remove plugin" : "Cancel");
this.RaisePropertyChanged(nameof(IsEnabled));
}
await Dispatcher.UIThread.InvokeAsync(() => _notificationService.CreateNotification()
.WithSeverity(NotificationSeverity.Error)
.WithMessage($"Failed to enable plugin {Plugin.Info.Name}\r\n{e.Message}")
.HavingButton(b => b.WithText("View logs").WithCommand(ShowLogsFolder))
.Show());
}
private async Task ExecuteRemoveSettings()
{
bool confirmed = await _windowService.ShowConfirmContentDialog("Clear plugin settings", "Are you sure you want to clear the settings of this plugin?");
if (!confirmed)
return;
bool wasEnabled = IsEnabled;
if (IsEnabled)
await UpdateEnabled(false);
_pluginManagementService.RemovePluginSettings(Plugin);
if (wasEnabled)
await UpdateEnabled(true);
_notificationService.CreateNotification().WithTitle("Cleared plugin settings.").Show();
}
private async Task ExecuteRemove()
{
bool confirmed = await _windowService.ShowConfirmContentDialog("Remove plugin", "Are you sure you want to remove this plugin?");
if (!confirmed)
return;
// If the plugin or any of its features has uninstall actions, offer to run these
List<IPrerequisitesSubject> subjects = new() {Plugin.Info};
subjects.AddRange(Plugin.Features);
if (subjects.Any(s => s.PlatformPrerequisites.Any(p => p.UninstallActions.Any())))
await ExecuteRemovePrerequisites(true);
try
{
_pluginManagementService.RemovePlugin(Plugin, false);
}
catch (Exception e)
{
_windowService.ShowExceptionDialog("Failed to remove plugin", e);
throw;
}
_notificationService.CreateNotification().WithTitle("Removed plugin.").Show();
}
private void ExecuteShowLogsFolder()
{
try
{
Utilities.OpenFolder(Constants.LogsFolder);
}
catch (Exception e)
{
_windowService.ShowExceptionDialog("Welp, we couldn\'t open the logs folder for you", e);
}
}
private void PluginManagementServiceOnPluginToggled(object? sender, PluginEventArgs e)
{
this.RaisePropertyChanged(nameof(IsEnabled));
}
private async Task UpdateEnabled(bool enable)
{
if (IsEnabled == enable)
{
this.RaisePropertyChanged(nameof(IsEnabled));
return;
}
if (enable)
{
Enabling = true;
if (Plugin.Info.RequiresAdmin && !_coreService.IsElevated)
{
bool confirmed = await _windowService.ShowConfirmContentDialog("Enable plugin", "This plugin requires admin rights, are you sure you want to enable it?");
if (!confirmed)
{
CancelEnable();
return;
}
}
// Check if all prerequisites are met async
List<IPrerequisitesSubject> subjects = new() {Plugin.Info};
subjects.AddRange(Plugin.Features.Where(f => f.AlwaysEnabled || f.EnabledInStorage));
if (subjects.Any(s => !s.ArePrerequisitesMet()))
{
await PluginPrerequisitesInstallDialogViewModel.Show(_windowService, subjects);
if (!subjects.All(s => s.ArePrerequisitesMet()))
{
CancelEnable();
return;
}
}
await Task.Run(async () =>
{
try
{
_pluginManagementService.EnablePlugin(Plugin, true, true);
}
catch (Exception e)
{
await Dispatcher.UIThread.InvokeAsync(() => _notificationService.CreateNotification()
.WithMessage($"Failed to enable plugin {Plugin.Info.Name}\r\n{e.Message}")
.HavingButton(b => b.WithText("View logs").WithCommand(ShowLogsFolder))
.Show());
}
finally
{
Enabling = false;
}
});
}
else
{
_pluginManagementService.DisablePlugin(Plugin, true);
}
this.RaisePropertyChanged(nameof(IsEnabled));
}
private void CancelEnable()
finally
{
Enabling = false;
this.RaisePropertyChanged(nameof(IsEnabled));
}
}
private void CheckPrerequisites()
private void CheckPrerequisites()
{
CanInstallPrerequisites = Plugin.Info.PlatformPrerequisites.Any() ||
Plugin.Features.Where(f => f.AlwaysEnabled).Any(f => f.PlatformPrerequisites.Any());
CanRemovePrerequisites = Plugin.Info.PlatformPrerequisites.Any(p => p.UninstallActions.Any()) ||
Plugin.Features.Where(f => f.AlwaysEnabled).Any(f => f.PlatformPrerequisites.Any(p => p.UninstallActions.Any()));
}
private void OnPluginToggled(object? sender, EventArgs e)
{
Dispatcher.UIThread.Post(() =>
{
CanInstallPrerequisites = Plugin.Info.PlatformPrerequisites.Any() ||
Plugin.Features.Where(f => f.AlwaysEnabled).Any(f => f.PlatformPrerequisites.Any());
CanRemovePrerequisites = Plugin.Info.PlatformPrerequisites.Any(p => p.UninstallActions.Any()) ||
Plugin.Features.Where(f => f.AlwaysEnabled).Any(f => f.PlatformPrerequisites.Any(p => p.UninstallActions.Any()));
}
this.RaisePropertyChanged(nameof(IsEnabled));
if (!IsEnabled)
_window?.Close();
});
}
}
@@ -1,6 +1,7 @@
using System;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Artemis.UI.Shared;
using Avalonia;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;