mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Streamlines plugin management and installation
Refactors plugin handling for improved reliability and user experience. - Removes built-in plugin copying, migrating to workshop-based installation - Introduces a dedicated service for plugin interactions (enabling, disabling, removal, settings) - Simplifies plugin enabling/disabling logic in the UI, centralizing the logic
This commit is contained in:
parent
7f5b677cc3
commit
bda247d3c3
@ -63,7 +63,6 @@ internal class CoreService : ICoreService
|
|||||||
_logger.Debug("Forcing plugins to use HidSharp {HidSharpVersion}", hidSharpVersion);
|
_logger.Debug("Forcing plugins to use HidSharp {HidSharpVersion}", hidSharpVersion);
|
||||||
|
|
||||||
// Initialize the services
|
// Initialize the services
|
||||||
_pluginManagementService.CopyBuiltInPlugins();
|
|
||||||
_pluginManagementService.LoadPlugins(IsElevated);
|
_pluginManagementService.LoadPlugins(IsElevated);
|
||||||
_pluginManagementService.StartHotReload();
|
_pluginManagementService.StartHotReload();
|
||||||
_renderService.Initialize();
|
_renderService.Initialize();
|
||||||
|
|||||||
@ -23,10 +23,9 @@ public interface IPluginManagementService : IArtemisService, IDisposable
|
|||||||
bool LoadingPlugins { get; }
|
bool LoadingPlugins { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Copy built-in plugins from the executable directory to the plugins directory if the version is higher
|
/// Indicates whether or not plugins are currently loaded
|
||||||
/// (higher or equal if compiled as debug)
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void CopyBuiltInPlugins();
|
bool LoadedPlugins { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Loads all installed plugins. If plugins already loaded this will reload them all
|
/// Loads all installed plugins. If plugins already loaded this will reload them all
|
||||||
@ -151,11 +150,6 @@ public interface IPluginManagementService : IArtemisService, IDisposable
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
DeviceProvider GetDeviceProviderByDevice(IRGBDevice device);
|
DeviceProvider GetDeviceProviderByDevice(IRGBDevice device);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when built-in plugins are being loaded
|
|
||||||
/// </summary>
|
|
||||||
event EventHandler CopyingBuildInPlugins;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Occurs when a plugin has started loading
|
/// Occurs when a plugin has started loading
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -47,113 +47,7 @@ internal class PluginManagementService : IPluginManagementService
|
|||||||
|
|
||||||
public bool LoadingPlugins { get; private set; }
|
public bool LoadingPlugins { get; private set; }
|
||||||
|
|
||||||
|
public bool LoadedPlugins { get; private set; }
|
||||||
#region Built in plugins
|
|
||||||
|
|
||||||
public void CopyBuiltInPlugins()
|
|
||||||
{
|
|
||||||
OnCopyingBuildInPlugins();
|
|
||||||
DirectoryInfo pluginDirectory = new(Constants.PluginsFolder);
|
|
||||||
|
|
||||||
if (Directory.Exists(Path.Combine(pluginDirectory.FullName, "Artemis.Plugins.Modules.Overlay-29e3ff97")))
|
|
||||||
Directory.Delete(Path.Combine(pluginDirectory.FullName, "Artemis.Plugins.Modules.Overlay-29e3ff97"), true);
|
|
||||||
if (Directory.Exists(Path.Combine(pluginDirectory.FullName, "Artemis.Plugins.DataModelExpansions.TestData-ab41d601")))
|
|
||||||
Directory.Delete(Path.Combine(pluginDirectory.FullName, "Artemis.Plugins.DataModelExpansions.TestData-ab41d601"), true);
|
|
||||||
|
|
||||||
// Iterate built-in plugins
|
|
||||||
DirectoryInfo builtInPluginDirectory = new(Path.Combine(Constants.ApplicationFolder, "Plugins"));
|
|
||||||
if (!builtInPluginDirectory.Exists)
|
|
||||||
{
|
|
||||||
_logger.Warning("No built-in plugins found at {pluginDir}, skipping CopyBuiltInPlugins", builtInPluginDirectory.FullName);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
foreach (FileInfo zipFile in builtInPluginDirectory.EnumerateFiles("*.zip"))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ExtractBuiltInPlugin(zipFile, pluginDirectory);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_logger.Error(e, "Failed to copy built-in plugin from {ZipFile}", zipFile.FullName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ExtractBuiltInPlugin(FileInfo zipFile, DirectoryInfo pluginDirectory)
|
|
||||||
{
|
|
||||||
// Find the metadata file in the zip
|
|
||||||
using ZipArchive archive = ZipFile.OpenRead(zipFile.FullName);
|
|
||||||
|
|
||||||
ZipArchiveEntry? metaDataFileEntry = archive.Entries.FirstOrDefault(e => e.Name == "plugin.json");
|
|
||||||
if (metaDataFileEntry == null)
|
|
||||||
throw new ArtemisPluginException("Couldn't find a plugin.json in " + zipFile.FullName);
|
|
||||||
|
|
||||||
using StreamReader reader = new(metaDataFileEntry.Open());
|
|
||||||
PluginInfo builtInPluginInfo = CoreJson.Deserialize<PluginInfo>(reader.ReadToEnd())!;
|
|
||||||
string preferred = builtInPluginInfo.PreferredPluginDirectory;
|
|
||||||
|
|
||||||
// Find the matching plugin in the plugin folder
|
|
||||||
DirectoryInfo? match = pluginDirectory.EnumerateDirectories().FirstOrDefault(d => d.Name == preferred);
|
|
||||||
if (match == null)
|
|
||||||
{
|
|
||||||
CopyBuiltInPlugin(archive, preferred);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
string metadataFile = Path.Combine(match.FullName, "plugin.json");
|
|
||||||
if (!File.Exists(metadataFile))
|
|
||||||
{
|
|
||||||
_logger.Debug("Copying missing built-in plugin {builtInPluginInfo}", builtInPluginInfo);
|
|
||||||
CopyBuiltInPlugin(archive, preferred);
|
|
||||||
}
|
|
||||||
else if (metaDataFileEntry.LastWriteTime > File.GetLastWriteTime(metadataFile))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_logger.Debug("Copying updated built-in plugin {builtInPluginInfo}", builtInPluginInfo);
|
|
||||||
CopyBuiltInPlugin(archive, preferred);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
throw new ArtemisPluginException($"Failed to install built-in plugin: {e.Message}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CopyBuiltInPlugin(ZipArchive zipArchive, string targetDirectory)
|
|
||||||
{
|
|
||||||
ZipArchiveEntry metaDataFileEntry = zipArchive.Entries.First(e => e.Name == "plugin.json");
|
|
||||||
DirectoryInfo pluginDirectory = new(Path.Combine(Constants.PluginsFolder, targetDirectory));
|
|
||||||
bool createLockFile = File.Exists(Path.Combine(pluginDirectory.FullName, "artemis.lock"));
|
|
||||||
|
|
||||||
// Remove the old directory if it exists
|
|
||||||
if (Directory.Exists(pluginDirectory.FullName))
|
|
||||||
pluginDirectory.Delete(true);
|
|
||||||
|
|
||||||
// Extract everything in the same archive directory to the unique plugin directory
|
|
||||||
Utilities.CreateAccessibleDirectory(pluginDirectory.FullName);
|
|
||||||
string metaDataDirectory = metaDataFileEntry.FullName.Replace(metaDataFileEntry.Name, "");
|
|
||||||
foreach (ZipArchiveEntry zipArchiveEntry in zipArchive.Entries)
|
|
||||||
{
|
|
||||||
if (zipArchiveEntry.FullName.StartsWith(metaDataDirectory) && !zipArchiveEntry.FullName.EndsWith("/"))
|
|
||||||
{
|
|
||||||
string target = Path.Combine(pluginDirectory.FullName, zipArchiveEntry.FullName.Remove(0, metaDataDirectory.Length));
|
|
||||||
// Create folders
|
|
||||||
Utilities.CreateAccessibleDirectory(Path.GetDirectoryName(target)!);
|
|
||||||
// Extract files
|
|
||||||
zipArchiveEntry.ExtractToFile(target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (createLockFile)
|
|
||||||
File.Create(Path.Combine(pluginDirectory.FullName, "artemis.lock")).Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
public List<Plugin> GetAllPlugins()
|
public List<Plugin> GetAllPlugins()
|
||||||
{
|
{
|
||||||
@ -328,8 +222,10 @@ internal class PluginManagementService : IPluginManagementService
|
|||||||
// ReSharper restore InconsistentlySynchronizedField
|
// ReSharper restore InconsistentlySynchronizedField
|
||||||
|
|
||||||
LoadingPlugins = false;
|
LoadingPlugins = false;
|
||||||
|
LoadedPlugins = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void UnloadPlugins()
|
public void UnloadPlugins()
|
||||||
{
|
{
|
||||||
// Unload all plugins
|
// Unload all plugins
|
||||||
|
|||||||
@ -6,11 +6,11 @@ using System.Reactive;
|
|||||||
using System.Reactive.Disposables;
|
using System.Reactive.Disposables;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Core.Services;
|
|
||||||
using Artemis.UI.Exceptions;
|
using Artemis.UI.Exceptions;
|
||||||
|
using Artemis.UI.Services;
|
||||||
|
using Artemis.UI.Services.Interfaces;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services;
|
||||||
using Artemis.UI.Shared.Services.Builders;
|
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using Material.Icons;
|
using Material.Icons;
|
||||||
@ -21,9 +21,7 @@ namespace Artemis.UI.Screens.Plugins;
|
|||||||
|
|
||||||
public partial class PluginViewModel : ActivatableViewModelBase
|
public partial class PluginViewModel : ActivatableViewModelBase
|
||||||
{
|
{
|
||||||
private readonly ICoreService _coreService;
|
private readonly IPluginInteractionService _pluginInteractionService;
|
||||||
private readonly INotificationService _notificationService;
|
|
||||||
private readonly IPluginManagementService _pluginManagementService;
|
|
||||||
private readonly IWindowService _windowService;
|
private readonly IWindowService _windowService;
|
||||||
private Window? _settingsWindow;
|
private Window? _settingsWindow;
|
||||||
[Notify] private bool _canInstallPrerequisites;
|
[Notify] private bool _canInstallPrerequisites;
|
||||||
@ -31,18 +29,11 @@ public partial class PluginViewModel : ActivatableViewModelBase
|
|||||||
[Notify] private bool _enabling;
|
[Notify] private bool _enabling;
|
||||||
[Notify] private Plugin _plugin;
|
[Notify] private Plugin _plugin;
|
||||||
|
|
||||||
public PluginViewModel(Plugin plugin,
|
public PluginViewModel(Plugin plugin, ReactiveCommand<Unit, Unit>? reload, IWindowService windowService, IPluginInteractionService pluginInteractionService)
|
||||||
ReactiveCommand<Unit, Unit>? reload,
|
|
||||||
ICoreService coreService,
|
|
||||||
IWindowService windowService,
|
|
||||||
INotificationService notificationService,
|
|
||||||
IPluginManagementService pluginManagementService)
|
|
||||||
{
|
{
|
||||||
_plugin = plugin;
|
_plugin = plugin;
|
||||||
_coreService = coreService;
|
|
||||||
_windowService = windowService;
|
_windowService = windowService;
|
||||||
_notificationService = notificationService;
|
_pluginInteractionService = pluginInteractionService;
|
||||||
_pluginManagementService = pluginManagementService;
|
|
||||||
|
|
||||||
Platforms = new ObservableCollection<PluginPlatformViewModel>();
|
Platforms = new ObservableCollection<PluginPlatformViewModel>();
|
||||||
if (Plugin.Info.Platforms != null)
|
if (Plugin.Info.Platforms != null)
|
||||||
@ -88,7 +79,6 @@ public partial class PluginViewModel : ActivatableViewModelBase
|
|||||||
public ReactiveCommand<Unit, Unit> OpenPluginDirectory { get; }
|
public ReactiveCommand<Unit, Unit> OpenPluginDirectory { get; }
|
||||||
|
|
||||||
public ObservableCollection<PluginPlatformViewModel> Platforms { get; }
|
public ObservableCollection<PluginPlatformViewModel> Platforms { get; }
|
||||||
public string Type => Plugin.GetType().BaseType?.Name ?? Plugin.GetType().Name;
|
|
||||||
public bool IsEnabled => Plugin.IsEnabled;
|
public bool IsEnabled => Plugin.IsEnabled;
|
||||||
|
|
||||||
public async Task UpdateEnabled(bool enable)
|
public async Task UpdateEnabled(bool enable)
|
||||||
@ -97,55 +87,15 @@ public partial class PluginViewModel : ActivatableViewModelBase
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (!enable)
|
if (!enable)
|
||||||
{
|
await _pluginInteractionService.DisablePlugin(Plugin);
|
||||||
try
|
else
|
||||||
{
|
|
||||||
await Task.Run(() => _pluginManagementService.DisablePlugin(Plugin, true));
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
await ShowUpdateEnableFailure(enable, e);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
this.RaisePropertyChanged(nameof(IsEnabled));
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
Enabling = true;
|
Enabling = true;
|
||||||
if (Plugin.Info.RequiresAdmin && !_coreService.IsElevated)
|
await _pluginInteractionService.EnablePlugin(Plugin, false);
|
||||||
{
|
|
||||||
bool confirmed = await _windowService.ShowConfirmContentDialog("Enable plugin", "This plugin requires admin rights, are you sure you want to enable it? Artemis will need to restart.", "Confirm and restart");
|
|
||||||
if (!confirmed)
|
|
||||||
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()))
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await Task.Run(() => _pluginManagementService.EnablePlugin(Plugin, true, true));
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
await ShowUpdateEnableFailure(enable, e);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Enabling = false;
|
Enabling = false;
|
||||||
this.RaisePropertyChanged(nameof(IsEnabled));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.RaisePropertyChanged(nameof(IsEnabled));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CheckPrerequisites()
|
public void CheckPrerequisites()
|
||||||
@ -220,43 +170,12 @@ public partial class PluginViewModel : ActivatableViewModelBase
|
|||||||
|
|
||||||
private async Task ExecuteRemoveSettings()
|
private async Task ExecuteRemoveSettings()
|
||||||
{
|
{
|
||||||
bool confirmed = await _windowService.ShowConfirmContentDialog("Clear plugin settings", "Are you sure you want to clear the settings of this plugin?");
|
await _pluginInteractionService.RemovePluginSettings(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()
|
private async Task ExecuteRemove()
|
||||||
{
|
{
|
||||||
bool confirmed = await _windowService.ShowConfirmContentDialog("Remove plugin", "Are you sure you want to remove this plugin?");
|
await _pluginInteractionService.RemovePlugin(Plugin);
|
||||||
if (!confirmed)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// If the plugin or any of its features has uninstall actions, offer to run these
|
|
||||||
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()
|
private void ExecuteShowLogsFolder()
|
||||||
@ -271,20 +190,6 @@ public partial class PluginViewModel : ActivatableViewModelBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ShowUpdateEnableFailure(bool enable, Exception e)
|
|
||||||
{
|
|
||||||
string action = enable ? "enable" : "disable";
|
|
||||||
ContentDialogBuilder builder = _windowService.CreateContentDialog()
|
|
||||||
.WithTitle($"Failed to {action} plugin {Plugin.Info.Name}")
|
|
||||||
.WithContent(e.Message)
|
|
||||||
.HavingPrimaryButton(b => b.WithText("View logs").WithCommand(ShowLogsFolder));
|
|
||||||
// If available, add a secondary button pointing to the support page
|
|
||||||
if (Plugin.Info.HelpPage != null)
|
|
||||||
builder = builder.HavingSecondaryButton(b => b.WithText("Open support page").WithAction(() => Utilities.OpenUrl(Plugin.Info.HelpPage.ToString())));
|
|
||||||
|
|
||||||
await builder.ShowAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnPluginToggled(object? sender, EventArgs e)
|
private void OnPluginToggled(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.Post(() =>
|
Dispatcher.UIThread.Post(() =>
|
||||||
|
|||||||
@ -83,7 +83,7 @@ public class RootViewModel : RoutableHostScreen<RoutableScreen>, IMainWindowProv
|
|||||||
_coreService.Initialized += (_, _) => Dispatcher.UIThread.InvokeAsync(OpenMainWindow);
|
_coreService.Initialized += (_, _) => Dispatcher.UIThread.InvokeAsync(OpenMainWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
Task.Run(() =>
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -93,7 +93,7 @@ public class RootViewModel : RoutableHostScreen<RoutableScreen>, IMainWindowProv
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Workshop service goes first so it has a chance to clean up old workshop entries and introduce new ones
|
// Workshop service goes first so it has a chance to clean up old workshop entries and introduce new ones
|
||||||
workshopService.Initialize();
|
await workshopService.Initialize();
|
||||||
// Core is initialized now that everything is ready to go
|
// Core is initialized now that everything is ready to go
|
||||||
coreService.Initialize();
|
coreService.Initialize();
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
|
using Artemis.WebClient.Workshop.Services;
|
||||||
using PropertyChanged.SourceGenerator;
|
using PropertyChanged.SourceGenerator;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Root;
|
namespace Artemis.UI.Screens.Root;
|
||||||
@ -10,12 +11,12 @@ public partial class SplashViewModel : ViewModelBase
|
|||||||
{
|
{
|
||||||
[Notify] private string _status;
|
[Notify] private string _status;
|
||||||
|
|
||||||
public SplashViewModel(ICoreService coreService, IPluginManagementService pluginManagementService)
|
public SplashViewModel(ICoreService coreService, IPluginManagementService pluginManagementService, IWorkshopService workshopService)
|
||||||
{
|
{
|
||||||
CoreService = coreService;
|
CoreService = coreService;
|
||||||
_status = "Initializing Core";
|
_status = "Initializing Core";
|
||||||
|
|
||||||
pluginManagementService.CopyingBuildInPlugins += OnPluginManagementServiceOnCopyingBuildInPluginsManagement;
|
workshopService.MigratingBuildInPlugins += WorkshopServiceOnMigratingBuildInPlugins;
|
||||||
pluginManagementService.PluginLoading += OnPluginManagementServiceOnPluginManagementLoading;
|
pluginManagementService.PluginLoading += OnPluginManagementServiceOnPluginManagementLoading;
|
||||||
pluginManagementService.PluginLoaded += OnPluginManagementServiceOnPluginManagementLoaded;
|
pluginManagementService.PluginLoaded += OnPluginManagementServiceOnPluginManagementLoaded;
|
||||||
pluginManagementService.PluginEnabling += PluginManagementServiceOnPluginManagementEnabling;
|
pluginManagementService.PluginEnabling += PluginManagementServiceOnPluginManagementEnabling;
|
||||||
@ -26,6 +27,11 @@ public partial class SplashViewModel : ViewModelBase
|
|||||||
|
|
||||||
public ICoreService CoreService { get; }
|
public ICoreService CoreService { get; }
|
||||||
|
|
||||||
|
private void WorkshopServiceOnMigratingBuildInPlugins(object? sender, EventArgs args)
|
||||||
|
{
|
||||||
|
Status = "Migrating built-in plugins";
|
||||||
|
}
|
||||||
|
|
||||||
private void OnPluginManagementServiceOnPluginManagementLoaded(object? sender, PluginEventArgs args)
|
private void OnPluginManagementServiceOnPluginManagementLoaded(object? sender, PluginEventArgs args)
|
||||||
{
|
{
|
||||||
Status = "Initializing UI";
|
Status = "Initializing UI";
|
||||||
@ -55,9 +61,4 @@ public partial class SplashViewModel : ViewModelBase
|
|||||||
{
|
{
|
||||||
Status = "Initializing UI";
|
Status = "Initializing UI";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPluginManagementServiceOnCopyingBuildInPluginsManagement(object? sender, EventArgs args)
|
|
||||||
{
|
|
||||||
Status = "Updating built-in plugins";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -5,9 +5,10 @@ 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.DryIoc.Factories;
|
|
||||||
using Artemis.UI.Exceptions;
|
using Artemis.UI.Exceptions;
|
||||||
using Artemis.UI.Screens.Plugins;
|
using Artemis.UI.Screens.Plugins;
|
||||||
|
using Artemis.UI.Services;
|
||||||
|
using Artemis.UI.Services.Interfaces;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services;
|
||||||
using Artemis.UI.Shared.Utilities;
|
using Artemis.UI.Shared.Utilities;
|
||||||
@ -28,7 +29,7 @@ public partial class DefaultEntryItemViewModel : ActivatableViewModelBase
|
|||||||
private readonly IWindowService _windowService;
|
private readonly IWindowService _windowService;
|
||||||
private readonly IPluginManagementService _pluginManagementService;
|
private readonly IPluginManagementService _pluginManagementService;
|
||||||
private readonly IProfileService _profileService;
|
private readonly IProfileService _profileService;
|
||||||
private readonly ISettingsVmFactory _settingsVmFactory;
|
private readonly IPluginInteractionService _pluginInteractionService;
|
||||||
private readonly Progress<StreamProgress> _progress = new();
|
private readonly Progress<StreamProgress> _progress = new();
|
||||||
|
|
||||||
[Notify] private bool _isInstalled;
|
[Notify] private bool _isInstalled;
|
||||||
@ -41,14 +42,14 @@ public partial class DefaultEntryItemViewModel : ActivatableViewModelBase
|
|||||||
IWindowService windowService,
|
IWindowService windowService,
|
||||||
IPluginManagementService pluginManagementService,
|
IPluginManagementService pluginManagementService,
|
||||||
IProfileService profileService,
|
IProfileService profileService,
|
||||||
ISettingsVmFactory settingsVmFactory)
|
IPluginInteractionService pluginInteractionService)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_workshopService = workshopService;
|
_workshopService = workshopService;
|
||||||
_windowService = windowService;
|
_windowService = windowService;
|
||||||
_pluginManagementService = pluginManagementService;
|
_pluginManagementService = pluginManagementService;
|
||||||
_profileService = profileService;
|
_profileService = profileService;
|
||||||
_settingsVmFactory = settingsVmFactory;
|
_pluginInteractionService = pluginInteractionService;
|
||||||
Entry = entry;
|
Entry = entry;
|
||||||
|
|
||||||
_progress.ProgressChanged += (_, f) => InstallProgress = f.ProgressPercentage;
|
_progress.ProgressChanged += (_, f) => InstallProgress = f.ProgressPercentage;
|
||||||
@ -62,10 +63,7 @@ public partial class DefaultEntryItemViewModel : ActivatableViewModelBase
|
|||||||
if (IsInstalled || !ShouldInstall || Entry.LatestRelease == null)
|
if (IsInstalled || !ShouldInstall || Entry.LatestRelease == null)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// Most entries install so fast it looks broken without a small delay
|
|
||||||
Task minimumDelay = Task.Delay(100, cancellationToken);
|
|
||||||
EntryInstallResult result = await _workshopService.InstallEntry(Entry, Entry.LatestRelease, _progress, cancellationToken);
|
EntryInstallResult result = await _workshopService.InstallEntry(Entry, Entry.LatestRelease, _progress, cancellationToken);
|
||||||
await minimumDelay;
|
|
||||||
|
|
||||||
if (!result.IsSuccess)
|
if (!result.IsSuccess)
|
||||||
{
|
{
|
||||||
@ -95,8 +93,7 @@ public partial class DefaultEntryItemViewModel : ActivatableViewModelBase
|
|||||||
throw new InvalidOperationException($"Plugin with id '{pluginId}' does not exist.");
|
throw new InvalidOperationException($"Plugin with id '{pluginId}' does not exist.");
|
||||||
|
|
||||||
// There's quite a bit of UI involved in enabling a plugin, borrowing the PluginSettingsViewModel for this
|
// There's quite a bit of UI involved in enabling a plugin, borrowing the PluginSettingsViewModel for this
|
||||||
PluginViewModel pluginViewModel = _settingsVmFactory.PluginViewModel(plugin, ReactiveCommand.Create(() => { }));
|
await _pluginInteractionService.EnablePlugin(plugin, true);
|
||||||
await pluginViewModel.UpdateEnabled(true);
|
|
||||||
|
|
||||||
// Find features without prerequisites to enable
|
// Find features without prerequisites to enable
|
||||||
foreach (PluginFeatureInfo pluginFeatureInfo in plugin.Features)
|
foreach (PluginFeatureInfo pluginFeatureInfo in plugin.Features)
|
||||||
@ -113,15 +110,6 @@ public partial class DefaultEntryItemViewModel : ActivatableViewModelBase
|
|||||||
_logger.Warning(e, "Failed to enable plugin feature '{FeatureName}', skipping", pluginFeatureInfo.Name);
|
_logger.Warning(e, "Failed to enable plugin feature '{FeatureName}', skipping", pluginFeatureInfo.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the plugin has a mandatory settings window, open it and wait
|
|
||||||
if (plugin.ConfigurationDialog != null && plugin.ConfigurationDialog.IsMandatory)
|
|
||||||
{
|
|
||||||
if (plugin.Resolve(plugin.ConfigurationDialog.Type) is not PluginConfigurationViewModel viewModel)
|
|
||||||
throw new ArtemisUIException($"The type of a plugin configuration dialog must inherit {nameof(PluginConfigurationViewModel)}");
|
|
||||||
|
|
||||||
await _windowService.ShowDialogAsync(new PluginSettingsWindowViewModel(viewModel));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PrepareProfile(InstalledEntry entry)
|
private void PrepareProfile(InstalledEntry entry)
|
||||||
|
|||||||
@ -6,9 +6,8 @@ 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.DryIoc.Factories;
|
|
||||||
using Artemis.UI.Screens.Plugins;
|
|
||||||
using Artemis.UI.Screens.Workshop.EntryReleases.Dialogs;
|
using Artemis.UI.Screens.Workshop.EntryReleases.Dialogs;
|
||||||
|
using Artemis.UI.Services.Interfaces;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using Artemis.UI.Shared.Routing;
|
using Artemis.UI.Shared.Routing;
|
||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services;
|
||||||
@ -30,7 +29,7 @@ public partial class EntryReleaseInfoViewModel : ActivatableViewModelBase
|
|||||||
private readonly IWindowService _windowService;
|
private readonly IWindowService _windowService;
|
||||||
private readonly IWorkshopService _workshopService;
|
private readonly IWorkshopService _workshopService;
|
||||||
private readonly IPluginManagementService _pluginManagementService;
|
private readonly IPluginManagementService _pluginManagementService;
|
||||||
private readonly ISettingsVmFactory _settingsVmFactory;
|
private readonly IPluginInteractionService _pluginInteractionService;
|
||||||
private readonly Progress<StreamProgress> _progress = new();
|
private readonly Progress<StreamProgress> _progress = new();
|
||||||
|
|
||||||
[Notify] private IReleaseDetails? _release;
|
[Notify] private IReleaseDetails? _release;
|
||||||
@ -46,14 +45,14 @@ public partial class EntryReleaseInfoViewModel : ActivatableViewModelBase
|
|||||||
IWindowService windowService,
|
IWindowService windowService,
|
||||||
IWorkshopService workshopService,
|
IWorkshopService workshopService,
|
||||||
IPluginManagementService pluginManagementService,
|
IPluginManagementService pluginManagementService,
|
||||||
ISettingsVmFactory settingsVmFactory)
|
IPluginInteractionService pluginInteractionService)
|
||||||
{
|
{
|
||||||
_router = router;
|
_router = router;
|
||||||
_notificationService = notificationService;
|
_notificationService = notificationService;
|
||||||
_windowService = windowService;
|
_windowService = windowService;
|
||||||
_workshopService = workshopService;
|
_workshopService = workshopService;
|
||||||
_pluginManagementService = pluginManagementService;
|
_pluginManagementService = pluginManagementService;
|
||||||
_settingsVmFactory = settingsVmFactory;
|
_pluginInteractionService = pluginInteractionService;
|
||||||
_progress.ProgressChanged += (_, f) => InstallProgress = f.ProgressPercentage;
|
_progress.ProgressChanged += (_, f) => InstallProgress = f.ProgressPercentage;
|
||||||
|
|
||||||
this.WhenActivated(d =>
|
this.WhenActivated(d =>
|
||||||
@ -124,7 +123,10 @@ public partial class EntryReleaseInfoViewModel : ActivatableViewModelBase
|
|||||||
_workshopService.SetAutoUpdate(result.Entry, !disableAutoUpdates);
|
_workshopService.SetAutoUpdate(result.Entry, !disableAutoUpdates);
|
||||||
_notificationService.CreateNotification().WithTitle("Installation succeeded").WithSeverity(NotificationSeverity.Success).Show();
|
_notificationService.CreateNotification().WithTitle("Installation succeeded").WithSeverity(NotificationSeverity.Success).Show();
|
||||||
InstallationInProgress = false;
|
InstallationInProgress = false;
|
||||||
await Manage();
|
|
||||||
|
// Auto-enable plugins as the installation handler won't deal with the required UI interactions
|
||||||
|
if (result.Installed is Plugin installedPlugin)
|
||||||
|
await AutoEnablePlugin(installedPlugin);
|
||||||
}
|
}
|
||||||
else if (!_cts.IsCancellationRequested)
|
else if (!_cts.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
@ -141,12 +143,6 @@ public partial class EntryReleaseInfoViewModel : ActivatableViewModelBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Manage()
|
|
||||||
{
|
|
||||||
if (Release?.Entry.EntryType != EntryType.Profile)
|
|
||||||
await _router.Navigate("../../manage", new RouterNavigationOptions {AdditionalArguments = true});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Reinstall()
|
public async Task Reinstall()
|
||||||
{
|
{
|
||||||
if (await _windowService.ShowConfirmContentDialog("Reinstall entry", "Are you sure you want to reinstall this entry?"))
|
if (await _windowService.ShowConfirmContentDialog("Reinstall entry", "Are you sure you want to reinstall this entry?"))
|
||||||
@ -193,7 +189,28 @@ public partial class EntryReleaseInfoViewModel : ActivatableViewModelBase
|
|||||||
if (plugin == null)
|
if (plugin == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
PluginViewModel pluginViewModel = _settingsVmFactory.PluginViewModel(plugin, ReactiveCommand.Create(() => { }));
|
await _pluginInteractionService.RemovePluginPrerequisites(plugin, true);
|
||||||
await pluginViewModel.ExecuteRemovePrerequisites(true);
|
}
|
||||||
|
|
||||||
|
private async Task AutoEnablePlugin(Plugin plugin)
|
||||||
|
{
|
||||||
|
// There's quite a bit of UI involved in enabling a plugin, borrowing the PluginSettingsViewModel for this
|
||||||
|
await _pluginInteractionService.EnablePlugin(plugin, true);
|
||||||
|
|
||||||
|
// Find features without prerequisites to enable
|
||||||
|
foreach (PluginFeatureInfo pluginFeatureInfo in plugin.Features)
|
||||||
|
{
|
||||||
|
if (pluginFeatureInfo.Instance == null || pluginFeatureInfo.Instance.IsEnabled || pluginFeatureInfo.Prerequisites.Count != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_pluginManagementService.EnablePluginFeature(pluginFeatureInfo.Instance, true);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_notificationService.CreateNotification().WithTitle("Failed to enable plugin feature").WithMessage(e.Message).WithSeverity(NotificationSeverity.Error).Show();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Artemis.Core;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Services.Interfaces;
|
||||||
|
|
||||||
|
public interface IPluginInteractionService : IArtemisUIService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Enables a plugin, showing prerequisites and config windows as necessary.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="plugin">The plugin to enable.</param>
|
||||||
|
/// <param name="showMandatoryConfigWindow"></param>
|
||||||
|
/// <returns>A task representing the asynchronous operation.</returns>
|
||||||
|
Task<bool> EnablePlugin(Plugin plugin, bool showMandatoryConfigWindow);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disables a plugin, stopping all its features and services.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="plugin">The plugin to disable.</param>
|
||||||
|
/// <returns>A task representing the asynchronous operation with a boolean indicating success.</returns>
|
||||||
|
Task<bool> DisablePlugin(Plugin plugin);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a plugin from the system, optionally running uninstall actions for prerequisites.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="plugin">The plugin to remove.</param>
|
||||||
|
/// <returns>A task representing the asynchronous operation with a boolean indicating success.</returns>
|
||||||
|
Task<bool> RemovePlugin(Plugin plugin);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all settings and configuration data for a plugin, temporarily disabling it during the process.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="plugin">The plugin whose settings should be cleared.</param>
|
||||||
|
/// <returns>A task representing the asynchronous operation with a boolean indicating success.</returns>
|
||||||
|
Task<bool> RemovePluginSettings(Plugin plugin);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the prerequisites for a plugin.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="plugin">The plugin whose prerequisites should be removed.</param>
|
||||||
|
/// <param name="forPluginRemoval">Whether the prerequisites are being removed for a plugin removal.</param>
|
||||||
|
/// <returns>A task representing the asynchronous operation with a boolean indicating success.</returns>
|
||||||
|
Task<bool> RemovePluginPrerequisites(Plugin plugin, bool forPluginRemoval);
|
||||||
|
}
|
||||||
170
src/Artemis.UI/Services/PluginInteractionService.cs
Normal file
170
src/Artemis.UI/Services/PluginInteractionService.cs
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.Core.Services;
|
||||||
|
using Artemis.UI.Exceptions;
|
||||||
|
using Artemis.UI.Screens.Plugins;
|
||||||
|
using Artemis.UI.Services.Interfaces;
|
||||||
|
using Artemis.UI.Shared;
|
||||||
|
using Artemis.UI.Shared.Services;
|
||||||
|
using Artemis.UI.Shared.Services.Builders;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Services;
|
||||||
|
|
||||||
|
public class PluginInteractionService : IPluginInteractionService
|
||||||
|
{
|
||||||
|
private readonly ICoreService _coreService;
|
||||||
|
private readonly IPluginManagementService _pluginManagementService;
|
||||||
|
private readonly IWindowService _windowService;
|
||||||
|
private readonly INotificationService _notificationService;
|
||||||
|
|
||||||
|
public PluginInteractionService(ICoreService coreService, IPluginManagementService pluginManagementService, IWindowService windowService, INotificationService notificationService)
|
||||||
|
{
|
||||||
|
_coreService = coreService;
|
||||||
|
_pluginManagementService = pluginManagementService;
|
||||||
|
_windowService = windowService;
|
||||||
|
_notificationService = notificationService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<bool> EnablePlugin(Plugin plugin, bool showMandatoryConfigWindow)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
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? Artemis will need to restart.",
|
||||||
|
"Confirm and restart"
|
||||||
|
);
|
||||||
|
if (!confirmed)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if all prerequisites are met async
|
||||||
|
List<IPrerequisitesSubject> subjects = [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()))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Run(() => _pluginManagementService.EnablePlugin(plugin, true, true));
|
||||||
|
|
||||||
|
// If the plugin has a mandatory settings window, open it and wait
|
||||||
|
if (showMandatoryConfigWindow && plugin.ConfigurationDialog != null && plugin.ConfigurationDialog.IsMandatory)
|
||||||
|
{
|
||||||
|
if (plugin.Resolve(plugin.ConfigurationDialog.Type) is not PluginConfigurationViewModel viewModel)
|
||||||
|
throw new ArtemisUIException($"The type of a plugin configuration dialog must inherit {nameof(PluginConfigurationViewModel)}");
|
||||||
|
|
||||||
|
await _windowService.ShowDialogAsync(new PluginSettingsWindowViewModel(viewModel));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
await ShowPluginToggleFailure(plugin, true, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<bool> DisablePlugin(Plugin plugin)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Task.Run(() => _pluginManagementService.DisablePlugin(plugin, true));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
await ShowPluginToggleFailure(plugin, false, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<bool> RemovePlugin(Plugin plugin)
|
||||||
|
{
|
||||||
|
bool confirmed = await _windowService.ShowConfirmContentDialog("Remove plugin", "Are you sure you want to remove this plugin?");
|
||||||
|
if (!confirmed)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// If the plugin or any of its features has uninstall actions, offer to run these
|
||||||
|
await RemovePrerequisites(plugin, true);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_pluginManagementService.RemovePlugin(plugin, false);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_windowService.ShowExceptionDialog("Failed to remove plugin", e);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
_notificationService.CreateNotification().WithTitle("Removed plugin.").Show();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<bool> RemovePluginSettings(Plugin plugin)
|
||||||
|
{
|
||||||
|
bool confirmed = await _windowService.ShowConfirmContentDialog("Clear plugin settings", "Are you sure you want to clear the settings of this plugin?");
|
||||||
|
if (!confirmed)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bool wasEnabled = plugin.IsEnabled;
|
||||||
|
|
||||||
|
if (wasEnabled)
|
||||||
|
_pluginManagementService.DisablePlugin(plugin, false);
|
||||||
|
|
||||||
|
_pluginManagementService.RemovePluginSettings(plugin);
|
||||||
|
|
||||||
|
if (wasEnabled)
|
||||||
|
_pluginManagementService.EnablePlugin(plugin, false);
|
||||||
|
|
||||||
|
_notificationService.CreateNotification().WithTitle("Cleared plugin settings.").Show();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<bool> RemovePluginPrerequisites(Plugin plugin, bool forPluginRemoval)
|
||||||
|
{
|
||||||
|
await RemovePrerequisites(plugin, false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ShowPluginToggleFailure(Plugin plugin, bool enable, Exception e)
|
||||||
|
{
|
||||||
|
string action = enable ? "enable" : "disable";
|
||||||
|
ContentDialogBuilder builder = _windowService.CreateContentDialog()
|
||||||
|
.WithTitle($"Failed to {action} plugin {plugin.Info.Name}")
|
||||||
|
.WithContent(e.Message)
|
||||||
|
.HavingPrimaryButton(b => b.WithText("View logs").WithAction(() => Utilities.OpenFolder(Constants.LogsFolder)));
|
||||||
|
// If available, add a secondary button pointing to the support page
|
||||||
|
if (plugin.Info.HelpPage != null)
|
||||||
|
builder = builder.HavingSecondaryButton(b => b.WithText("Open support page").WithAction(() => Utilities.OpenUrl(plugin.Info.HelpPage.ToString())));
|
||||||
|
|
||||||
|
await builder.ShowAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RemovePrerequisites(Plugin plugin, bool forPluginRemoval)
|
||||||
|
{
|
||||||
|
List<IPrerequisitesSubject> subjects = [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");
|
||||||
|
}
|
||||||
|
}
|
||||||
106
src/Artemis.WebClient.Workshop/BuiltInPluginsMigrator.cs
Normal file
106
src/Artemis.WebClient.Workshop/BuiltInPluginsMigrator.cs
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.Storage.Entities.Plugins;
|
||||||
|
using Artemis.Storage.Repositories.Interfaces;
|
||||||
|
using Artemis.UI.Shared.Utilities;
|
||||||
|
using Artemis.WebClient.Workshop.Services;
|
||||||
|
using Serilog;
|
||||||
|
using StrawberryShake;
|
||||||
|
|
||||||
|
namespace Artemis.WebClient.Workshop;
|
||||||
|
|
||||||
|
public static class BuiltInPluginsMigrator
|
||||||
|
{
|
||||||
|
private static readonly Guid[] ObsoleteBuiltInPlugins =
|
||||||
|
[
|
||||||
|
new("4e1e54fd-6636-40ad-afdc-b3b0135feab2"),
|
||||||
|
new("cad475d3-c621-4ec7-bbfc-784e3b4723ce"),
|
||||||
|
new("ab41d601-35e0-4a73-bf0b-94509b006ab0"),
|
||||||
|
new("27d124e3-48e8-4b0a-8a5e-d5e337a88d4a")
|
||||||
|
];
|
||||||
|
|
||||||
|
public static async Task<bool> Migrate(IWorkshopService workshopService, IWorkshopClient workshopClient, ILogger logger, IPluginRepository pluginRepository)
|
||||||
|
{
|
||||||
|
// If no default plugins are present (later installs), do nothing
|
||||||
|
DirectoryInfo pluginDirectory = new(Constants.PluginsFolder);
|
||||||
|
if (!pluginDirectory.Exists)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load plugin info, the plugin management service isn't available yet (which is exactly what we want)
|
||||||
|
List<(PluginInfo PluginInfo, DirectoryInfo Directory)> plugins = [];
|
||||||
|
foreach (DirectoryInfo subDirectory in pluginDirectory.EnumerateDirectories())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Load the metadata
|
||||||
|
string metadataFile = Path.Combine(subDirectory.FullName, "plugin.json");
|
||||||
|
if (File.Exists(metadataFile))
|
||||||
|
plugins.Add((CoreJson.Deserialize<PluginInfo>(await File.ReadAllTextAsync(metadataFile))!, subDirectory));
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// ignored, who knows what old stuff people might have in their plugins folder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plugins.Count == 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
IWorkshopService.WorkshopStatus workshopStatus = await workshopService.GetWorkshopStatus(CancellationToken.None);
|
||||||
|
if (!workshopStatus.IsReachable)
|
||||||
|
{
|
||||||
|
logger.Warning("MigrateBuiltInPlugins - Cannot migrate built-in plugins because the workshop is unreachable");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Information("MigrateBuiltInPlugins - Migrating built-in plugins to workshop entries");
|
||||||
|
IOperationResult<IGetDefaultPluginsResult> result = await workshopClient.GetDefaultPlugins.ExecuteAsync(100, null, CancellationToken.None);
|
||||||
|
List<IGetDefaultPlugins_EntriesV2_Edges_Node> entries = result.Data?.EntriesV2?.Edges?.Select(e => e.Node).ToList() ?? [];
|
||||||
|
while (result.Data?.EntriesV2?.PageInfo is {HasNextPage: true})
|
||||||
|
{
|
||||||
|
result = await workshopClient.GetDefaultPlugins.ExecuteAsync(100, result.Data.EntriesV2.PageInfo.EndCursor, CancellationToken.None);
|
||||||
|
if (result.Data?.EntriesV2?.Edges != null)
|
||||||
|
entries.AddRange(result.Data.EntriesV2.Edges.Select(e => e.Node));
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Information("MigrateBuiltInPlugins - Found {Count} default plugins in the workshop", entries.Count);
|
||||||
|
foreach (IGetDefaultPlugins_EntriesV2_Edges_Node entry in entries)
|
||||||
|
{
|
||||||
|
// Skip entries without plugin info or releases, shouldn't happen but theoretically possible
|
||||||
|
if (entry.PluginInfo == null || entry.LatestRelease == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Find a built-in plugin
|
||||||
|
(PluginInfo? pluginInfo, DirectoryInfo? directory) = plugins.FirstOrDefault(p => p.PluginInfo.Guid == entry.PluginInfo.PluginGuid);
|
||||||
|
if (pluginInfo == null || directory == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// If the plugin is enabled, install the workshop equivalent (the built-in plugin will be removed by the install process)
|
||||||
|
PluginEntity? entity = pluginRepository.GetPluginByPluginGuid(pluginInfo.Guid);
|
||||||
|
if (entity != null && entity.IsEnabled)
|
||||||
|
{
|
||||||
|
logger.Information("MigrateBuiltInPlugins - Migrating built-in plugin {Plugin} to workshop entry {Entry}", pluginInfo.Name, entry);
|
||||||
|
await workshopService.InstallEntry(entry, entry.LatestRelease, new Progress<StreamProgress>(), CancellationToken.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the built-in plugin, it's no longer needed
|
||||||
|
directory.Delete(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove obsolete built-in plugins
|
||||||
|
foreach (Guid obsoleteBuiltInPlugin in ObsoleteBuiltInPlugins)
|
||||||
|
{
|
||||||
|
(PluginInfo? pluginInfo, DirectoryInfo? directory) = plugins.FirstOrDefault(p => p.PluginInfo.Guid == obsoleteBuiltInPlugin);
|
||||||
|
if (pluginInfo == null || directory == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
directory.Delete(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Information("MigrateBuiltInPlugins - Finished migrating built-in plugins to workshop entries");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,13 +1,31 @@
|
|||||||
using Artemis.WebClient.Workshop.Models;
|
using Artemis.Core;
|
||||||
|
using Artemis.WebClient.Workshop.Models;
|
||||||
using Artemis.WebClient.Workshop.Services;
|
using Artemis.WebClient.Workshop.Services;
|
||||||
|
|
||||||
namespace Artemis.WebClient.Workshop.Handlers.InstallationHandlers;
|
namespace Artemis.WebClient.Workshop.Handlers.InstallationHandlers;
|
||||||
|
|
||||||
public class EntryInstallResult
|
public class EntryInstallResult
|
||||||
{
|
{
|
||||||
public bool IsSuccess { get; set; }
|
/// <summary>
|
||||||
public string? Message { get; set; }
|
/// Gets a value indicating whether the installation was successful.
|
||||||
public InstalledEntry? Entry { get; set; }
|
/// </summary>
|
||||||
|
public bool IsSuccess { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a message describing the result of the installation.
|
||||||
|
/// </summary>
|
||||||
|
public string? Message { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the entry that was installed, if any.
|
||||||
|
/// </summary>
|
||||||
|
public InstalledEntry? Entry { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the result object returned by the installation handler, if any.
|
||||||
|
/// <remarks>This'll be a <see cref="ProfileConfiguration"/>, <see cref="ArtemisLayout"/> or <see cref="Plugin"/> depending on the entry type.</remarks>
|
||||||
|
/// </summary>
|
||||||
|
public object? Installed { get; private set; }
|
||||||
|
|
||||||
public static EntryInstallResult FromFailure(string? message)
|
public static EntryInstallResult FromFailure(string? message)
|
||||||
{
|
{
|
||||||
@ -18,12 +36,13 @@ public class EntryInstallResult
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static EntryInstallResult FromSuccess(InstalledEntry installedEntry)
|
public static EntryInstallResult FromSuccess(InstalledEntry installedEntry, object? result)
|
||||||
{
|
{
|
||||||
return new EntryInstallResult
|
return new EntryInstallResult
|
||||||
{
|
{
|
||||||
IsSuccess = true,
|
IsSuccess = true,
|
||||||
Entry = installedEntry
|
Entry = installedEntry,
|
||||||
|
Installed = result
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -59,7 +59,7 @@ public class LayoutEntryInstallationHandler : IEntryInstallationHandler
|
|||||||
{
|
{
|
||||||
installedEntry.ApplyRelease(release);
|
installedEntry.ApplyRelease(release);
|
||||||
_workshopService.SaveInstalledEntry(installedEntry);
|
_workshopService.SaveInstalledEntry(installedEntry);
|
||||||
return EntryInstallResult.FromSuccess(installedEntry);
|
return EntryInstallResult.FromSuccess(installedEntry, layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the layout ended up being invalid yoink it out again, shoooo
|
// If the layout ended up being invalid yoink it out again, shoooo
|
||||||
|
|||||||
@ -30,7 +30,7 @@ public class PluginEntryInstallationHandler : IEntryInstallationHandler
|
|||||||
{
|
{
|
||||||
// If the folder already exists, we're not going to reinstall the plugin since files may be in use, consider our job done
|
// If the folder already exists, we're not going to reinstall the plugin since files may be in use, consider our job done
|
||||||
if (installedEntry.GetReleaseDirectory(release).Exists)
|
if (installedEntry.GetReleaseDirectory(release).Exists)
|
||||||
return ApplyAndSave(installedEntry, release);
|
return ApplyAndSave(null, installedEntry, release);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -64,6 +64,11 @@ public class PluginEntryInstallationHandler : IEntryInstallationHandler
|
|||||||
archive.ExtractToDirectory(releaseDirectory.FullName);
|
archive.ExtractToDirectory(releaseDirectory.FullName);
|
||||||
|
|
||||||
PluginInfo pluginInfo = CoreJson.Deserialize<PluginInfo>(await File.ReadAllTextAsync(Path.Combine(releaseDirectory.FullName, "plugin.json"), cancellationToken))!;
|
PluginInfo pluginInfo = CoreJson.Deserialize<PluginInfo>(await File.ReadAllTextAsync(Path.Combine(releaseDirectory.FullName, "plugin.json"), cancellationToken))!;
|
||||||
|
installedEntry.SetMetadata("PluginId", pluginInfo.Guid);
|
||||||
|
|
||||||
|
// If the plugin management service isn't loaded yet (happens while migrating from built-in plugins) we're done here
|
||||||
|
if (!_pluginManagementService.LoadedPlugins)
|
||||||
|
return ApplyAndSave(null, installedEntry, release);
|
||||||
|
|
||||||
// If there is already a version of the plugin installed, remove it
|
// If there is already a version of the plugin installed, remove it
|
||||||
Plugin? currentVersion = _pluginManagementService.GetAllPlugins().FirstOrDefault(p => p.Guid == pluginInfo.Guid);
|
Plugin? currentVersion = _pluginManagementService.GetAllPlugins().FirstOrDefault(p => p.Guid == pluginInfo.Guid);
|
||||||
@ -78,13 +83,12 @@ public class PluginEntryInstallationHandler : IEntryInstallationHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load the plugin, next time during startup this will happen automatically
|
// Load the plugin, next time during startup this will happen automatically
|
||||||
|
Plugin? plugin = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Plugin? plugin = _pluginManagementService.LoadPlugin(releaseDirectory);
|
plugin = _pluginManagementService.LoadPlugin(releaseDirectory);
|
||||||
if (plugin == null)
|
if (plugin == null)
|
||||||
throw new ArtemisWorkshopException("Failed to load plugin, it may be incompatible");
|
throw new ArtemisWorkshopException("Failed to load plugin, it may be incompatible");
|
||||||
|
|
||||||
installedEntry.SetMetadata("PluginId", plugin.Guid);
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@ -102,7 +106,7 @@ public class PluginEntryInstallationHandler : IEntryInstallationHandler
|
|||||||
return EntryInstallResult.FromFailure(e.Message);
|
return EntryInstallResult.FromFailure(e.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ApplyAndSave(installedEntry, release);
|
return ApplyAndSave(plugin, installedEntry, release);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<EntryUninstallResult> UninstallAsync(InstalledEntry installedEntry, CancellationToken cancellationToken)
|
public Task<EntryUninstallResult> UninstallAsync(InstalledEntry installedEntry, CancellationToken cancellationToken)
|
||||||
@ -133,10 +137,10 @@ public class PluginEntryInstallationHandler : IEntryInstallationHandler
|
|||||||
return Task.FromResult(EntryUninstallResult.FromSuccess(message));
|
return Task.FromResult(EntryUninstallResult.FromSuccess(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
private EntryInstallResult ApplyAndSave(InstalledEntry installedEntry, IRelease release)
|
private EntryInstallResult ApplyAndSave(Plugin? plugin, InstalledEntry installedEntry, IRelease release)
|
||||||
{
|
{
|
||||||
installedEntry.ApplyRelease(release);
|
installedEntry.ApplyRelease(release);
|
||||||
_workshopService.SaveInstalledEntry(installedEntry);
|
_workshopService.SaveInstalledEntry(installedEntry);
|
||||||
return EntryInstallResult.FromSuccess(installedEntry);
|
return EntryInstallResult.FromSuccess(installedEntry, plugin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -52,7 +52,7 @@ public class ProfileEntryInstallationHandler : IEntryInstallationHandler
|
|||||||
// With everything updated, remove the old profile
|
// With everything updated, remove the old profile
|
||||||
_profileService.RemoveProfileConfiguration(existing);
|
_profileService.RemoveProfileConfiguration(existing);
|
||||||
|
|
||||||
return EntryInstallResult.FromSuccess(installedEntry);
|
return EntryInstallResult.FromSuccess(installedEntry, overwritten);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ public class ProfileEntryInstallationHandler : IEntryInstallationHandler
|
|||||||
|
|
||||||
// Update the release and return the profile configuration
|
// Update the release and return the profile configuration
|
||||||
UpdateRelease(installedEntry, release);
|
UpdateRelease(installedEntry, release);
|
||||||
return EntryInstallResult.FromSuccess(installedEntry);
|
return EntryInstallResult.FromSuccess(installedEntry, imported);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<EntryUninstallResult> UninstallAsync(InstalledEntry installedEntry, CancellationToken cancellationToken)
|
public async Task<EntryUninstallResult> UninstallAsync(InstalledEntry installedEntry, CancellationToken cancellationToken)
|
||||||
|
|||||||
@ -40,3 +40,32 @@ query GetDefaultEntries($first: Int, $after: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
query GetDefaultPlugins($first: Int, $after: String) {
|
||||||
|
entriesV2(
|
||||||
|
includeDefaults: true
|
||||||
|
where: {
|
||||||
|
and: [
|
||||||
|
{ defaultEntryInfo: { entryId: { gt: 0 } } }
|
||||||
|
{ entryType: {eq: PLUGIN} }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
first: $first
|
||||||
|
after: $after
|
||||||
|
) {
|
||||||
|
totalCount
|
||||||
|
pageInfo {
|
||||||
|
hasNextPage
|
||||||
|
endCursor
|
||||||
|
}
|
||||||
|
edges {
|
||||||
|
cursor
|
||||||
|
node {
|
||||||
|
...entrySummary
|
||||||
|
pluginInfo {
|
||||||
|
pluginGuid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -124,7 +124,7 @@ public interface IWorkshopService
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes the workshop service.
|
/// Initializes the workshop service.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Initialize();
|
Task Initialize();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the status of the workshop.
|
/// Represents the status of the workshop.
|
||||||
@ -134,6 +134,7 @@ public interface IWorkshopService
|
|||||||
public event EventHandler<InstalledEntry>? OnInstalledEntrySaved;
|
public event EventHandler<InstalledEntry>? OnInstalledEntrySaved;
|
||||||
public event EventHandler<InstalledEntry>? OnEntryUninstalled;
|
public event EventHandler<InstalledEntry>? OnEntryUninstalled;
|
||||||
public event EventHandler<InstalledEntry>? OnEntryInstalled;
|
public event EventHandler<InstalledEntry>? OnEntryInstalled;
|
||||||
|
public event EventHandler? MigratingBuildInPlugins;
|
||||||
|
|
||||||
void SetAutoUpdate(InstalledEntry installedEntry, bool autoUpdate);
|
void SetAutoUpdate(InstalledEntry installedEntry, bool autoUpdate);
|
||||||
}
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
|
using Artemis.Storage.Entities.Plugins;
|
||||||
using Artemis.Storage.Entities.Workshop;
|
using Artemis.Storage.Entities.Workshop;
|
||||||
using Artemis.Storage.Repositories.Interfaces;
|
using Artemis.Storage.Repositories.Interfaces;
|
||||||
using Artemis.UI.Shared.Routing;
|
using Artemis.UI.Shared.Routing;
|
||||||
@ -10,6 +11,7 @@ using Artemis.WebClient.Workshop.Handlers.InstallationHandlers;
|
|||||||
using Artemis.WebClient.Workshop.Handlers.UploadHandlers;
|
using Artemis.WebClient.Workshop.Handlers.UploadHandlers;
|
||||||
using Artemis.WebClient.Workshop.Models;
|
using Artemis.WebClient.Workshop.Models;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
using StrawberryShake;
|
||||||
|
|
||||||
namespace Artemis.WebClient.Workshop.Services;
|
namespace Artemis.WebClient.Workshop.Services;
|
||||||
|
|
||||||
@ -22,15 +24,24 @@ public class WorkshopService : IWorkshopService
|
|||||||
private readonly Lazy<IPluginManagementService> _pluginManagementService;
|
private readonly Lazy<IPluginManagementService> _pluginManagementService;
|
||||||
private readonly Lazy<IProfileService> _profileService;
|
private readonly Lazy<IProfileService> _profileService;
|
||||||
private readonly EntryInstallationHandlerFactory _factory;
|
private readonly EntryInstallationHandlerFactory _factory;
|
||||||
|
private readonly IPluginRepository _pluginRepository;
|
||||||
|
private readonly IWorkshopClient _workshopClient;
|
||||||
|
private readonly PluginSetting<bool> _migratedBuiltInPlugins;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private bool _initialized;
|
private bool _initialized;
|
||||||
|
|
||||||
public WorkshopService(ILogger logger,
|
public WorkshopService(ILogger logger,
|
||||||
IHttpClientFactory httpClientFactory,
|
IHttpClientFactory httpClientFactory,
|
||||||
IRouter router,
|
IRouter router,
|
||||||
IEntryRepository entryRepository,
|
IEntryRepository entryRepository,
|
||||||
|
ISettingsService settingsService,
|
||||||
Lazy<IPluginManagementService> pluginManagementService,
|
Lazy<IPluginManagementService> pluginManagementService,
|
||||||
Lazy<IProfileService> profileService,
|
Lazy<IProfileService> profileService,
|
||||||
EntryInstallationHandlerFactory factory)
|
EntryInstallationHandlerFactory factory,
|
||||||
|
IPluginRepository pluginRepository,
|
||||||
|
IWorkshopClient workshopClient)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_httpClientFactory = httpClientFactory;
|
_httpClientFactory = httpClientFactory;
|
||||||
@ -39,6 +50,10 @@ public class WorkshopService : IWorkshopService
|
|||||||
_pluginManagementService = pluginManagementService;
|
_pluginManagementService = pluginManagementService;
|
||||||
_profileService = profileService;
|
_profileService = profileService;
|
||||||
_factory = factory;
|
_factory = factory;
|
||||||
|
_pluginRepository = pluginRepository;
|
||||||
|
_workshopClient = workshopClient;
|
||||||
|
|
||||||
|
_migratedBuiltInPlugins = settingsService.GetSetting("Workshop.MigratedBuiltInPlugins", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Stream?> GetEntryIcon(long entryId, CancellationToken cancellationToken)
|
public async Task<Stream?> GetEntryIcon(long entryId, CancellationToken cancellationToken)
|
||||||
@ -227,7 +242,7 @@ public class WorkshopService : IWorkshopService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Initialize()
|
public async Task Initialize()
|
||||||
{
|
{
|
||||||
if (_initialized)
|
if (_initialized)
|
||||||
throw new ArtemisWorkshopException("Workshop service is already initialized");
|
throw new ArtemisWorkshopException("Workshop service is already initialized");
|
||||||
@ -238,6 +253,7 @@ public class WorkshopService : IWorkshopService
|
|||||||
Directory.CreateDirectory(Constants.WorkshopFolder);
|
Directory.CreateDirectory(Constants.WorkshopFolder);
|
||||||
|
|
||||||
RemoveOrphanedFiles();
|
RemoveOrphanedFiles();
|
||||||
|
await MigrateBuiltInPlugins();
|
||||||
|
|
||||||
_pluginManagementService.Value.AdditionalPluginDirectories.AddRange(GetInstalledEntries()
|
_pluginManagementService.Value.AdditionalPluginDirectories.AddRange(GetInstalledEntries()
|
||||||
.Where(e => e.EntryType == EntryType.Plugin)
|
.Where(e => e.EntryType == EntryType.Plugin)
|
||||||
@ -297,6 +313,19 @@ public class WorkshopService : IWorkshopService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task MigrateBuiltInPlugins()
|
||||||
|
{
|
||||||
|
// If already migrated, do nothing
|
||||||
|
if (_migratedBuiltInPlugins.Value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
MigratingBuildInPlugins?.Invoke(this, EventArgs.Empty);
|
||||||
|
|
||||||
|
bool migrated = await BuiltInPluginsMigrator.Migrate(this, _workshopClient, _logger, _pluginRepository);
|
||||||
|
_migratedBuiltInPlugins.Value = migrated;
|
||||||
|
_migratedBuiltInPlugins.Save();
|
||||||
|
}
|
||||||
|
|
||||||
private void ProfileServiceOnProfileRemoved(object? sender, ProfileConfigurationEventArgs e)
|
private void ProfileServiceOnProfileRemoved(object? sender, ProfileConfigurationEventArgs e)
|
||||||
{
|
{
|
||||||
InstalledEntry? entry = GetInstalledEntryByProfile(e.ProfileConfiguration);
|
InstalledEntry? entry = GetInstalledEntryByProfile(e.ProfileConfiguration);
|
||||||
@ -322,4 +351,6 @@ public class WorkshopService : IWorkshopService
|
|||||||
public event EventHandler<InstalledEntry>? OnEntryUninstalled;
|
public event EventHandler<InstalledEntry>? OnEntryUninstalled;
|
||||||
|
|
||||||
public event EventHandler<InstalledEntry>? OnEntryInstalled;
|
public event EventHandler<InstalledEntry>? OnEntryInstalled;
|
||||||
|
|
||||||
|
public event EventHandler? MigratingBuildInPlugins;
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user