diff --git a/ci/azure-pipelines.yml b/ci/azure-pipelines.yml index 2f40282d0..8e16ec412 100644 --- a/ci/azure-pipelines.yml +++ b/ci/azure-pipelines.yml @@ -53,7 +53,7 @@ steps: command: 'publish' publishWebProjects: false projects: '$(artemisSolution)' - arguments: '--runtime win-x64 --self-contained false --output $(Build.ArtifactStagingDirectory)/build /nowarn:cs1591' + arguments: '--runtime win-x64 --self-contained false --configuration Release --output $(Build.ArtifactStagingDirectory)/build /nowarn:cs1591' zipAfterPublish: false modifyOutputPath: false @@ -73,12 +73,13 @@ steps: fileType: 'json' targetFiles: '**/buildinfo.json' +# Copy Artemis binaries to where plugin projects expect them - task: CopyFiles@2 displayName: 'Plugins - Prepare Artemis binaries' inputs: SourceFolder: '$(Build.ArtifactStagingDirectory)/build' Contents: '**' - TargetFolder: 'Artemis/src/Artemis.UI/bin/x64/Debug/net5.0-windows' + TargetFolder: 'Artemis/src/Artemis.UI/bin/net5.0-windows' - task: PowerShell@2 displayName: 'Plugins - Insert build number into plugin.json' @@ -99,7 +100,7 @@ steps: inputs: command: 'publish' publishWebProjects: false - arguments: '--runtime win-x64 --self-contained false --output $(Build.ArtifactStagingDirectory)/build/Plugins' + arguments: '--runtime win-x64 --configuration Release --self-contained false --output $(Build.ArtifactStagingDirectory)/build/Plugins' projects: '$(pluginProjects)' zipAfterPublish: true diff --git a/src/Artemis.Core/Ninject/CoreModule.cs b/src/Artemis.Core/Ninject/CoreModule.cs index 5408951de..b3dee7af1 100644 --- a/src/Artemis.Core/Ninject/CoreModule.cs +++ b/src/Artemis.Core/Ninject/CoreModule.cs @@ -1,4 +1,7 @@ -using System.IO; +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; using Artemis.Core.Services; using Artemis.Storage; using Artemis.Storage.Migrations.Interfaces; diff --git a/src/Artemis.Core/Ninject/LoggerProvider.cs b/src/Artemis.Core/Ninject/LoggerProvider.cs index fe2aaf5b6..713d8d835 100644 --- a/src/Artemis.Core/Ninject/LoggerProvider.cs +++ b/src/Artemis.Core/Ninject/LoggerProvider.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using Ninject.Activation; using Serilog; using Serilog.Core; diff --git a/src/Artemis.Core/Plugins/Plugin.cs b/src/Artemis.Core/Plugins/Plugin.cs index 0b4379a09..da4e3f7aa 100644 --- a/src/Artemis.Core/Plugins/Plugin.cs +++ b/src/Artemis.Core/Plugins/Plugin.cs @@ -88,6 +88,11 @@ namespace Artemis.Core /// internal PluginEntity Entity { get; set; } + /// + /// Populated when plugin settings are first loaded + /// + internal PluginSettings? Settings { get; set; } + /// /// Resolves the relative path provided in the parameter to an absolute path /// @@ -101,7 +106,6 @@ namespace Artemis.Core /// /// Looks up the instance of the feature of type - /// Note: This method only returns instances of enabled features /// /// The type of feature to find /// If found, the instance of the feature @@ -116,6 +120,83 @@ namespace Artemis.Core return Info.ToString(); } + /// + /// Occurs when the plugin is enabled + /// + public event EventHandler? Enabled; + + /// + /// Occurs when the plugin is disabled + /// + public event EventHandler? Disabled; + + /// + /// Occurs when an feature is loaded and added to the plugin + /// + public event EventHandler? FeatureAdded; + + /// + /// Occurs when an feature is disabled and removed from the plugin + /// + public event EventHandler? FeatureRemoved; + + /// + /// Releases the unmanaged resources used by the object and optionally releases the managed resources. + /// + /// + /// to release both managed and unmanaged resources; + /// to release only unmanaged resources. + /// + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + foreach (PluginFeatureInfo feature in Features) + feature.Instance?.Dispose(); + SetEnabled(false); + + Kernel?.Dispose(); + PluginLoader?.Dispose(); + + GC.Collect(); + GC.WaitForPendingFinalizers(); + + _features.Clear(); + } + } + + /// + /// Invokes the Enabled event + /// + protected virtual void OnEnabled() + { + Enabled?.Invoke(this, EventArgs.Empty); + } + + /// + /// Invokes the Disabled event + /// + protected virtual void OnDisabled() + { + Disabled?.Invoke(this, EventArgs.Empty); + } + + /// + /// Invokes the FeatureAdded event + /// + protected virtual void OnFeatureAdded(PluginFeatureInfoEventArgs e) + { + FeatureAdded?.Invoke(this, e); + } + + /// + /// Invokes the FeatureRemoved event + /// + protected virtual void OnFeatureRemoved(PluginFeatureInfoEventArgs e) + { + FeatureRemoved?.Invoke(this, e); + } + internal void ApplyToEntity() { Entity.Id = Guid; @@ -169,96 +250,11 @@ namespace Artemis.Core return Entity.Features.Any(f => f.IsEnabled) || Features.Any(f => f.AlwaysEnabled); } - #region IDisposable - - /// - /// Releases the unmanaged resources used by the object and optionally releases the managed resources. - /// - /// - /// to release both managed and unmanaged resources; - /// to release only unmanaged resources. - /// - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - foreach (PluginFeatureInfo feature in Features) - feature.Instance?.Dispose(); - SetEnabled(false); - - Kernel?.Dispose(); - PluginLoader?.Dispose(); - - GC.Collect(); - GC.WaitForPendingFinalizers(); - - _features.Clear(); - } - } - /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } - - #endregion - - #region Events - - /// - /// Occurs when the plugin is enabled - /// - public event EventHandler? Enabled; - - /// - /// Occurs when the plugin is disabled - /// - public event EventHandler? Disabled; - - /// - /// Occurs when an feature is loaded and added to the plugin - /// - public event EventHandler? FeatureAdded; - - /// - /// Occurs when an feature is disabled and removed from the plugin - /// - public event EventHandler? FeatureRemoved; - - /// - /// Invokes the Enabled event - /// - protected virtual void OnEnabled() - { - Enabled?.Invoke(this, EventArgs.Empty); - } - - /// - /// Invokes the Disabled event - /// - protected virtual void OnDisabled() - { - Disabled?.Invoke(this, EventArgs.Empty); - } - - /// - /// Invokes the FeatureAdded event - /// - protected virtual void OnFeatureAdded(PluginFeatureInfoEventArgs e) - { - FeatureAdded?.Invoke(this, e); - } - - /// - /// Invokes the FeatureRemoved event - /// - protected virtual void OnFeatureRemoved(PluginFeatureInfoEventArgs e) - { - FeatureRemoved?.Invoke(this, e); - } - - #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/Settings/PluginSettings.cs b/src/Artemis.Core/Plugins/Settings/PluginSettings.cs index 03ee46316..1c34b58d2 100644 --- a/src/Artemis.Core/Plugins/Settings/PluginSettings.cs +++ b/src/Artemis.Core/Plugins/Settings/PluginSettings.cs @@ -17,6 +17,8 @@ namespace Artemis.Core internal PluginSettings(Plugin plugin, IPluginRepository pluginRepository) { Plugin = plugin; + Plugin.Settings = this; + _pluginRepository = pluginRepository; _settingEntities = new Dictionary(); } @@ -65,5 +67,10 @@ namespace Artemis.Core return pluginSetting; } } + + internal void ClearSettings() + { + _settingEntities.Clear(); + } } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs b/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs index 7643c93ae..3791ae838 100644 --- a/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs +++ b/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs @@ -74,7 +74,14 @@ namespace Artemis.Core.Services /// Unloads and permanently removes the provided plugin /// /// The plugin to remove - void RemovePlugin(Plugin plugin); + /// + void RemovePlugin(Plugin plugin, bool removeSettings); + + /// + /// Removes the settings of a disabled plugin + /// + /// The plugin whose settings to remove + void RemovePluginSettings(Plugin plugin); /// /// Enables the provided plugin feature @@ -134,8 +141,6 @@ namespace Artemis.Core.Services /// The action to take void QueuePluginAction(Plugin plugin, PluginManagementAction pluginAction); - #region Events - /// /// Occurs when built-in plugins are being loaded /// @@ -190,7 +195,5 @@ namespace Artemis.Core.Services /// Occurs when a plugin feature has been disabled /// public event EventHandler PluginFeatureDisabled; - - #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs index 7840660ac..e0c16e1e2 100644 --- a/src/Artemis.Core/Services/PluginManagementService.cs +++ b/src/Artemis.Core/Services/PluginManagementService.cs @@ -479,7 +479,7 @@ namespace Artemis.Core.Services if (existing != null) try { - RemovePlugin(existing); + RemovePlugin(existing, false); } catch (Exception e) { @@ -519,7 +519,7 @@ namespace Artemis.Core.Services return LoadPlugin(directoryInfo); } - public void RemovePlugin(Plugin plugin) + public void RemovePlugin(Plugin plugin, bool removeSettings) { DirectoryInfo directory = plugin.Directory; lock (_plugins) @@ -529,6 +529,16 @@ namespace Artemis.Core.Services } directory.Delete(true); + if (removeSettings) + RemovePluginSettings(plugin); + } + + public void RemovePluginSettings(Plugin plugin) + { + if (plugin.IsEnabled) + throw new ArtemisCoreException("Cannot remove the settings of an enabled plugin"); + _pluginRepository.RemoveSettings(plugin.Guid); + plugin.Settings?.ClearSettings(); } #endregion diff --git a/src/Artemis.Storage/Repositories/Interfaces/IPluginRepository.cs b/src/Artemis.Storage/Repositories/Interfaces/IPluginRepository.cs index d93ba4628..602eb2572 100644 --- a/src/Artemis.Storage/Repositories/Interfaces/IPluginRepository.cs +++ b/src/Artemis.Storage/Repositories/Interfaces/IPluginRepository.cs @@ -14,7 +14,8 @@ namespace Artemis.Storage.Repositories.Interfaces PluginSettingEntity GetSettingByGuid(Guid pluginGuid); PluginSettingEntity GetSettingByNameAndGuid(string name, Guid pluginGuid); void SaveSetting(PluginSettingEntity pluginSettingEntity); - + void RemoveSettings(Guid pluginGuid); + void AddQueuedAction(PluginQueuedActionEntity pluginQueuedActionEntity); List GetQueuedActions(); List GetQueuedActions(Guid pluginGuid); diff --git a/src/Artemis.Storage/Repositories/PluginRepository.cs b/src/Artemis.Storage/Repositories/PluginRepository.cs index 5a923a60a..db67f0c91 100644 --- a/src/Artemis.Storage/Repositories/PluginRepository.cs +++ b/src/Artemis.Storage/Repositories/PluginRepository.cs @@ -54,6 +54,12 @@ namespace Artemis.Storage.Repositories _repository.Upsert(pluginSettingEntity); } + /// + public void RemoveSettings(Guid pluginGuid) + { + _repository.DeleteMany(s => s.PluginGuid == pluginGuid); + } + public List GetQueuedActions() { return _repository.Query().ToList(); diff --git a/src/Artemis.UI/ApplicationStateManager.cs b/src/Artemis.UI/ApplicationStateManager.cs index d07a0aa17..e2f1100a8 100644 --- a/src/Artemis.UI/ApplicationStateManager.cs +++ b/src/Artemis.UI/ApplicationStateManager.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using System.Windows; using Artemis.Core; using Artemis.UI.Utilities; +using Ninject; using Stylet; namespace Artemis.UI @@ -19,13 +20,16 @@ namespace Artemis.UI // ReSharper disable once NotAccessedField.Local - Kept in scope to ensure it does not get released private Mutex _artemisMutex; - public ApplicationStateManager(string[] startupArguments) + public ApplicationStateManager(IKernel kernel, string[] startupArguments) { StartupArguments = startupArguments; IsElevated = new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator); Core.Utilities.ShutdownRequested += UtilitiesOnShutdownRequested; Core.Utilities.RestartRequested += UtilitiesOnRestartRequested; + + // On Windows shutdown dispose the kernel just so device providers get a chance to clean up + Application.Current.SessionEnding += (_, _) => kernel.Dispose(); } public string[] StartupArguments { get; } diff --git a/src/Artemis.UI/Bootstrapper.cs b/src/Artemis.UI/Bootstrapper.cs index 888851231..471d56412 100644 --- a/src/Artemis.UI/Bootstrapper.cs +++ b/src/Artemis.UI/Bootstrapper.cs @@ -6,7 +6,6 @@ using System.Threading.Tasks; using System.Windows; using System.Windows.Markup; using System.Windows.Threading; -using Artemis.Core; using Artemis.Core.Ninject; using Artemis.Core.Services; using Artemis.UI.Ninject; @@ -14,11 +13,9 @@ using Artemis.UI.Screens; using Artemis.UI.Services; using Artemis.UI.Shared; using Artemis.UI.Shared.Services; -using Artemis.UI.SkiaSharp; using Artemis.UI.Stylet; using Ninject; using Serilog; -using SkiaSharp; using Stylet; namespace Artemis.UI @@ -39,7 +36,7 @@ namespace Artemis.UI protected override void Launch() { - _applicationStateManager = new ApplicationStateManager(Args); + _applicationStateManager = new ApplicationStateManager(Kernel, Args); Core.Utilities.PrepareFirstLaunch(); ILogger logger = Kernel.Get(); @@ -94,10 +91,7 @@ namespace Artemis.UI registrationService.RegisterInputProvider(); registrationService.RegisterControllers(); - Execute.OnUIThreadSync(() => - { - registrationService.ApplyPreferredGraphicsContext(); - }); + Execute.OnUIThreadSync(() => { registrationService.ApplyPreferredGraphicsContext(); }); // Initialize background services Kernel.Get(); diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml index 2060c520c..5055b02ac 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml @@ -17,8 +17,8 @@ - + @@ -42,15 +42,20 @@ - + - + diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsView.xaml b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsView.xaml index 92bb2ad41..9948c56b9 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsView.xaml +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsView.xaml @@ -12,7 +12,7 @@ d:DataContext="{d:DesignInstance devices:PluginSettingsViewModel}" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> - + @@ -47,7 +47,7 @@ behaviors:HighlightTermBehavior.TermToBeHighlighted="{Binding Parent.SearchPluginInput}" behaviors:HighlightTermBehavior.Text="{Binding Plugin.Info.Name}" behaviors:HighlightTermBehavior.HighlightForeground="{StaticResource Primary600Foreground}" - behaviors:HighlightTermBehavior.HighlightBackground="{StaticResource Primary600}"/> + behaviors:HighlightTermBehavior.HighlightBackground="{StaticResource Primary600}" /> +