Plugins - Implemented features

Core - Removed Stylet dependency for #500
This commit is contained in:
SpoinkyNL committed 2020-11-11 23:53:52 +01:00
1 parent b56966c585
commit e812929215
139 files changed
+1635 -1106

No files matched your search

@@ -14,10 +14,10 @@ namespace Artemis.Core.DataModelExpansions
private readonly Dictionary<string, DataModel> _dynamicDataModels = new Dictionary<string, DataModel>();
/// <summary>
/// Gets the plugin implementation this data model belongs to
/// Gets the plugin feature this data model belongs to
/// </summary>
[DataModelIgnore]
public DataModelPluginImplementation Implementation { get; internal set; }
public DataModelPluginFeature Feature { get; internal set; }
/// <summary>
/// Gets the <see cref="DataModelPropertyAttribute" /> describing this data model
@@ -43,9 +43,9 @@ namespace Artemis.Core.DataModelExpansions
/// <returns></returns>
public ReadOnlyCollection<PropertyInfo> GetHiddenProperties()
{
if (Implementation is ProfileModule profileModule)
if (Feature is ProfileModule profileModule)
return profileModule.HiddenProperties;
if (Implementation is BaseDataModelExpansion dataModelExpansion)
if (Feature is BaseDataModelExpansion dataModelExpansion)
return dataModelExpansion.HiddenProperties;
return new List<PropertyInfo>().AsReadOnly();
@@ -81,7 +81,7 @@ namespace Artemis.Core.DataModelExpansions
throw new ArtemisCoreException($"Cannot add a dynamic data model with key '{key}' " +
"because the key is already in use by a static property on this data model.");
dynamicDataModel.Implementation = Implementation;
dynamicDataModel.Feature = Feature;
dynamicDataModel.DataModelDescription = new DataModelPropertyAttribute
{
Name = string.IsNullOrWhiteSpace(name) ? key.Humanize() : name,
@@ -45,7 +45,7 @@ namespace Artemis.Core.DataModelExpansions
internal override void InternalEnable()
{
DataModel = Activator.CreateInstance<T>();
DataModel.Implementation = PluginInfo;
DataModel.Feature = this;
DataModel.DataModelDescription = GetDataModelDescription();
base.InternalEnable();
}
@@ -8,7 +8,7 @@ namespace Artemis.Core.DataModelExpansions
/// For internal use only, to implement your own layer property type, extend <see cref="DataModelExpansion{T}" />
/// instead.
/// </summary>
public abstract class BaseDataModelExpansion : DataModelPluginImplementation
public abstract class BaseDataModelExpansion : DataModelPluginFeature
{
/// <summary>
/// Gets a list of all properties ignored at runtime using <c>IgnoreProperty(x => x.y)</c>
@@ -35,7 +35,7 @@ namespace Artemis.Core.DataModelExpansions
/// <returns></returns>
public virtual DataModelPropertyAttribute GetDataModelDescription()
{
return new DataModelPropertyAttribute {Name = PluginInfo.Name, Description = PluginInfo.Description};
return new DataModelPropertyAttribute {Name = Plugin.Info.Name, Description = Plugin.Info.Description};
}
}
}
@@ -4,9 +4,9 @@ using System.Threading.Tasks;
namespace Artemis.Core
{
/// <summary>
/// Represents an implementation of a certain type provided by a plugin with support for data models
/// Represents an feature of a certain type provided by a plugin with support for data models
/// </summary>
public abstract class DataModelPluginImplementation : PluginImplementation
public abstract class DataModelPluginFeature : PluginFeature
{
/// <summary>
/// Registers a timed update that whenever the plugin is enabled calls the provided <paramref name="action" /> at the
@@ -23,11 +23,11 @@ namespace Artemis.Core
{
if (action == null)
throw new ArgumentNullException(nameof(action));
return new TimedUpdateRegistration(PluginInfo, interval, action);
return new TimedUpdateRegistration(this, interval, action);
}
/// <summary>
/// Registers a timed update that whenever the plugin is enabled calls the provided <paramref name="action" /> at the
/// Registers a timed update that whenever the plugin is enabled calls the provided <paramref name="asyncAction" /> at the
/// provided
/// <paramref name="interval" />
/// </summary>
@@ -41,7 +41,7 @@ namespace Artemis.Core
{
if (asyncAction == null)
throw new ArgumentNullException(nameof(asyncAction));
return new TimedUpdateRegistration(PluginInfo, interval, asyncAction);
return new TimedUpdateRegistration(this, interval, asyncAction);
}
}
}
@@ -10,7 +10,7 @@ namespace Artemis.Core.DeviceProviders
/// <summary>
/// Allows you to implement and register your own device provider
/// </summary>
public abstract class DeviceProvider : PluginImplementation
public abstract class DeviceProvider : PluginFeature
{
/// <summary>
/// Creates a new instance of the <see cref="DeviceProvider" /> class
@@ -50,9 +50,9 @@ namespace Artemis.Core.DeviceProviders
{
// Start from the plugin directory
if (e.RelativePart != null && e.FileName != null)
e.FinalPath = Path.Combine(PluginInfo.Directory.FullName, e.RelativePart, e.FileName);
e.FinalPath = Path.Combine(Plugin.Directory.FullName, e.RelativePart, e.FileName);
else if (e.RelativePath != null)
e.FinalPath = Path.Combine(PluginInfo.Directory.FullName, e.RelativePath);
e.FinalPath = Path.Combine(Plugin.Directory.FullName, e.RelativePath);
IRGBDeviceInfo deviceInfo = ((IRGBDevice) sender).DeviceInfo;
if (e.FileName != null && !File.Exists(e.FinalPath))
@@ -0,0 +1,20 @@
namespace Artemis.Core
{
/// <summary>
/// An optional entry point for your plugin
/// </summary>
public interface IPluginBootstrapper
{
/// <summary>
/// Called when the plugin is activated
/// </summary>
/// <param name="plugin">The plugin instance of your plugin</param>
void Enable(Plugin plugin);
/// <summary>
/// Called when the plugin is deactivated or when Artemis shuts down
/// </summary>
/// <param name="plugin">The plugin instance of your plugin</param>
void Disable(Plugin plugin);
}
}
@@ -0,0 +1,7 @@
namespace Artemis.Core
{
public interface IPluginConfigurationDialog
{
}
}
@@ -0,0 +1,10 @@
namespace Artemis.Core
{
/// <summary>
/// Represents a view model for a plugin configuration window
/// </summary>
public interface IPluginConfigurationViewModel
{
}
}
@@ -1,34 +0,0 @@
using Stylet;
namespace Artemis.Core.LayerBrushes
{
/// <summary>
/// Represents a view model for a brush configuration window
/// </summary>
public abstract class BrushConfigurationViewModel : Screen
{
/// <summary>
/// Creates a new instance of the <see cref="BrushConfigurationViewModel" /> class
/// </summary>
/// <param name="layerBrush"></param>
protected BrushConfigurationViewModel(BaseLayerBrush layerBrush)
{
LayerBrush = layerBrush;
}
/// <summary>
/// Creates a new instance of the <see cref="BrushConfigurationViewModel" /> class with a validator
/// </summary>
/// <param name="layerBrush"></param>
/// <param name="validator"></param>
protected BrushConfigurationViewModel(BaseLayerBrush layerBrush, IModelValidator validator) : base(validator)
{
LayerBrush = layerBrush;
}
/// <summary>
/// Gets the layer brush this view model is associated with
/// </summary>
public BaseLayerBrush LayerBrush { get; }
}
}
@@ -0,0 +1,9 @@
namespace Artemis.Core.LayerBrushes
{
/// <summary>
/// Represents the configuration dialog of a layer brush
/// </summary>
public interface ILayerBrushConfigurationDialog
{
}
}
@@ -1,17 +1,15 @@
using System;
using Artemis.Core.Services;
using SkiaSharp;
using Stylet;
namespace Artemis.Core.LayerBrushes
{
/// <summary>
/// For internal use only, please use <see cref="LayerBrush{T}" /> or <see cref="RgbNetLayerBrush{T}" /> or instead
/// </summary>
public abstract class BaseLayerBrush : PropertyChangedBase, IDisposable
public abstract class BaseLayerBrush : CorePropertyChanged, IDisposable
{
private LayerBrushType _brushType;
private LayerBrushConfigurationDialog _configurationDialog;
private ILayerBrushConfigurationDialog _configurationDialog;
private LayerBrushDescriptor _descriptor;
private Layer _layer;
private bool _supportsTransformation = true;
@@ -37,7 +35,7 @@ namespace Artemis.Core.LayerBrushes
/// <summary>
/// Gets or sets a configuration dialog complementing the regular properties
/// </summary>
public LayerBrushConfigurationDialog ConfigurationDialog
public ILayerBrushConfigurationDialog ConfigurationDialog
{
get => _configurationDialog;
protected set => SetAndNotify(ref _configurationDialog, value);
@@ -53,9 +51,9 @@ namespace Artemis.Core.LayerBrushes
}
/// <summary>
/// Gets the plugin info that defined this brush
/// Gets the ID of the <see cref="LayerBrushProvider" /> that provided this effect
/// </summary>
public PluginInfo PluginInfo => Descriptor.LayerBrushProvider.PluginInfo;
public string ProviderId => Descriptor?.Provider?.Id;
/// <summary>
/// Gets a reference to the layer property group without knowing it's type
@@ -72,21 +70,11 @@ namespace Artemis.Core.LayerBrushes
protected set
{
if (value && BrushType == LayerBrushType.RgbNet)
throw new ArtemisPluginException(PluginInfo, "An RGB.NET brush cannot support transformation");
throw new ArtemisPluginFeatureException(Descriptor.Provider, "An RGB.NET brush cannot support transformation");
_supportsTransformation = value;
}
}
/// <inheritdoc />
public void Dispose()
{
DisableLayerBrush();
BaseProperties.Dispose();
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Called when the layer brush is activated
/// </summary>
@@ -112,6 +100,16 @@ namespace Artemis.Core.LayerBrushes
internal virtual void Dispose(bool disposing)
{
}
/// <inheritdoc />
public void Dispose()
{
DisableLayerBrush();
BaseProperties.Dispose();
Dispose(true);
GC.SuppressFinalize(this);
}
}
/// <summary>
@@ -37,7 +37,7 @@ namespace Artemis.Core.LayerBrushes
Properties = Activator.CreateInstance<T>();
Properties.GroupDescription ??= new PropertyGroupDescriptionAttribute {Name = Descriptor.DisplayName, Description = Descriptor.Description};
Properties.LayerBrush = this;
Properties.Initialize(Layer, "LayerBrush.", PluginInfo);
Properties.Initialize(Layer, "LayerBrush.", Descriptor.Provider);
PropertiesInitialized = true;
EnableLayerBrush();
@@ -1,27 +0,0 @@
using System;
namespace Artemis.Core.LayerBrushes
{
/// <inheritdoc />
public class LayerBrushConfigurationDialog<T> : LayerBrushConfigurationDialog where T : BrushConfigurationViewModel
{
/// <inheritdoc />
public override Type Type => typeof(T);
}
/// <summary>
/// Describes a UI tab for a layer brush
/// </summary>
public abstract class LayerBrushConfigurationDialog
{
/// <summary>
/// The layer brush this dialog belongs to
/// </summary>
internal BaseLayerBrush LayerBrush { get; set; }
/// <summary>
/// The type of view model the tab contains
/// </summary>
public abstract Type Type { get; }
}
}
@@ -1,5 +1,4 @@
using System;
using Artemis.Core.Services;
using Ninject;
namespace Artemis.Core.LayerBrushes
@@ -9,13 +8,13 @@ namespace Artemis.Core.LayerBrushes
/// </summary>
public class LayerBrushDescriptor
{
internal LayerBrushDescriptor(string displayName, string description, string icon, Type layerBrushType, LayerBrushProvider layerBrushProvider)
internal LayerBrushDescriptor(string displayName, string description, string icon, Type layerBrushType, LayerBrushProvider provider)
{
DisplayName = displayName;
Description = description;
Icon = icon;
LayerBrushType = layerBrushType;
LayerBrushProvider = layerBrushProvider;
Provider = provider;
}
/// <summary>
@@ -42,7 +41,7 @@ namespace Artemis.Core.LayerBrushes
/// <summary>
/// The plugin that provided this <see cref="LayerBrushDescriptor" />
/// </summary>
public LayerBrushProvider LayerBrushProvider { get; }
public LayerBrushProvider Provider { get; }
/// <summary>
/// Determines whether the provided <paramref name="reference" /> references to a brush provided by this descriptor
@@ -51,7 +50,8 @@ namespace Artemis.Core.LayerBrushes
{
if (reference == null)
return false;
return LayerBrushProvider.PluginInfo.Guid == reference.BrushPluginGuid && LayerBrushType.Name == reference.BrushType;
return Provider.Id == reference.LayerBrushProviderId && LayerBrushType.Name == reference.BrushType;
}
/// <summary>
@@ -62,7 +62,7 @@ namespace Artemis.Core.LayerBrushes
if (layer.LayerBrush != null)
throw new ArtemisCoreException("Layer already has an instantiated layer brush");
BaseLayerBrush brush = (BaseLayerBrush) LayerBrushProvider.PluginInfo.Kernel.Get(LayerBrushType);
BaseLayerBrush brush = (BaseLayerBrush) Provider.Plugin.Kernel!.Get(LayerBrushType);
brush.Layer = layer;
brush.Descriptor = this;
brush.Initialize();
@@ -7,7 +7,7 @@ namespace Artemis.Core.LayerBrushes
/// <summary>
/// Allows you to create one or more <see cref="LayerBrush{T}" />s usable by profile layers.
/// </summary>
public abstract class LayerBrushProvider : PluginImplementation
public abstract class LayerBrushProvider : PluginFeature
{
private readonly List<LayerBrushDescriptor> _layerBrushDescriptors;
@@ -39,7 +39,7 @@ namespace Artemis.Core.LayerBrushes
protected void RegisterLayerBrushDescriptor<T>(string displayName, string description, string icon) where T : BaseLayerBrush
{
if (!IsEnabled)
throw new ArtemisPluginException(PluginInfo, "Can only add a layer brush descriptor when the plugin is enabled");
throw new ArtemisPluginException(Plugin, "Can only add a layer brush descriptor when the plugin is enabled");
LayerBrushDescriptor descriptor = new LayerBrushDescriptor(displayName, description, icon, typeof(T), this);
_layerBrushDescriptors.Add(descriptor);
@@ -1,34 +0,0 @@
using Stylet;
namespace Artemis.Core.LayerEffects
{
/// <summary>
/// Represents a view model for an effect configuration window
/// </summary>
public abstract class EffectConfigurationViewModel : Screen
{
/// <summary>
/// Creates a new instance of the <see cref="EffectConfigurationViewModel" /> class
/// </summary>
/// <param name="layerEffect"></param>
protected EffectConfigurationViewModel(BaseLayerEffect layerEffect)
{
LayerEffect = layerEffect;
}
/// <summary>
/// Creates a new instance of the <see cref="EffectConfigurationViewModel" /> class with a validator
/// </summary>
/// <param name="layerEffect"></param>
/// <param name="validator"></param>
protected EffectConfigurationViewModel(BaseLayerEffect layerEffect, IModelValidator validator) : base(validator)
{
LayerEffect = layerEffect;
}
/// <summary>
/// Gets the layer effect this view model is associated with
/// </summary>
public BaseLayerEffect LayerEffect { get; }
}
}
@@ -0,0 +1,6 @@
namespace Artemis.Core.LayerEffects
{
public interface IEffectConfigurationViewModel
{
}
}
@@ -0,0 +1,6 @@
namespace Artemis.Core.LayerEffects
{
public interface ILayerEffectConfigurationDialog
{
}
}
@@ -1,16 +1,15 @@
using System;
using Artemis.Core.Services;
using SkiaSharp;
using Stylet;
namespace Artemis.Core.LayerEffects
{
/// <summary>
/// For internal use only, please use <see cref="LayerEffect{T}" /> instead
/// </summary>
public abstract class BaseLayerEffect : PropertyChangedBase, IDisposable
public abstract class BaseLayerEffect : CorePropertyChanged, IDisposable
{
private LayerEffectConfigurationDialog _configurationDialog;
private ILayerEffectConfigurationDialog _configurationDialog;
private LayerEffectDescriptor _descriptor;
private bool _enabled;
private Guid _entityId;
@@ -77,7 +76,7 @@ namespace Artemis.Core.LayerEffects
/// <summary>
/// Gets the <see cref="LayerEffectDescriptor" /> that registered this effect
/// </summary>
public LayerEffectDescriptor Descriptor
public LayerEffectDescriptor? Descriptor
{
get => _descriptor;
internal set => SetAndNotify(ref _descriptor, value);
@@ -86,16 +85,16 @@ namespace Artemis.Core.LayerEffects
/// <summary>
/// Gets or sets a configuration dialog complementing the regular properties
/// </summary>
public LayerEffectConfigurationDialog ConfigurationDialog
public ILayerEffectConfigurationDialog ConfigurationDialog
{
get => _configurationDialog;
protected set => SetAndNotify(ref _configurationDialog, value);
}
/// <summary>
/// Gets the plugin info that defined this effect
/// Gets the ID of the <see cref="LayerEffectProvider"/> that provided this effect
/// </summary>
public PluginInfo PluginInfo => Descriptor.LayerEffectProvider?.PluginInfo;
public string ProviderId => Descriptor?.Provider?.Id;
/// <summary>
/// Gets a reference to the layer property group without knowing it's type
@@ -37,7 +37,7 @@ namespace Artemis.Core.LayerEffects
{
Properties = Activator.CreateInstance<T>();
Properties.LayerEffect = this;
Properties.Initialize(ProfileElement, PropertyRootPath, PluginInfo);
Properties.Initialize(ProfileElement, PropertyRootPath, Descriptor.Provider);
PropertiesInitialized = true;
EnableLayerEffect();
@@ -1,27 +0,0 @@
using System;
namespace Artemis.Core.LayerEffects
{
/// <inheritdoc />
public class LayerEffectConfigurationDialog<T> : LayerEffectConfigurationDialog where T : EffectConfigurationViewModel
{
/// <inheritdoc />
public override Type Type => typeof(T);
}
/// <summary>
/// Describes a UI tab for a specific layer effect
/// </summary>
public abstract class LayerEffectConfigurationDialog
{
/// <summary>
/// The layer effect this dialog belongs to
/// </summary>
internal BaseLayerEffect LayerEffect { get; set; }
/// <summary>
/// The type of view model the tab contains
/// </summary>
public abstract Type Type { get; }
}
}
@@ -1,7 +1,6 @@
using System;
using System.Linq;
using Artemis.Core.LayerEffects.Placeholder;
using Artemis.Core.Services;
using Artemis.Storage.Entities.Profile;
using Ninject;
@@ -12,13 +11,13 @@ namespace Artemis.Core.LayerEffects
/// </summary>
public class LayerEffectDescriptor
{
internal LayerEffectDescriptor(string displayName, string description, string icon, Type layerEffectType, LayerEffectProvider layerEffectProvider)
internal LayerEffectDescriptor(string displayName, string description, string icon, Type layerEffectType, LayerEffectProvider provider)
{
DisplayName = displayName;
Description = description;
Icon = icon;
LayerEffectType = layerEffectType;
LayerEffectProvider = layerEffectProvider;
Provider = provider;
}
/// <summary>
@@ -45,12 +44,12 @@ namespace Artemis.Core.LayerEffects
/// <summary>
/// The plugin that provided this <see cref="LayerEffectDescriptor" />
/// </summary>
public LayerEffectProvider LayerEffectProvider { get; }
public LayerEffectProvider? Provider { get; }
/// <summary>
/// Gets the GUID this descriptor is acting as a placeholder for. If null, this descriptor is not a placeholder
/// </summary>
public Guid? PlaceholderFor { get; internal set; }
public string? PlaceholderFor { get; internal set; }
/// <summary>
/// Creates an instance of the described effect and applies it to the render element
@@ -67,7 +66,10 @@ namespace Artemis.Core.LayerEffects
return;
}
BaseLayerEffect effect = (BaseLayerEffect) LayerEffectProvider.PluginInfo.Kernel.Get(LayerEffectType);
if (Provider == null)
throw new ArtemisCoreException("Cannot create an instance of a layer effect because this descriptor is not a placeholder but is still missing its provider");
BaseLayerEffect effect = (BaseLayerEffect) Provider.Plugin.Kernel!.Get(LayerEffectType);
effect.ProfileElement = renderElement;
effect.EntityId = entity.Id;
effect.Order = entity.Order;
@@ -83,7 +85,7 @@ namespace Artemis.Core.LayerEffects
private void CreatePlaceHolderInstance(RenderProfileElement renderElement, LayerEffectEntity entity)
{
PlaceholderLayerEffect effect = new PlaceholderLayerEffect(entity, PlaceholderFor.Value) {ProfileElement = renderElement, Descriptor = this};
PlaceholderLayerEffect effect = new PlaceholderLayerEffect(entity, PlaceholderFor) {ProfileElement = renderElement, Descriptor = this};
effect.Initialize();
renderElement.ActivateLayerEffect(effect);
}
@@ -7,7 +7,7 @@ namespace Artemis.Core.LayerEffects
/// <summary>
/// Allows you to register one or more <see cref="LayerEffect{T}" />s usable by profile layers.
/// </summary>
public abstract class LayerEffectProvider : PluginImplementation
public abstract class LayerEffectProvider : PluginFeature
{
private readonly List<LayerEffectDescriptor> _layerEffectDescriptors;
@@ -39,7 +39,7 @@ namespace Artemis.Core.LayerEffects
protected void RegisterLayerEffectDescriptor<T>(string displayName, string description, string icon) where T : BaseLayerEffect
{
if (!IsEnabled)
throw new ArtemisPluginException(PluginInfo, "Can only add a layer effect descriptor when the plugin is enabled");
throw new ArtemisPluginFeatureException(this, "Can only add a layer effect descriptor when the plugin is enabled");
LayerEffectDescriptor descriptor = new LayerEffectDescriptor(displayName, description, icon, typeof(T), this);
_layerEffectDescriptors.Add(descriptor);
@@ -1,5 +1,4 @@
using System;
using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Entities.Profile;
using SkiaSharp;
namespace Artemis.Core.LayerEffects.Placeholder
@@ -9,7 +8,7 @@ namespace Artemis.Core.LayerEffects.Placeholder
/// </summary>
internal class PlaceholderLayerEffect : LayerEffect<PlaceholderProperties>
{
internal PlaceholderLayerEffect(LayerEffectEntity originalEntity, Guid placeholderFor)
internal PlaceholderLayerEffect(LayerEffectEntity originalEntity, string placeholderFor)
{
OriginalEntity = originalEntity;
PlaceholderFor = placeholderFor;
@@ -21,8 +20,9 @@ namespace Artemis.Core.LayerEffects.Placeholder
HasBeenRenamed = OriginalEntity.HasBeenRenamed;
}
public string PlaceholderFor { get; }
internal LayerEffectEntity OriginalEntity { get; }
public Guid PlaceholderFor { get; }
/// <inheritdoc />
public override void EnableLayerEffect()
@@ -56,7 +56,7 @@ namespace Artemis.Core.LayerEffects.Placeholder
}
/// <summary>
/// This is in place so that the UI has something to show
/// This is in place so that the UI has something to show
/// </summary>
internal class PlaceholderProperties : LayerPropertyGroup
{
@@ -1,14 +1,12 @@
using System;
namespace Artemis.Core.LayerEffects.Placeholder
namespace Artemis.Core.LayerEffects.Placeholder
{
internal static class PlaceholderLayerEffectDescriptor
{
public static LayerEffectDescriptor Create(Guid missingPluginGuid)
public static LayerEffectDescriptor Create(string missingProviderId)
{
LayerEffectDescriptor descriptor = new LayerEffectDescriptor("Missing effect", "This effect could not be loaded", "FileQuestion", null, Constants.EffectPlaceholderPlugin)
{
PlaceholderFor = missingPluginGuid
PlaceholderFor = missingProviderId
};
return descriptor;
@@ -0,0 +1,10 @@
namespace Artemis.Core.Modules
{
/// <summary>
/// The base class for any view model that belongs to a module
/// </summary>
public interface IModuleViewModel
{
}
}
+4 -4
View File
@@ -42,13 +42,13 @@ namespace Artemis.Core.Modules
/// <returns></returns>
public virtual DataModelPropertyAttribute GetDataModelDescription()
{
return new DataModelPropertyAttribute {Name = PluginInfo.Name, Description = PluginInfo.Description};
return new DataModelPropertyAttribute {Name = Plugin.Info.Name, Description = Plugin.Info.Description};
}
internal override void InternalEnable()
{
DataModel = Activator.CreateInstance<T>();
DataModel.Implementation = PluginInfo;
DataModel.Feature = this;
DataModel.DataModelDescription = GetDataModelDescription();
base.InternalEnable();
}
@@ -64,7 +64,7 @@ namespace Artemis.Core.Modules
/// <summary>
/// Allows you to add support for new games/applications
/// </summary>
public abstract class Module : DataModelPluginImplementation
public abstract class Module : DataModelPluginFeature
{
/// <summary>
/// The modules display name that's shown in the menu
@@ -236,7 +236,7 @@ namespace Artemis.Core.Modules
if (Entity == null)
Entity = new ModuleSettingsEntity();
Entity.PluginGuid = PluginInfo.Guid;
Entity.ModuleId = Id;
Entity.PriorityCategory = (int) PriorityCategory;
Entity.Priority = Priority;
}
@@ -3,7 +3,7 @@
namespace Artemis.Core.Modules
{
/// <inheritdoc />
public class ModuleTab<T> : ModuleTab where T : ModuleViewModel
public class ModuleTab<T> : ModuleTab where T : IModuleViewModel
{
/// <summary>
/// Initializes a new instance of the <see cref="ModuleTab{T}" /> class
@@ -1,26 +0,0 @@
using Stylet;
namespace Artemis.Core.Modules
{
/// <summary>
/// The base class for any view model that belongs to a module
/// </summary>
public abstract class ModuleViewModel : Screen
{
/// <summary>
/// The base class for any view model that belongs to a module
/// </summary>
/// <param name="module">The module this view model belongs to</param>
/// <param name="displayName">The name of the tab that's shown on the modules UI page</param>
protected ModuleViewModel(Module module, string displayName)
{
Module = module;
DisplayName = displayName.ToUpper();
}
/// <summary>
/// Gets the module this view model belongs to
/// </summary>
public Module Module { get; }
}
}
@@ -46,7 +46,7 @@ namespace Artemis.Core.Modules
/// <returns></returns>
public virtual DataModelPropertyAttribute GetDataModelDescription()
{
return new DataModelPropertyAttribute {Name = PluginInfo.Name, Description = PluginInfo.Description};
return new DataModelPropertyAttribute {Name = Plugin.Info.Name, Description = Plugin.Info.Description};
}
/// <summary>
@@ -74,7 +74,7 @@ namespace Artemis.Core.Modules
internal override void InternalEnable()
{
DataModel = Activator.CreateInstance<T>();
DataModel.Implementation = PluginInfo;
DataModel.Feature = this;
DataModel.DataModelDescription = GetDataModelDescription();
base.InternalEnable();
}
@@ -184,7 +184,7 @@ namespace Artemis.Core.Modules
internal async Task ChangeActiveProfileAnimated(Profile profile, ArtemisSurface surface)
{
if (profile != null && profile.Module != this)
throw new ArtemisCoreException($"Cannot activate a profile of module {profile.Module} on a module of plugin {PluginInfo}.");
throw new ArtemisCoreException($"Cannot activate a profile of module {profile.Module} on a module of plugin {this}.");
if (!IsActivated)
throw new ArtemisCoreException("Cannot activate a profile on a deactivated module");
@@ -206,7 +206,7 @@ namespace Artemis.Core.Modules
internal void ChangeActiveProfile(Profile profile, ArtemisSurface surface)
{
if (profile != null && profile.Module != this)
throw new ArtemisCoreException($"Cannot activate a profile of module {profile.Module} on a module of plugin {PluginInfo}.");
throw new ArtemisCoreException($"Cannot activate a profile of module {profile.Module} on a module of plugin {this}.");
if (!IsActivated)
throw new ArtemisCoreException("Cannot activate a profile on a deactivated module");
+130 -31
View File
@@ -7,16 +7,15 @@ using System.Reflection;
using Artemis.Storage.Entities.Plugins;
using McMaster.NETCore.Plugins;
using Ninject;
using Stylet;
namespace Artemis.Core
{
/// <summary>
/// Represents a plugin
/// </summary>
public class Plugin : PropertyChangedBase, IDisposable
public class Plugin : CorePropertyChanged, IDisposable
{
private readonly List<PluginImplementation> _implementations;
private readonly List<PluginFeature> _features;
private bool _isEnabled;
@@ -25,7 +24,7 @@ namespace Artemis.Core
Info = info;
Directory = directory;
_implementations = new List<PluginImplementation>();
_features = new List<PluginFeature>();
}
/// <summary>
@@ -46,8 +45,8 @@ namespace Artemis.Core
/// <summary>
/// Gets or sets a configuration dialog for this plugin that is accessible in the UI under Settings > Plugins
/// </summary>
public PluginConfigurationDialog? ConfigurationDialog { get; protected set; }
public IPluginConfigurationDialog? ConfigurationDialog { get; set; }
/// <summary>
/// Indicates whether the user enabled the plugin or not
/// </summary>
@@ -58,19 +57,24 @@ namespace Artemis.Core
}
/// <summary>
/// Gets a read-only collection of all implementations this plugin provides
/// Gets a read-only collection of all features this plugin provides
/// </summary>
public ReadOnlyCollection<PluginImplementation> Implementations => _implementations.AsReadOnly();
public ReadOnlyCollection<PluginFeature> Features => _features.AsReadOnly();
/// <summary>
/// The assembly the plugin code lives in
/// </summary>
internal Assembly? Assembly { get; set; }
public Assembly? Assembly { get; internal set; }
/// <summary>
/// Gets the plugin bootstrapper
/// </summary>
public IPluginBootstrapper? Bootstrapper { get; internal set; }
/// <summary>
/// The Ninject kernel of the plugin
/// </summary>
internal IKernel? Kernel { get; set; }
public IKernel? Kernel { get; internal set; }
/// <summary>
/// The PluginLoader backing this plugin
@@ -80,7 +84,7 @@ namespace Artemis.Core
/// <summary>
/// The entity representing the plugin
/// </summary>
internal PluginEntity? Entity { get; set; }
internal PluginEntity Entity { get; set; }
/// <summary>
/// Resolves the relative path provided in the <paramref name="path" /> parameter to an absolute path
@@ -92,25 +96,15 @@ namespace Artemis.Core
return path == null ? null : Path.Combine(Directory.FullName, path);
}
internal void ApplyToEntity()
/// <summary>
/// Looks up the instance of the feature of type <typeparamref name="T" />
/// <para>Note: This method only returns instances of enabled features</para>
/// </summary>
/// <typeparam name="T">The type of feature to find</typeparam>
/// <returns>If found, the instance of the feature</returns>
public T? GetFeature<T>() where T : PluginFeature
{
Entity.Id = Guid;
Entity.IsEnabled = IsEnabled;
}
internal void AddImplementation(PluginImplementation implementation)
{
implementation.Plugin = this;
_implementations.Add(implementation);
}
public void SetEnabled(bool enable)
{
if (IsEnabled == enable)
return;
if (!enable && Implementations.Any(e => e.IsEnabled))
throw new ArtemisCoreException("Cannot disable this plugin because it still has enabled implementations");
return _features.FirstOrDefault(i => i is T) as T;
}
/// <inheritdoc />
@@ -119,13 +113,118 @@ namespace Artemis.Core
return Info.ToString();
}
internal void ApplyToEntity()
{
Entity.Id = Guid;
Entity.IsEnabled = IsEnabled;
}
internal void AddFeature(PluginFeature feature)
{
feature.Plugin = this;
_features.Add(feature);
OnFeatureAdded(new PluginFeatureEventArgs(feature));
}
internal void RemoveFeature(PluginFeature feature)
{
_features.Remove(feature);
feature.InternalDisable();
feature.Dispose();
OnFeatureRemoved(new PluginFeatureEventArgs(feature));
}
internal void SetEnabled(bool enable)
{
if (IsEnabled == enable)
return;
if (!enable && Features.Any(e => e.IsEnabled))
throw new ArtemisCoreException("Cannot disable this plugin because it still has enabled features");
IsEnabled = enable;
if (enable)
{
Bootstrapper?.Enable(this);
OnEnabled();
}
else
{
Bootstrapper?.Disable(this);
OnDisabled();
}
}
public void Dispose()
{
foreach (PluginImplementation pluginImplementation in Implementations)
pluginImplementation.Dispose();
foreach (PluginFeature feature in Features)
feature.Dispose();
Kernel?.Dispose();
PluginLoader?.Dispose();
_features.Clear();
SetEnabled(false);
}
#region Events
/// <summary>
/// Occurs when the plugin is enabled
/// </summary>
public event EventHandler? Enabled;
/// <summary>
/// Occurs when the plugin is disabled
/// </summary>
public event EventHandler? Disabled;
/// <summary>
/// Occurs when an feature is loaded and added to the plugin
/// </summary>
public event EventHandler<PluginFeatureEventArgs>? FeatureAdded;
/// <summary>
/// Occurs when an feature is disabled and removed from the plugin
/// </summary>
public event EventHandler<PluginFeatureEventArgs>? FeatureRemoved;
/// <summary>
/// Invokes the Enabled event
/// </summary>
protected virtual void OnEnabled()
{
Enabled?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Invokes the Disabled event
/// </summary>
protected virtual void OnDisabled()
{
Disabled?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Invokes the FeatureAdded event
/// </summary>
protected virtual void OnFeatureAdded(PluginFeatureEventArgs e)
{
FeatureAdded?.Invoke(this, e);
}
/// <summary>
/// Invokes the FeatureRemoved event
/// </summary>
protected virtual void OnFeatureRemoved(PluginFeatureEventArgs e)
{
FeatureRemoved?.Invoke(this, e);
}
#endregion
}
}
@@ -1,27 +0,0 @@
using System;
namespace Artemis.Core
{
/// <inheritdoc />
public class PluginConfigurationDialog<T> : PluginConfigurationDialog where T : PluginConfigurationViewModel
{
/// <inheritdoc />
public override Type Type => typeof(T);
}
/// <summary>
/// Describes a configuration dialog for a specific plugin
/// </summary>
public abstract class PluginConfigurationDialog
{
/// <summary>
/// The layer brush this dialog belongs to
/// </summary>
internal PluginImplementation PluginImplementation { get; set; }
/// <summary>
/// The type of view model the tab contains
/// </summary>
public abstract Type Type { get; }
}
}
@@ -1,34 +0,0 @@
using Stylet;
namespace Artemis.Core
{
/// <summary>
/// Represents a view model for a plugin configuration window
/// </summary>
public abstract class PluginConfigurationViewModel : Screen
{
/// <summary>
/// Creates a new instance of the <see cref="PluginConfigurationViewModel" /> class
/// </summary>
/// <param name="pluginImplementation"></param>
protected PluginConfigurationViewModel(PluginImplementation pluginImplementation)
{
PluginImplementation = pluginImplementation;
}
/// <summary>
/// Creates a new instance of the <see cref="PluginConfigurationViewModel" /> class with a validator
/// </summary>
/// <param name="pluginImplementation"></param>
/// <param name="validator"></param>
protected PluginConfigurationViewModel(PluginImplementation pluginImplementation, IModelValidator validator) : base(validator)
{
PluginImplementation = pluginImplementation;
}
/// <summary>
/// Gets the plugin this configuration view model is associated with
/// </summary>
public PluginImplementation PluginImplementation { get; }
}
}
@@ -1,24 +1,22 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Artemis.Storage.Entities.Plugins;
using Stylet;
namespace Artemis.Core
{
/// <summary>
/// Represents an implementation of a certain type provided by a plugin
/// Represents an feature of a certain type provided by a plugin
/// </summary>
public abstract class PluginImplementation : PropertyChangedBase, IDisposable
public abstract class PluginFeature : CorePropertyChanged, IDisposable
{
private Exception? _loadException;
private bool _isEnabled;
private Exception? _loadException;
/// <summary>
/// Gets the plugin that provides this implementation
/// Gets the plugin that provides this feature
/// </summary>
public Plugin? Plugin { get; internal set; }
public Plugin Plugin { get; internal set; }
/// <summary>
/// Gets whether the plugin is enabled
@@ -38,15 +36,20 @@ namespace Artemis.Core
internal set => SetAndNotify(ref _loadException, value);
}
internal PluginImplementationEntity? Entity { get; set; }
/// <summary>
/// Gets the identifier of this plugin feature
/// </summary>
public string Id => $"{GetType().FullName}-{Plugin.Guid.ToString().Substring(0, 8)}"; // Not as unique as a GUID but good enough and stays readable
internal PluginFeatureEntity Entity { get; set; }
/// <summary>
/// Called when the implementation is activated
/// Called when the feature is activated
/// </summary>
public abstract void Enable();
/// <summary>
/// Called when the implementation is deactivated or when Artemis shuts down
/// Called when the feature is deactivated or when Artemis shuts down
/// </summary>
public abstract void Disable();
@@ -56,12 +59,12 @@ namespace Artemis.Core
return;
if (Plugin == null)
throw new ArtemisCoreException("Cannot enable a plugin implementation that is not associated with a plugin");
throw new ArtemisCoreException("Cannot enable a plugin feature that is not associated with a plugin");
lock (Plugin)
{
if (!Plugin.IsEnabled)
throw new ArtemisCoreException("Cannot enable a plugin implementation of a disabled plugin");
throw new ArtemisCoreException("Cannot enable a plugin feature of a disabled plugin");
if (!enable)
{
@@ -72,7 +75,7 @@ namespace Artemis.Core
OnDisabled();
return;
}
try
{
if (isAutoEnable && GetLockFileCreated())
@@ -89,19 +92,10 @@ namespace Artemis.Core
// Allow up to 15 seconds for plugins to activate.
// This means plugins that need more time should do their long running tasks in a background thread, which is intentional
ManualResetEvent wait = new ManualResetEvent(false);
Thread work = new Thread(() =>
{
InternalEnable();
wait.Set();
});
work.Start();
wait.WaitOne(TimeSpan.FromSeconds(15));
if (work.IsAlive)
{
work.Abort();
// This would've been a perfect match for Thread.Abort but that didn't make it into .NET Core
Task enableTask = Task.Run(InternalEnable);
if (!enableTask.Wait(TimeSpan.FromSeconds(15)))
throw new ArtemisPluginException(Plugin, "Plugin load timeout");
}
LoadException = null;
OnEnabled();
@@ -144,7 +138,7 @@ namespace Artemis.Core
internal void CreateLockFile()
{
if (Plugin == null)
throw new ArtemisCoreException("Cannot lock a plugin implementation that is not associated with a plugin");
throw new ArtemisCoreException("Cannot lock a plugin feature that is not associated with a plugin");
File.Create(Plugin.ResolveRelativePath($"{GetType().FullName}.lock")).Close();
}
@@ -152,7 +146,7 @@ namespace Artemis.Core
internal void DeleteLockFile()
{
if (Plugin == null)
throw new ArtemisCoreException("Cannot lock a plugin implementation that is not associated with a plugin");
throw new ArtemisCoreException("Cannot lock a plugin feature that is not associated with a plugin");
if (GetLockFileCreated())
File.Delete(Plugin.ResolveRelativePath($"{GetType().FullName}.lock"));
@@ -161,7 +155,7 @@ namespace Artemis.Core
internal bool GetLockFileCreated()
{
if (Plugin == null)
throw new ArtemisCoreException("Cannot lock a plugin implementation that is not associated with a plugin");
throw new ArtemisCoreException("Cannot lock a plugin feature that is not associated with a plugin");
return File.Exists(Plugin.ResolveRelativePath($"{GetType().FullName}.lock"));
}
@@ -171,17 +165,17 @@ namespace Artemis.Core
#region Events
/// <summary>
/// Occurs when the implementation is enabled
/// Occurs when the feature is enabled
/// </summary>
public event EventHandler? Enabled;
/// <summary>
/// Occurs when the implementation is disabled
/// Occurs when the feature is disabled
/// </summary>
public event EventHandler? Disabled;
/// <summary>
/// Triggers the PluginEnabled event
/// Triggers the Enabled event
/// </summary>
protected virtual void OnEnabled()
{
@@ -189,7 +183,7 @@ namespace Artemis.Core
}
/// <summary>
/// Triggers the PluginDisabled event
/// Triggers the Disabled event
/// </summary>
protected virtual void OnDisabled()
{
+1 -2
View File
@@ -1,6 +1,5 @@
using System;
using Newtonsoft.Json;
using Stylet;
namespace Artemis.Core
{
@@ -8,7 +7,7 @@ namespace Artemis.Core
/// Represents basic info about a plugin and contains a reference to the instance of said plugin
/// </summary>
[JsonObject(MemberSerialization.OptIn)]
public class PluginInfo : PropertyChangedBase
public class PluginInfo : CorePropertyChanged
{
private string _description;
private Guid _guid;
@@ -2,7 +2,6 @@
using Artemis.Storage.Entities.Plugins;
using Artemis.Storage.Repositories.Interfaces;
using Newtonsoft.Json;
using Stylet;
namespace Artemis.Core
{
@@ -10,17 +9,18 @@ namespace Artemis.Core
/// Represents a setting tied to a plugin of type <typeparamref name="T" />
/// </summary>
/// <typeparam name="T">The value type of the setting</typeparam>
public class PluginSetting<T> : PropertyChangedBase
public class PluginSetting<T> : CorePropertyChanged
{
// ReSharper disable once NotAccessedField.Local
private readonly PluginInfo _pluginInfo;
// TODO: Why? Should have included that...
// ReSharper disable once NotAccessedField.Local
private readonly Plugin _plugin;
private readonly IPluginRepository _pluginRepository;
private readonly PluginSettingEntity _pluginSettingEntity;
private T _value;
internal PluginSetting(PluginInfo pluginInfo, IPluginRepository pluginRepository, PluginSettingEntity pluginSettingEntity)
internal PluginSetting(Plugin plugin, IPluginRepository pluginRepository, PluginSettingEntity pluginSettingEntity)
{
_pluginInfo = pluginInfo;
_plugin = plugin;
_pluginRepository = pluginRepository;
_pluginSettingEntity = pluginSettingEntity;
@@ -52,7 +52,7 @@ namespace Artemis.Core
_value = value;
OnSettingChanged();
NotifyOfPropertyChange(nameof(Value));
OnPropertyChanged(nameof(Value));
if (AutoSave)
Save();
@@ -14,17 +14,17 @@ namespace Artemis.Core
private readonly IPluginRepository _pluginRepository;
private readonly Dictionary<string, object> _settingEntities;
internal PluginSettings(PluginInfo pluginInfo, IPluginRepository pluginRepository)
internal PluginSettings(Plugin plugin, IPluginRepository pluginRepository)
{
PluginInfo = pluginInfo;
Plugin = plugin;
_pluginRepository = pluginRepository;
_settingEntities = new Dictionary<string, object>();
}
/// <summary>
/// Gets the info of the plugin this setting belongs to
/// Gets the plugin these settings belong to
/// </summary>
public PluginInfo PluginInfo { get; }
public Plugin Plugin { get; }
/// <summary>
/// Gets the setting with the provided name. If the setting does not exist yet, it is created.
@@ -41,15 +41,15 @@ namespace Artemis.Core
if (_settingEntities.ContainsKey(name))
return (PluginSetting<T>) _settingEntities[name];
// Try to find in database
PluginSettingEntity settingEntity = _pluginRepository.GetSettingByNameAndGuid(name, PluginInfo.Guid);
PluginSettingEntity settingEntity = _pluginRepository.GetSettingByNameAndGuid(name, Plugin.Guid);
// If not found, create a new one
if (settingEntity == null)
{
settingEntity = new PluginSettingEntity {Name = name, PluginGuid = PluginInfo.Guid, Value = JsonConvert.SerializeObject(defaultValue)};
settingEntity = new PluginSettingEntity {Name = name, PluginGuid = Plugin.Guid, Value = JsonConvert.SerializeObject(defaultValue)};
_pluginRepository.AddSetting(settingEntity);
}
PluginSetting<T> pluginSetting = new PluginSetting<T>(PluginInfo, _pluginRepository, settingEntity);
PluginSetting<T> pluginSetting = new PluginSetting<T>(Plugin, _pluginRepository, settingEntity);
// This overrides null with the default value, I'm not sure if that's desirable because you
// might expect something to go null and you might not
@@ -8,40 +8,41 @@ namespace Artemis.Core
/// <summary>
/// Represents a registration for a timed plugin update
/// </summary>
public class TimedUpdateRegistration
public class TimedUpdateRegistration : IDisposable
{
private DateTime _lastEvent;
private Timer _timer;
private bool _disposed;
internal TimedUpdateRegistration(PluginInfo pluginInfo, TimeSpan interval, Action<double> action)
internal TimedUpdateRegistration(PluginFeature feature, TimeSpan interval, Action<double> action)
{
PluginInfo = pluginInfo;
Feature = feature;
Interval = interval;
Action = action;
PluginInfo.Plugin.Enabled += InstanceOnEnabled;
PluginInfo.Plugin.Disabled += InstanceOnDisabled;
if (PluginInfo.Plugin.IsEnabled)
Feature.Enabled += FeatureOnEnabled;
Feature.Disabled += FeatureOnDisabled;
if (Feature.IsEnabled)
Start();
}
internal TimedUpdateRegistration(PluginInfo pluginInfo, TimeSpan interval, Func<double, Task> asyncAction)
internal TimedUpdateRegistration(PluginFeature feature, TimeSpan interval, Func<double, Task> asyncAction)
{
PluginInfo = pluginInfo;
Feature = feature;
Interval = interval;
AsyncAction = asyncAction;
PluginInfo.Plugin.Enabled += InstanceOnEnabled;
PluginInfo.Plugin.Disabled += InstanceOnDisabled;
if (PluginInfo.Plugin.IsEnabled)
Feature.Enabled += FeatureOnEnabled;
Feature.Disabled += FeatureOnDisabled;
if (Feature.IsEnabled)
Start();
}
/// <summary>
/// Gets the plugin info of the plugin this registration is associated with
/// Gets the plugin feature this registration is associated with
/// </summary>
public PluginInfo PluginInfo { get; }
public PluginFeature Feature { get; }
/// <summary>
/// Gets the interval at which the update should occur
@@ -64,10 +65,13 @@ namespace Artemis.Core
/// </summary>
public void Start()
{
if (_disposed)
throw new ObjectDisposedException("TimedUpdateRegistration");
lock (this)
{
if (!PluginInfo.Plugin.IsEnabled)
throw new ArtemisPluginException("Cannot start a timed update for a disabled plugin");
if (!Feature.IsEnabled)
throw new ArtemisPluginException("Cannot start a timed update for a disabled plugin feature");
if (_timer != null)
return;
@@ -85,6 +89,9 @@ namespace Artemis.Core
/// </summary>
public void Stop()
{
if (_disposed)
throw new ObjectDisposedException("TimedUpdateRegistration");
lock (this)
{
if (_timer == null)
@@ -99,7 +106,7 @@ namespace Artemis.Core
private void TimerOnElapsed(object sender, ElapsedEventArgs e)
{
if (!PluginInfo.Plugin.IsEnabled)
if (!Feature.IsEnabled)
return;
lock (this)
@@ -108,7 +115,7 @@ namespace Artemis.Core
_lastEvent = DateTime.Now;
// Modules don't always want to update, honor that
if (PluginInfo.Plugin is Module module && !module.IsUpdateAllowed)
if (Feature is Module module && !module.IsUpdateAllowed)
return;
if (Action != null)
@@ -121,14 +128,25 @@ namespace Artemis.Core
}
}
private void InstanceOnEnabled(object sender, EventArgs e)
private void FeatureOnEnabled(object sender, EventArgs e)
{
Start();
}
private void InstanceOnDisabled(object sender, EventArgs e)
private void FeatureOnDisabled(object sender, EventArgs e)
{
Stop();
}
/// <inheritdoc />
public void Dispose()
{
Stop();
Feature.Enabled -= FeatureOnEnabled;
Feature.Disabled -= FeatureOnDisabled;
_disposed = true;
}
}
}