diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs
index ccbbff9ea..f6716082e 100644
--- a/src/Artemis.Core/Services/CoreService.cs
+++ b/src/Artemis.Core/Services/CoreService.cs
@@ -63,7 +63,6 @@ internal class CoreService : ICoreService
_logger.Debug("Forcing plugins to use HidSharp {HidSharpVersion}", hidSharpVersion);
// Initialize the services
- _pluginManagementService.CopyBuiltInPlugins();
_pluginManagementService.LoadPlugins(IsElevated);
_pluginManagementService.StartHotReload();
_renderService.Initialize();
diff --git a/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs b/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs
index 806526773..b6017a5c6 100644
--- a/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs
+++ b/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs
@@ -21,13 +21,12 @@ public interface IPluginManagementService : IArtemisService, IDisposable
/// Indicates whether or not plugins are currently being loaded
///
bool LoadingPlugins { get; }
-
+
///
- /// Copy built-in plugins from the executable directory to the plugins directory if the version is higher
- /// (higher or equal if compiled as debug)
+ /// Indicates whether or not plugins are currently loaded
///
- void CopyBuiltInPlugins();
-
+ bool LoadedPlugins { get; }
+
///
/// Loads all installed plugins. If plugins already loaded this will reload them all
///
@@ -150,12 +149,7 @@ public interface IPluginManagementService : IArtemisService, IDisposable
///
///
DeviceProvider GetDeviceProviderByDevice(IRGBDevice device);
-
- ///
- /// Occurs when built-in plugins are being loaded
- ///
- event EventHandler CopyingBuildInPlugins;
-
+
///
/// Occurs when a plugin has started loading
///
diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs
index 346d82a88..f65a9d858 100644
--- a/src/Artemis.Core/Services/PluginManagementService.cs
+++ b/src/Artemis.Core/Services/PluginManagementService.cs
@@ -46,114 +46,8 @@ internal class PluginManagementService : IPluginManagementService
public List AdditionalPluginDirectories { get; } = new();
public bool LoadingPlugins { 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(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 bool LoadedPlugins { get; private set; }
public List GetAllPlugins()
{
@@ -328,8 +222,10 @@ internal class PluginManagementService : IPluginManagementService
// ReSharper restore InconsistentlySynchronizedField
LoadingPlugins = false;
+ LoadedPlugins = true;
}
+
public void UnloadPlugins()
{
// Unload all plugins
@@ -686,7 +582,7 @@ internal class PluginManagementService : IPluginManagementService
if (removeSettings)
RemovePluginSettings(plugin);
-
+
OnPluginRemoved(new PluginEventArgs(plugin));
}
@@ -893,7 +789,7 @@ internal class PluginManagementService : IPluginManagementService
{
PluginDisabled?.Invoke(this, e);
}
-
+
protected virtual void OnPluginRemoved(PluginEventArgs e)
{
PluginRemoved?.Invoke(this, e);
diff --git a/src/Artemis.UI/Screens/Plugins/PluginViewModel.cs b/src/Artemis.UI/Screens/Plugins/PluginViewModel.cs
index c29963248..82d1f4b86 100644
--- a/src/Artemis.UI/Screens/Plugins/PluginViewModel.cs
+++ b/src/Artemis.UI/Screens/Plugins/PluginViewModel.cs
@@ -6,11 +6,11 @@ using System.Reactive;
using System.Reactive.Disposables;
using System.Threading.Tasks;
using Artemis.Core;
-using Artemis.Core.Services;
using Artemis.UI.Exceptions;
+using Artemis.UI.Services;
+using Artemis.UI.Services.Interfaces;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
-using Artemis.UI.Shared.Services.Builders;
using Avalonia.Controls;
using Avalonia.Threading;
using Material.Icons;
@@ -21,9 +21,7 @@ namespace Artemis.UI.Screens.Plugins;
public partial class PluginViewModel : ActivatableViewModelBase
{
- private readonly ICoreService _coreService;
- private readonly INotificationService _notificationService;
- private readonly IPluginManagementService _pluginManagementService;
+ private readonly IPluginInteractionService _pluginInteractionService;
private readonly IWindowService _windowService;
private Window? _settingsWindow;
[Notify] private bool _canInstallPrerequisites;
@@ -31,18 +29,11 @@ public partial class PluginViewModel : ActivatableViewModelBase
[Notify] private bool _enabling;
[Notify] private Plugin _plugin;
- public PluginViewModel(Plugin plugin,
- ReactiveCommand? reload,
- ICoreService coreService,
- IWindowService windowService,
- INotificationService notificationService,
- IPluginManagementService pluginManagementService)
+ public PluginViewModel(Plugin plugin, ReactiveCommand? reload, IWindowService windowService, IPluginInteractionService pluginInteractionService)
{
_plugin = plugin;
- _coreService = coreService;
_windowService = windowService;
- _notificationService = notificationService;
- _pluginManagementService = pluginManagementService;
+ _pluginInteractionService = pluginInteractionService;
Platforms = new ObservableCollection();
if (Plugin.Info.Platforms != null)
@@ -88,7 +79,6 @@ public partial class PluginViewModel : ActivatableViewModelBase
public ReactiveCommand OpenPluginDirectory { get; }
public ObservableCollection Platforms { get; }
- public string Type => Plugin.GetType().BaseType?.Name ?? Plugin.GetType().Name;
public bool IsEnabled => Plugin.IsEnabled;
public async Task UpdateEnabled(bool enable)
@@ -97,55 +87,15 @@ public partial class PluginViewModel : ActivatableViewModelBase
return;
if (!enable)
- {
- try
- {
- await Task.Run(() => _pluginManagementService.DisablePlugin(Plugin, true));
- }
- catch (Exception e)
- {
- await ShowUpdateEnableFailure(enable, e);
- }
- finally
- {
- this.RaisePropertyChanged(nameof(IsEnabled));
- }
-
- return;
- }
-
- try
+ await _pluginInteractionService.DisablePlugin(Plugin);
+ else
{
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? Artemis will need to restart.", "Confirm and restart");
- if (!confirmed)
- return;
- }
-
- // Check if all prerequisites are met async
- List 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
- {
+ await _pluginInteractionService.EnablePlugin(Plugin, false);
Enabling = false;
- this.RaisePropertyChanged(nameof(IsEnabled));
}
+
+ this.RaisePropertyChanged(nameof(IsEnabled));
}
public void CheckPrerequisites()
@@ -220,43 +170,12 @@ public partial class PluginViewModel : ActivatableViewModelBase
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();
+ await _pluginInteractionService.RemovePluginSettings(Plugin);
}
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
- 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();
+ await _pluginInteractionService.RemovePlugin(Plugin);
}
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)
{
Dispatcher.UIThread.Post(() =>
@@ -299,9 +204,9 @@ public partial class PluginViewModel : ActivatableViewModelBase
{
if (IsEnabled)
return;
-
+
await UpdateEnabled(true);
-
+
// If enabling failed, don't offer to show the settings
if (!IsEnabled || Plugin.ConfigurationDialog == null)
return;
diff --git a/src/Artemis.UI/Screens/Root/RootViewModel.cs b/src/Artemis.UI/Screens/Root/RootViewModel.cs
index df894ec9b..b3433b5da 100644
--- a/src/Artemis.UI/Screens/Root/RootViewModel.cs
+++ b/src/Artemis.UI/Screens/Root/RootViewModel.cs
@@ -83,7 +83,7 @@ public class RootViewModel : RoutableHostScreen, IMainWindowProv
_coreService.Initialized += (_, _) => Dispatcher.UIThread.InvokeAsync(OpenMainWindow);
}
- Task.Run(() =>
+ Task.Run(async () =>
{
try
{
@@ -93,7 +93,7 @@ public class RootViewModel : RoutableHostScreen, IMainWindowProv
return;
// 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
coreService.Initialize();
diff --git a/src/Artemis.UI/Screens/Root/SplashViewModel.cs b/src/Artemis.UI/Screens/Root/SplashViewModel.cs
index dd9622421..2900d9948 100644
--- a/src/Artemis.UI/Screens/Root/SplashViewModel.cs
+++ b/src/Artemis.UI/Screens/Root/SplashViewModel.cs
@@ -2,6 +2,7 @@
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Shared;
+using Artemis.WebClient.Workshop.Services;
using PropertyChanged.SourceGenerator;
namespace Artemis.UI.Screens.Root;
@@ -10,12 +11,12 @@ public partial class SplashViewModel : ViewModelBase
{
[Notify] private string _status;
- public SplashViewModel(ICoreService coreService, IPluginManagementService pluginManagementService)
+ public SplashViewModel(ICoreService coreService, IPluginManagementService pluginManagementService, IWorkshopService workshopService)
{
CoreService = coreService;
_status = "Initializing Core";
- pluginManagementService.CopyingBuildInPlugins += OnPluginManagementServiceOnCopyingBuildInPluginsManagement;
+ workshopService.MigratingBuildInPlugins += WorkshopServiceOnMigratingBuildInPlugins;
pluginManagementService.PluginLoading += OnPluginManagementServiceOnPluginManagementLoading;
pluginManagementService.PluginLoaded += OnPluginManagementServiceOnPluginManagementLoaded;
pluginManagementService.PluginEnabling += PluginManagementServiceOnPluginManagementEnabling;
@@ -25,6 +26,11 @@ public partial class SplashViewModel : ViewModelBase
}
public ICoreService CoreService { get; }
+
+ private void WorkshopServiceOnMigratingBuildInPlugins(object? sender, EventArgs args)
+ {
+ Status = "Migrating built-in plugins";
+ }
private void OnPluginManagementServiceOnPluginManagementLoaded(object? sender, PluginEventArgs args)
{
@@ -55,9 +61,4 @@ public partial class SplashViewModel : ViewModelBase
{
Status = "Initializing UI";
}
-
- private void OnPluginManagementServiceOnCopyingBuildInPluginsManagement(object? sender, EventArgs args)
- {
- Status = "Updating built-in plugins";
- }
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/StartupWizard/Steps/DefaultEntryItemViewModel.cs b/src/Artemis.UI/Screens/StartupWizard/Steps/DefaultEntryItemViewModel.cs
index b8382fbac..44b59b947 100644
--- a/src/Artemis.UI/Screens/StartupWizard/Steps/DefaultEntryItemViewModel.cs
+++ b/src/Artemis.UI/Screens/StartupWizard/Steps/DefaultEntryItemViewModel.cs
@@ -5,9 +5,10 @@ using System.Threading;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
-using Artemis.UI.DryIoc.Factories;
using Artemis.UI.Exceptions;
using Artemis.UI.Screens.Plugins;
+using Artemis.UI.Services;
+using Artemis.UI.Services.Interfaces;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Utilities;
@@ -28,7 +29,7 @@ public partial class DefaultEntryItemViewModel : ActivatableViewModelBase
private readonly IWindowService _windowService;
private readonly IPluginManagementService _pluginManagementService;
private readonly IProfileService _profileService;
- private readonly ISettingsVmFactory _settingsVmFactory;
+ private readonly IPluginInteractionService _pluginInteractionService;
private readonly Progress _progress = new();
[Notify] private bool _isInstalled;
@@ -41,14 +42,14 @@ public partial class DefaultEntryItemViewModel : ActivatableViewModelBase
IWindowService windowService,
IPluginManagementService pluginManagementService,
IProfileService profileService,
- ISettingsVmFactory settingsVmFactory)
+ IPluginInteractionService pluginInteractionService)
{
_logger = logger;
_workshopService = workshopService;
_windowService = windowService;
_pluginManagementService = pluginManagementService;
_profileService = profileService;
- _settingsVmFactory = settingsVmFactory;
+ _pluginInteractionService = pluginInteractionService;
Entry = entry;
_progress.ProgressChanged += (_, f) => InstallProgress = f.ProgressPercentage;
@@ -62,10 +63,7 @@ public partial class DefaultEntryItemViewModel : ActivatableViewModelBase
if (IsInstalled || !ShouldInstall || Entry.LatestRelease == null)
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);
- await minimumDelay;
if (!result.IsSuccess)
{
@@ -95,8 +93,7 @@ public partial class DefaultEntryItemViewModel : ActivatableViewModelBase
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
- PluginViewModel pluginViewModel = _settingsVmFactory.PluginViewModel(plugin, ReactiveCommand.Create(() => { }));
- await pluginViewModel.UpdateEnabled(true);
+ await _pluginInteractionService.EnablePlugin(plugin, true);
// Find features without prerequisites to enable
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);
}
}
-
- // 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)
diff --git a/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseInfoViewModel.cs b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseInfoViewModel.cs
index 3d5cba990..d077e8fae 100644
--- a/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseInfoViewModel.cs
+++ b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseInfoViewModel.cs
@@ -6,9 +6,8 @@ using System.Threading;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
-using Artemis.UI.DryIoc.Factories;
-using Artemis.UI.Screens.Plugins;
using Artemis.UI.Screens.Workshop.EntryReleases.Dialogs;
+using Artemis.UI.Services.Interfaces;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Routing;
using Artemis.UI.Shared.Services;
@@ -30,7 +29,7 @@ public partial class EntryReleaseInfoViewModel : ActivatableViewModelBase
private readonly IWindowService _windowService;
private readonly IWorkshopService _workshopService;
private readonly IPluginManagementService _pluginManagementService;
- private readonly ISettingsVmFactory _settingsVmFactory;
+ private readonly IPluginInteractionService _pluginInteractionService;
private readonly Progress _progress = new();
[Notify] private IReleaseDetails? _release;
@@ -46,14 +45,14 @@ public partial class EntryReleaseInfoViewModel : ActivatableViewModelBase
IWindowService windowService,
IWorkshopService workshopService,
IPluginManagementService pluginManagementService,
- ISettingsVmFactory settingsVmFactory)
+ IPluginInteractionService pluginInteractionService)
{
_router = router;
_notificationService = notificationService;
_windowService = windowService;
_workshopService = workshopService;
_pluginManagementService = pluginManagementService;
- _settingsVmFactory = settingsVmFactory;
+ _pluginInteractionService = pluginInteractionService;
_progress.ProgressChanged += (_, f) => InstallProgress = f.ProgressPercentage;
this.WhenActivated(d =>
@@ -124,7 +123,10 @@ public partial class EntryReleaseInfoViewModel : ActivatableViewModelBase
_workshopService.SetAutoUpdate(result.Entry, !disableAutoUpdates);
_notificationService.CreateNotification().WithTitle("Installation succeeded").WithSeverity(NotificationSeverity.Success).Show();
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)
{
@@ -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()
{
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)
return;
- PluginViewModel pluginViewModel = _settingsVmFactory.PluginViewModel(plugin, ReactiveCommand.Create(() => { }));
- await pluginViewModel.ExecuteRemovePrerequisites(true);
+ await _pluginInteractionService.RemovePluginPrerequisites(plugin, 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();
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Services/Interfaces/IPluginInteractionService.cs b/src/Artemis.UI/Services/Interfaces/IPluginInteractionService.cs
new file mode 100644
index 000000000..b76d765d6
--- /dev/null
+++ b/src/Artemis.UI/Services/Interfaces/IPluginInteractionService.cs
@@ -0,0 +1,44 @@
+using System.Threading.Tasks;
+using Artemis.Core;
+
+namespace Artemis.UI.Services.Interfaces;
+
+public interface IPluginInteractionService : IArtemisUIService
+{
+ ///
+ /// Enables a plugin, showing prerequisites and config windows as necessary.
+ ///
+ /// The plugin to enable.
+ ///
+ /// A task representing the asynchronous operation.
+ Task EnablePlugin(Plugin plugin, bool showMandatoryConfigWindow);
+
+ ///
+ /// Disables a plugin, stopping all its features and services.
+ ///
+ /// The plugin to disable.
+ /// A task representing the asynchronous operation with a boolean indicating success.
+ Task DisablePlugin(Plugin plugin);
+
+ ///
+ /// Removes a plugin from the system, optionally running uninstall actions for prerequisites.
+ ///
+ /// The plugin to remove.
+ /// A task representing the asynchronous operation with a boolean indicating success.
+ Task RemovePlugin(Plugin plugin);
+
+ ///
+ /// Removes all settings and configuration data for a plugin, temporarily disabling it during the process.
+ ///
+ /// The plugin whose settings should be cleared.
+ /// A task representing the asynchronous operation with a boolean indicating success.
+ Task RemovePluginSettings(Plugin plugin);
+
+ ///
+ /// Removes the prerequisites for a plugin.
+ ///
+ /// The plugin whose prerequisites should be removed.
+ /// Whether the prerequisites are being removed for a plugin removal.
+ /// A task representing the asynchronous operation with a boolean indicating success.
+ Task RemovePluginPrerequisites(Plugin plugin, bool forPluginRemoval);
+}
\ No newline at end of file
diff --git a/src/Artemis.UI/Services/PluginInteractionService.cs b/src/Artemis.UI/Services/PluginInteractionService.cs
new file mode 100644
index 000000000..2314a2093
--- /dev/null
+++ b/src/Artemis.UI/Services/PluginInteractionService.cs
@@ -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;
+ }
+
+ ///
+ public async Task 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 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;
+ }
+
+ ///
+ public async Task DisablePlugin(Plugin plugin)
+ {
+ try
+ {
+ await Task.Run(() => _pluginManagementService.DisablePlugin(plugin, true));
+ return true;
+ }
+ catch (Exception e)
+ {
+ await ShowPluginToggleFailure(plugin, false, e);
+ }
+
+ return false;
+ }
+
+ ///
+ public async Task 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;
+ }
+
+ ///
+ public async Task 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;
+ }
+
+ ///
+ public async Task 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 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");
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.WebClient.Workshop/BuiltInPluginsMigrator.cs b/src/Artemis.WebClient.Workshop/BuiltInPluginsMigrator.cs
new file mode 100644
index 000000000..e6c29cffd
--- /dev/null
+++ b/src/Artemis.WebClient.Workshop/BuiltInPluginsMigrator.cs
@@ -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 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(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 result = await workshopClient.GetDefaultPlugins.ExecuteAsync(100, null, CancellationToken.None);
+ List 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(), 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;
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/EntryInstallResult.cs b/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/EntryInstallResult.cs
index e3a2ecde4..2f1cca251 100644
--- a/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/EntryInstallResult.cs
+++ b/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/EntryInstallResult.cs
@@ -1,13 +1,31 @@
-using Artemis.WebClient.Workshop.Models;
+using Artemis.Core;
+using Artemis.WebClient.Workshop.Models;
using Artemis.WebClient.Workshop.Services;
namespace Artemis.WebClient.Workshop.Handlers.InstallationHandlers;
public class EntryInstallResult
{
- public bool IsSuccess { get; set; }
- public string? Message { get; set; }
- public InstalledEntry? Entry { get; set; }
+ ///
+ /// Gets a value indicating whether the installation was successful.
+ ///
+ public bool IsSuccess { get; private set; }
+
+ ///
+ /// Gets a message describing the result of the installation.
+ ///
+ public string? Message { get; private set; }
+
+ ///
+ /// Gets the entry that was installed, if any.
+ ///
+ public InstalledEntry? Entry { get; private set; }
+
+ ///
+ /// Gets the result object returned by the installation handler, if any.
+ /// This'll be a , or depending on the entry type.
+ ///
+ public object? Installed { get; private set; }
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
{
IsSuccess = true,
- Entry = installedEntry
+ Entry = installedEntry,
+ Installed = result
};
}
diff --git a/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/LayoutEntryInstallationHandler.cs b/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/LayoutEntryInstallationHandler.cs
index 849b466b5..7636c5def 100644
--- a/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/LayoutEntryInstallationHandler.cs
+++ b/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/LayoutEntryInstallationHandler.cs
@@ -59,7 +59,7 @@ public class LayoutEntryInstallationHandler : IEntryInstallationHandler
{
installedEntry.ApplyRelease(release);
_workshopService.SaveInstalledEntry(installedEntry);
- return EntryInstallResult.FromSuccess(installedEntry);
+ return EntryInstallResult.FromSuccess(installedEntry, layout);
}
// If the layout ended up being invalid yoink it out again, shoooo
diff --git a/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/PluginEntryInstallationHandler.cs b/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/PluginEntryInstallationHandler.cs
index d597eb4e7..b4451d9cf 100644
--- a/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/PluginEntryInstallationHandler.cs
+++ b/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/PluginEntryInstallationHandler.cs
@@ -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 (installedEntry.GetReleaseDirectory(release).Exists)
- return ApplyAndSave(installedEntry, release);
+ return ApplyAndSave(null, installedEntry, release);
}
else
{
@@ -64,7 +64,12 @@ public class PluginEntryInstallationHandler : IEntryInstallationHandler
archive.ExtractToDirectory(releaseDirectory.FullName);
PluginInfo pluginInfo = CoreJson.Deserialize(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
Plugin? currentVersion = _pluginManagementService.GetAllPlugins().FirstOrDefault(p => p.Guid == pluginInfo.Guid);
if (currentVersion != null)
@@ -78,13 +83,12 @@ public class PluginEntryInstallationHandler : IEntryInstallationHandler
}
// Load the plugin, next time during startup this will happen automatically
+ Plugin? plugin = null;
try
{
- Plugin? plugin = _pluginManagementService.LoadPlugin(releaseDirectory);
+ plugin = _pluginManagementService.LoadPlugin(releaseDirectory);
if (plugin == null)
throw new ArtemisWorkshopException("Failed to load plugin, it may be incompatible");
-
- installedEntry.SetMetadata("PluginId", plugin.Guid);
}
catch (Exception e)
{
@@ -102,7 +106,7 @@ public class PluginEntryInstallationHandler : IEntryInstallationHandler
return EntryInstallResult.FromFailure(e.Message);
}
- return ApplyAndSave(installedEntry, release);
+ return ApplyAndSave(plugin, installedEntry, release);
}
public Task UninstallAsync(InstalledEntry installedEntry, CancellationToken cancellationToken)
@@ -133,10 +137,10 @@ public class PluginEntryInstallationHandler : IEntryInstallationHandler
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);
_workshopService.SaveInstalledEntry(installedEntry);
- return EntryInstallResult.FromSuccess(installedEntry);
+ return EntryInstallResult.FromSuccess(installedEntry, plugin);
}
}
\ No newline at end of file
diff --git a/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/ProfileEntryInstallationHandler.cs b/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/ProfileEntryInstallationHandler.cs
index d702681a4..f16aa8d36 100644
--- a/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/ProfileEntryInstallationHandler.cs
+++ b/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/ProfileEntryInstallationHandler.cs
@@ -52,7 +52,7 @@ public class ProfileEntryInstallationHandler : IEntryInstallationHandler
// With everything updated, remove the old profile
_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
UpdateRelease(installedEntry, release);
- return EntryInstallResult.FromSuccess(installedEntry);
+ return EntryInstallResult.FromSuccess(installedEntry, imported);
}
public async Task UninstallAsync(InstalledEntry installedEntry, CancellationToken cancellationToken)
diff --git a/src/Artemis.WebClient.Workshop/Queries/GetEntries.graphql b/src/Artemis.WebClient.Workshop/Queries/GetEntries.graphql
index cdcda0581..10c5502ed 100644
--- a/src/Artemis.WebClient.Workshop/Queries/GetEntries.graphql
+++ b/src/Artemis.WebClient.Workshop/Queries/GetEntries.graphql
@@ -39,4 +39,33 @@ 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
+ }
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Artemis.WebClient.Workshop/Services/Interfaces/IWorkshopService.cs b/src/Artemis.WebClient.Workshop/Services/Interfaces/IWorkshopService.cs
index f6295a9ed..861d23c49 100644
--- a/src/Artemis.WebClient.Workshop/Services/Interfaces/IWorkshopService.cs
+++ b/src/Artemis.WebClient.Workshop/Services/Interfaces/IWorkshopService.cs
@@ -124,7 +124,7 @@ public interface IWorkshopService
///
/// Initializes the workshop service.
///
- void Initialize();
+ Task Initialize();
///
/// Represents the status of the workshop.
@@ -134,6 +134,7 @@ public interface IWorkshopService
public event EventHandler? OnInstalledEntrySaved;
public event EventHandler? OnEntryUninstalled;
public event EventHandler? OnEntryInstalled;
+ public event EventHandler? MigratingBuildInPlugins;
void SetAutoUpdate(InstalledEntry installedEntry, bool autoUpdate);
}
\ No newline at end of file
diff --git a/src/Artemis.WebClient.Workshop/Services/WorkshopService.cs b/src/Artemis.WebClient.Workshop/Services/WorkshopService.cs
index 553eb5f18..68698cec2 100644
--- a/src/Artemis.WebClient.Workshop/Services/WorkshopService.cs
+++ b/src/Artemis.WebClient.Workshop/Services/WorkshopService.cs
@@ -1,6 +1,7 @@
using System.Net.Http.Headers;
using Artemis.Core;
using Artemis.Core.Services;
+using Artemis.Storage.Entities.Plugins;
using Artemis.Storage.Entities.Workshop;
using Artemis.Storage.Repositories.Interfaces;
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.Models;
using Serilog;
+using StrawberryShake;
namespace Artemis.WebClient.Workshop.Services;
@@ -22,15 +24,24 @@ public class WorkshopService : IWorkshopService
private readonly Lazy _pluginManagementService;
private readonly Lazy _profileService;
private readonly EntryInstallationHandlerFactory _factory;
+ private readonly IPluginRepository _pluginRepository;
+ private readonly IWorkshopClient _workshopClient;
+ private readonly PluginSetting _migratedBuiltInPlugins;
+
+
+
private bool _initialized;
public WorkshopService(ILogger logger,
IHttpClientFactory httpClientFactory,
IRouter router,
IEntryRepository entryRepository,
+ ISettingsService settingsService,
Lazy pluginManagementService,
Lazy profileService,
- EntryInstallationHandlerFactory factory)
+ EntryInstallationHandlerFactory factory,
+ IPluginRepository pluginRepository,
+ IWorkshopClient workshopClient)
{
_logger = logger;
_httpClientFactory = httpClientFactory;
@@ -39,6 +50,10 @@ public class WorkshopService : IWorkshopService
_pluginManagementService = pluginManagementService;
_profileService = profileService;
_factory = factory;
+ _pluginRepository = pluginRepository;
+ _workshopClient = workshopClient;
+
+ _migratedBuiltInPlugins = settingsService.GetSetting("Workshop.MigratedBuiltInPlugins", false);
}
public async Task GetEntryIcon(long entryId, CancellationToken cancellationToken)
@@ -166,7 +181,7 @@ public class WorkshopService : IWorkshopService
OnEntryInstalled?.Invoke(this, result.Entry);
else
_logger.Warning("Failed to install entry {Entry}: {Message}", entry, result.Message);
-
+
return result;
}
@@ -227,7 +242,7 @@ public class WorkshopService : IWorkshopService
}
///
- public void Initialize()
+ public async Task Initialize()
{
if (_initialized)
throw new ArtemisWorkshopException("Workshop service is already initialized");
@@ -238,6 +253,7 @@ public class WorkshopService : IWorkshopService
Directory.CreateDirectory(Constants.WorkshopFolder);
RemoveOrphanedFiles();
+ await MigrateBuiltInPlugins();
_pluginManagementService.Value.AdditionalPluginDirectories.AddRange(GetInstalledEntries()
.Where(e => e.EntryType == EntryType.Plugin)
@@ -259,7 +275,7 @@ public class WorkshopService : IWorkshopService
{
if (installedEntry.AutoUpdate == autoUpdate)
return;
-
+
installedEntry.AutoUpdate = autoUpdate;
SaveInstalledEntry(installedEntry);
}
@@ -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)
{
InstalledEntry? entry = GetInstalledEntryByProfile(e.ProfileConfiguration);
@@ -322,4 +351,6 @@ public class WorkshopService : IWorkshopService
public event EventHandler? OnEntryUninstalled;
public event EventHandler? OnEntryInstalled;
+
+ public event EventHandler? MigratingBuildInPlugins;
}
\ No newline at end of file