using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Reflection; using Artemis.Storage.Entities.Plugins; using McMaster.NETCore.Plugins; using Ninject; namespace Artemis.Core { /// /// Represents a plugin /// public class Plugin : CorePropertyChanged, IDisposable { private readonly List _features; private readonly List _profilers; private bool _isEnabled; internal Plugin(PluginInfo info, DirectoryInfo directory, PluginEntity? pluginEntity) { Info = info; Directory = directory; Entity = pluginEntity ?? new PluginEntity {Id = Guid, IsEnabled = true}; Info.Plugin = this; _features = new List(); _profilers = new List(); Features = new(_features); Profilers = new(_profilers); } /// /// Gets the plugin GUID /// public Guid Guid => Info.Guid; /// /// Gets the plugin info related to this plugin /// public PluginInfo Info { get; } /// /// The plugins root directory /// public DirectoryInfo Directory { get; } /// /// Gets or sets a configuration dialog for this plugin that is accessible in the UI under Settings > Plugins /// public IPluginConfigurationDialog? ConfigurationDialog { get; set; } /// /// Indicates whether the user enabled the plugin or not /// public bool IsEnabled { get => _isEnabled; private set => SetAndNotify(ref _isEnabled, value); } /// /// Gets a read-only collection of all features this plugin provides /// public ReadOnlyCollection Features { get; } /// /// Gets a read-only collection of profiles running on the plugin /// public ReadOnlyCollection Profilers { get; } /// /// The assembly the plugin code lives in /// public Assembly? Assembly { get; internal set; } /// /// Gets the plugin bootstrapper /// public PluginBootstrapper? Bootstrapper { get; internal set; } /// /// The Ninject kernel of the plugin /// public IKernel? Kernel { get; internal set; } /// /// The PluginLoader backing this plugin /// internal PluginLoader? PluginLoader { get; set; } /// /// The entity representing the plugin /// 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 /// /// The path to resolve /// An absolute path pointing to the provided relative path [return: NotNullIfNotNull("path")] public string? ResolveRelativePath(string? path) { return path == null ? null : Path.Combine(Directory.FullName, path); } /// /// Looks up the instance of the feature of type /// /// The type of feature to find /// If found, the instance of the feature public T? GetFeature() where T : PluginFeature { return _features.FirstOrDefault(i => i.Instance is T)?.Instance as T; } /// /// Looks up the feature info the feature of type /// /// The type of feature to find /// Feature info of the feature public PluginFeatureInfo GetFeatureInfo() where T : PluginFeature { // This should be a safe assumption because any type of PluginFeature is registered and added return _features.First(i => i.FeatureType == typeof(T)); } /// /// Gets a profiler with the provided , if it does not yet exist it will be created. /// /// The name of the profiler /// A new or existing profiler with the provided public Profiler GetProfiler(string name) { Profiler? profiler = _profilers.FirstOrDefault(p => p.Name == name); if (profiler != null) return profiler; profiler = new Profiler(this, name); _profilers.Add(profiler); return profiler; } /// /// Removes a profiler from the plugin /// /// The profiler to remove public void RemoveProfiler(Profiler profiler) { _profilers.Remove(profiler); } /// /// Gets an instance of the specified service using the plugins dependency injection container. /// Note: To use parameters reference Ninject and use directly. /// /// The service to resolve. /// An instance of the service. public T Get() { if (Kernel == null) throw new ArtemisPluginException("Cannot use Get before the plugin finished loading"); return Kernel.Get(); } /// public override string ToString() { 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; Entity.IsEnabled = IsEnabled; } internal void AddFeature(PluginFeatureInfo featureInfo) { if (featureInfo.Plugin != this) throw new ArtemisCoreException("Feature is not associated with this plugin"); _features.Add(featureInfo); OnFeatureAdded(new PluginFeatureInfoEventArgs(featureInfo)); } internal void RemoveFeature(PluginFeatureInfo featureInfo) { if (featureInfo.Instance != null && featureInfo.Instance.IsEnabled) throw new ArtemisCoreException("Cannot remove an enabled feature from a plugin"); _features.Remove(featureInfo); featureInfo.Instance?.Dispose(); OnFeatureRemoved(new PluginFeatureInfoEventArgs(featureInfo)); } internal void SetEnabled(bool enable) { if (IsEnabled == enable) return; if (!enable && Features.Any(e => e.Instance != null && e.Instance.IsEnabled)) throw new ArtemisCoreException("Cannot disable this plugin because it still has enabled features"); IsEnabled = enable; if (enable) { Bootstrapper?.OnPluginEnabled(this); OnEnabled(); } else { Bootstrapper?.OnPluginDisabled(this); OnDisabled(); } } internal bool HasEnabledFeatures() { return Entity.Features.Any(f => f.IsEnabled) || Features.Any(f => f.AlwaysEnabled); } /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } } }